From afcbf1c52e5d9813ccb5e745da56839294bcb68a Mon Sep 17 00:00:00 2001 From: Felipe <84182706+felipe-rod123@users.noreply.github.com> Date: Tue, 7 Jun 2022 17:12:01 -0300 Subject: [PATCH 01/59] Chore: convert invites, misc and subscriptions to TS and create definitions (#25350) --- apps/meteor/app/api/server/api.d.ts | 16 +- .../api/server/v1/{invites.js => invites.ts} | 54 ++-- .../app/api/server/v1/{misc.js => misc.ts} | 244 +++++++++++++----- .../meteor/app/api/server/v1/subscriptions.js | 99 ------- .../meteor/app/api/server/v1/subscriptions.ts | 101 ++++++++ .../server/functions/validateInviteToken.js | 5 +- .../meteor/client/views/invite/InvitePage.tsx | 8 +- .../externals/meteor/ddp-rate-limiter.d.ts | 10 + .../tests/end-to-end/api/00-miscellaneous.js | 6 + .../tests/end-to-end/api/04-direct-message.js | 2 + .../meteor/tests/end-to-end/api/23-invites.js | 2 +- .../meteor/tests/end-to-end/api/24-methods.js | 117 +++++++++ apps/meteor/tests/end-to-end/api/25-teams.js | 4 + packages/rest-typings/src/index.ts | 7 + packages/rest-typings/src/v1/invites.ts | 46 +++- packages/rest-typings/src/v1/misc.ts | 213 +++++++++++++++ .../src/v1/subscriptionsEndpoints.ts | 111 ++++++++ 17 files changed, 836 insertions(+), 209 deletions(-) rename apps/meteor/app/api/server/v1/{invites.js => invites.ts} (50%) rename apps/meteor/app/api/server/v1/{misc.js => misc.ts} (74%) delete mode 100644 apps/meteor/app/api/server/v1/subscriptions.js create mode 100644 apps/meteor/app/api/server/v1/subscriptions.ts create mode 100644 apps/meteor/definition/externals/meteor/ddp-rate-limiter.d.ts create mode 100644 packages/rest-typings/src/v1/subscriptionsEndpoints.ts diff --git a/apps/meteor/app/api/server/api.d.ts b/apps/meteor/app/api/server/api.d.ts index 98fcefe501e1..e8a8634e0615 100644 --- a/apps/meteor/app/api/server/api.d.ts +++ b/apps/meteor/app/api/server/api.d.ts @@ -82,6 +82,7 @@ type PartialThis = { }; type ActionThis = { + readonly requestIp: string; urlParams: UrlParams; // TODO make it unsafe readonly queryParams: TMethod extends 'GET' @@ -110,16 +111,29 @@ type ActionThis({ object, userId }: { object: { [key: string]: unknown }; userId: string }): { [key: string]: unknown } & T; composeRoomWithLastMessage(room: IRoom, userId: string): IRoom; } & (TOptions extends { authRequired: true } ? { readonly user: IUser; readonly userId: string; + readonly token: string; } : { readonly user: null; - readonly userId: null; + readonly userId: undefined; + readonly token?: string; }); export type ResultFor = diff --git a/apps/meteor/app/api/server/v1/invites.js b/apps/meteor/app/api/server/v1/invites.ts similarity index 50% rename from apps/meteor/app/api/server/v1/invites.js rename to apps/meteor/app/api/server/v1/invites.ts index 907585a818bc..8ac3a2b8eddb 100644 --- a/apps/meteor/app/api/server/v1/invites.js +++ b/apps/meteor/app/api/server/v1/invites.ts @@ -1,3 +1,7 @@ +/* eslint-disable react-hooks/rules-of-hooks */ +import { IInvite } from '@rocket.chat/core-typings'; +import { isFindOrCreateInviteParams, isUseInviteTokenProps, isValidateInviteTokenProps } from '@rocket.chat/rest-typings'; + import { API } from '../api'; import { findOrCreateInvite } from '../../../invites/server/functions/findOrCreateInvite'; import { removeInvite } from '../../../invites/server/functions/removeInvite'; @@ -7,10 +11,12 @@ import { validateInviteToken } from '../../../invites/server/functions/validateI API.v1.addRoute( 'listInvites', - { authRequired: true }, { - get() { - const result = Promise.await(listInvites(this.userId)); + authRequired: true, + }, + { + async get() { + const result = await listInvites(this.userId); return API.v1.success(result); }, }, @@ -18,13 +24,15 @@ API.v1.addRoute( API.v1.addRoute( 'findOrCreateInvite', - { authRequired: true }, { - post() { + authRequired: true, + validateParams: isFindOrCreateInviteParams, + }, + { + async post() { const { rid, days, maxUses } = this.bodyParams; - const result = Promise.await(findOrCreateInvite(this.userId, { rid, days, maxUses })); - return API.v1.success(result); + return API.v1.success((await findOrCreateInvite(this.userId, { rid, days, maxUses })) as IInvite); }, }, ); @@ -33,44 +41,44 @@ API.v1.addRoute( 'removeInvite/:_id', { authRequired: true }, { - delete() { + async delete() { const { _id } = this.urlParams; - const result = Promise.await(removeInvite(this.userId, { _id })); - return API.v1.success(result); + return API.v1.success(await removeInvite(this.userId, { _id })); }, }, ); API.v1.addRoute( 'useInviteToken', - { authRequired: true }, { - post() { + authRequired: true, + validateParams: isUseInviteTokenProps, + }, + { + async post() { const { token } = this.bodyParams; // eslint-disable-next-line react-hooks/rules-of-hooks - const result = Promise.await(useInviteToken(this.userId, token)); - return API.v1.success(result); + return API.v1.success(await useInviteToken(this.userId, token)); }, }, ); API.v1.addRoute( 'validateInviteToken', - { authRequired: false }, { - post() { + authRequired: false, + validateParams: isValidateInviteTokenProps, + }, + { + async post() { const { token } = this.bodyParams; - - let valid = true; try { - Promise.await(validateInviteToken(token)); - } catch (e) { - valid = false; + return API.v1.success({ valid: Boolean(await validateInviteToken(token)) }); + } catch (_) { + return API.v1.success({ valid: false }); } - - return API.v1.success({ valid }); }, }, ); diff --git a/apps/meteor/app/api/server/v1/misc.js b/apps/meteor/app/api/server/v1/misc.ts similarity index 74% rename from apps/meteor/app/api/server/v1/misc.js rename to apps/meteor/app/api/server/v1/misc.ts index 6d6d94438c6c..9a7b7135a56a 100644 --- a/apps/meteor/app/api/server/v1/misc.js +++ b/apps/meteor/app/api/server/v1/misc.ts @@ -6,6 +6,14 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { EJSON } from 'meteor/ejson'; import { DDPRateLimiter } from 'meteor/ddp-rate-limiter'; import { escapeHTML } from '@rocket.chat/string-helpers'; +import { + isShieldSvgProps, + isSpotlightProps, + isDirectoryProps, + isMethodCallProps, + isMethodCallAnonProps, + isMeteorCall, +} from '@rocket.chat/rest-typings'; import { hasPermission } from '../../../authorization/server'; import { Users } from '../../../models/server'; @@ -176,9 +184,17 @@ API.v1.addRoute( let onlineCache = 0; let onlineCacheDate = 0; const cacheInvalid = 60000; // 1 minute + API.v1.addRoute( 'shield.svg', - { authRequired: false, rateLimiterOptions: { numRequestsAllowed: 60, intervalTimeInMS: 60000 } }, + { + authRequired: false, + rateLimiterOptions: { + numRequestsAllowed: 60, + intervalTimeInMS: 60000, + }, + validateParams: isShieldSvgProps, + }, { get() { const { type, icon } = this.queryParams; @@ -189,13 +205,13 @@ API.v1.addRoute( }); } - const types = settings.get('API_Shield_Types'); + const types = settings.get('API_Shield_Types'); if ( type && types !== '*' && !types .split(',') - .map((t) => t.trim()) + .map((t: string) => t.trim()) .includes(type) ) { throw new Meteor.Error('error-shield-disabled', 'This shield type is disabled', { @@ -297,23 +313,22 @@ API.v1.addRoute( ` .trim() .replace(/\>[\s]+\<'), - }; + } as any; }, }, ); API.v1.addRoute( 'spotlight', - { authRequired: true }, + { + authRequired: true, + validateParams: isSpotlightProps, + }, { get() { - check(this.queryParams, { - query: String, - }); - const { query } = this.queryParams; - const result = Meteor.runAsUser(this.userId, () => Meteor.call('spotlight', query)); + const result = Meteor.call('spotlight', query); return API.v1.success(result); }, @@ -322,7 +337,10 @@ API.v1.addRoute( API.v1.addRoute( 'directory', - { authRequired: true }, + { + authRequired: true, + validateParams: isDirectoryProps, + }, { get() { const { offset, count } = this.getPaginationItems(); @@ -336,17 +354,15 @@ API.v1.addRoute( const sortBy = sort ? Object.keys(sort)[0] : undefined; const sortDirection = sort && Object.values(sort)[0] === 1 ? 'asc' : 'desc'; - const result = Meteor.runAsUser(this.userId, () => - Meteor.call('browseChannels', { - text, - type, - workspace, - sortBy, - sortDirection, - offset: Math.max(0, offset), - limit: Math.max(0, count), - }), - ); + const result = Meteor.call('browseChannels', { + text, + type, + workspace, + sortBy, + sortDirection, + offset: Math.max(0, offset), + limit: Math.max(0, count), + }); if (!result) { return API.v1.failure('Please verify the parameters'); @@ -410,60 +426,154 @@ API.v1.addRoute( }, ); -const mountResult = ({ id, error, result }) => ({ +declare module '@rocket.chat/rest-typings' { + // eslint-disable-next-line @typescript-eslint/interface-name-prefix + interface Endpoints { + 'method.call/:method': { + POST: (params: { method: string; args: any[] }) => any; + }; + 'method.callAnon/:method': { + POST: (params: { method: string; args: any[] }) => any; + }; + } +} + +const mountResult = ({ + id, + error, + result, +}: { + id: string; + error?: unknown; + result?: unknown; +}): { + message: string; +} => ({ message: EJSON.stringify({ msg: 'result', id, - error, - result, + error: error as any, + result: result as any, }), }); -const methodCall = () => ({ - post() { - check(this.bodyParams, { - message: String, - }); - - const { method, params, id } = EJSON.parse(this.bodyParams.message); - - const connectionId = - this.token || - crypto - .createHash('md5') - .update(this.requestIp + this.request.headers['user-agent']) - .digest('hex'); - - const rateLimiterInput = { - userId: this.userId, - clientAddress: this.requestIp, - type: 'method', - name: method, - connectionId, - }; +// had to create two different endpoints for authenticated and non-authenticated calls +// because restivus does not provide 'this.userId' if 'authRequired: false' +API.v1.addRoute( + 'method.call/:method', + { + authRequired: true, + rateLimiterOptions: false, + validateParams: isMeteorCall, + }, + { + post() { + check(this.bodyParams, { + message: String, + }); - try { - DDPRateLimiter._increment(rateLimiterInput); - const rateLimitResult = DDPRateLimiter._check(rateLimiterInput); - if (!rateLimitResult.allowed) { - throw new Meteor.Error('too-many-requests', DDPRateLimiter.getErrorMessage(rateLimitResult), { - timeToReset: rateLimitResult.timeToReset, - }); + const data = EJSON.parse(this.bodyParams.message); + + if (!isMethodCallProps(data)) { + return API.v1.failure('Invalid method call'); } - const result = Meteor.call(method, ...params); - return API.v1.success(mountResult({ id, result })); - } catch (error) { - SystemLogger.error(`Exception while invoking method ${method}`, error.message); - if (settings.get('Log_Level') === '2') { - Meteor._debug(`Exception while invoking method ${method}`, error); + const { method, params, id } = data; + + const connectionId = + this.token || + crypto + .createHash('md5') + .update(this.requestIp + this.request.headers['user-agent']) + .digest('hex'); + + const rateLimiterInput = { + userId: this.userId, + clientAddress: this.requestIp, + type: 'method', + name: method, + connectionId, + }; + + try { + DDPRateLimiter._increment(rateLimiterInput); + const rateLimitResult = DDPRateLimiter._check(rateLimiterInput); + if (!rateLimitResult.allowed) { + throw new Meteor.Error('too-many-requests', DDPRateLimiter.getErrorMessage(rateLimitResult), { + timeToReset: rateLimitResult.timeToReset, + }); + } + + const result = Meteor.call(method, ...params); + return API.v1.success(mountResult({ id, result })); + } catch (error) { + if (error instanceof Error) SystemLogger.error(`Exception while invoking method ${method}`, error.message); + else SystemLogger.error(`Exception while invoking method ${method}`, error); + + if (settings.get('Log_Level') === '2') { + Meteor._debug(`Exception while invoking method ${method}`, error); + } + return API.v1.success(mountResult({ id, error })); } - return API.v1.success(mountResult({ id, error })); - } + }, }, -}); +); +API.v1.addRoute( + 'method.callAnon/:method', + { + authRequired: false, + rateLimiterOptions: false, + validateParams: isMeteorCall, + }, + { + post() { + check(this.bodyParams, { + message: String, + }); -// had to create two different endpoints for authenticated and non-authenticated calls -// because restivus does not provide 'this.userId' if 'authRequired: false' -API.v1.addRoute('method.call/:method', { authRequired: true, rateLimiterOptions: false }, methodCall()); -API.v1.addRoute('method.callAnon/:method', { authRequired: false, rateLimiterOptions: false }, methodCall()); + const data = EJSON.parse(this.bodyParams.message); + + if (!isMethodCallAnonProps(data)) { + return API.v1.failure('Invalid method call'); + } + + const { method, params, id } = data; + + const connectionId = + this.token || + crypto + .createHash('md5') + .update(this.requestIp + this.request.headers['user-agent']) + .digest('hex'); + + const rateLimiterInput = { + userId: this.userId || undefined, + clientAddress: this.requestIp, + type: 'method', + name: method, + connectionId, + }; + + try { + DDPRateLimiter._increment(rateLimiterInput); + const rateLimitResult = DDPRateLimiter._check(rateLimiterInput); + if (!rateLimitResult.allowed) { + throw new Meteor.Error('too-many-requests', DDPRateLimiter.getErrorMessage(rateLimitResult), { + timeToReset: rateLimitResult.timeToReset, + }); + } + + const result = Meteor.call(method, ...params); + return API.v1.success(mountResult({ id, result })); + } catch (error) { + if (error instanceof Error) SystemLogger.error(`Exception while invoking method ${method}`, error.message); + else SystemLogger.error(`Exception while invoking method ${method}`, error); + + if (settings.get('Log_Level') === '2') { + Meteor._debug(`Exception while invoking method ${method}`, error); + } + return API.v1.success(mountResult({ id, error })); + } + }, + }, +); diff --git a/apps/meteor/app/api/server/v1/subscriptions.js b/apps/meteor/app/api/server/v1/subscriptions.js deleted file mode 100644 index 6624ede0e6c4..000000000000 --- a/apps/meteor/app/api/server/v1/subscriptions.js +++ /dev/null @@ -1,99 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { check } from 'meteor/check'; - -import { Subscriptions } from '../../../models'; -import { API } from '../api'; - -API.v1.addRoute( - 'subscriptions.get', - { authRequired: true }, - { - get() { - const { updatedSince } = this.queryParams; - - let updatedSinceDate; - if (updatedSince) { - if (isNaN(Date.parse(updatedSince))) { - throw new Meteor.Error('error-roomId-param-invalid', 'The "lastUpdate" query parameter must be a valid date.'); - } else { - updatedSinceDate = new Date(updatedSince); - } - } - - let result; - Meteor.runAsUser(this.userId, () => { - result = Meteor.call('subscriptions/get', updatedSinceDate); - }); - - if (Array.isArray(result)) { - result = { - update: result, - remove: [], - }; - } - - return API.v1.success(result); - }, - }, -); - -API.v1.addRoute( - 'subscriptions.getOne', - { authRequired: true }, - { - get() { - const { roomId } = this.requestParams(); - - if (!roomId) { - return API.v1.failure("The 'roomId' param is required"); - } - - const subscription = Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId); - - return API.v1.success({ - subscription, - }); - }, - }, -); - -/** - This API is suppose to mark any room as read. - - Method: POST - Route: api/v1/subscriptions.read - Params: - - rid: The rid of the room to be marked as read. - */ -API.v1.addRoute( - 'subscriptions.read', - { authRequired: true }, - { - post() { - check(this.bodyParams, { - rid: String, - }); - - Meteor.runAsUser(this.userId, () => Meteor.call('readMessages', this.bodyParams.rid)); - - return API.v1.success(); - }, - }, -); - -API.v1.addRoute( - 'subscriptions.unread', - { authRequired: true }, - { - post() { - const { roomId, firstUnreadMessage } = this.bodyParams; - if (!roomId && firstUnreadMessage && !firstUnreadMessage._id) { - return API.v1.failure('At least one of "roomId" or "firstUnreadMessage._id" params is required'); - } - - Meteor.runAsUser(this.userId, () => Meteor.call('unreadMessages', firstUnreadMessage, roomId)); - - return API.v1.success(); - }, - }, -); diff --git a/apps/meteor/app/api/server/v1/subscriptions.ts b/apps/meteor/app/api/server/v1/subscriptions.ts new file mode 100644 index 000000000000..c042863eb1ec --- /dev/null +++ b/apps/meteor/app/api/server/v1/subscriptions.ts @@ -0,0 +1,101 @@ +import { Meteor } from 'meteor/meteor'; +import { + isSubscriptionsGetProps, + isSubscriptionsGetOneProps, + isSubscriptionsReadProps, + isSubscriptionsUnreadProps, +} from '@rocket.chat/rest-typings'; + +import { Subscriptions } from '../../../models/server/raw'; +import { API } from '../api'; + +API.v1.addRoute( + 'subscriptions.get', + { + authRequired: true, + validateParams: isSubscriptionsGetProps, + }, + { + async get() { + const { updatedSince } = this.queryParams; + + let updatedSinceDate: Date | undefined; + if (updatedSince) { + if (isNaN(Date.parse(updatedSince as string))) { + throw new Meteor.Error('error-roomId-param-invalid', 'The "lastUpdate" query parameter must be a valid date.'); + } + updatedSinceDate = new Date(updatedSince as string); + } + + const result = await Meteor.call('subscriptions/get', updatedSinceDate); + + return API.v1.success( + Array.isArray(result) + ? { + update: result, + remove: [], + } + : result, + ); + }, + }, +); + +API.v1.addRoute( + 'subscriptions.getOne', + { + authRequired: true, + validateParams: isSubscriptionsGetOneProps, + }, + { + async get() { + const { roomId }: { [roomId: string]: {} } | Record = this.queryParams; + + if (!roomId) { + return API.v1.failure("The 'roomId' param is required"); + } + + return API.v1.success({ + subscription: await Subscriptions.findOneByRoomIdAndUserId(roomId as string, this.userId), + }); + }, + }, +); + +/** + This API is suppose to mark any room as read. + + Method: POST + Route: api/v1/subscriptions.read + Params: + - rid: The rid of the room to be marked as read. + */ +API.v1.addRoute( + 'subscriptions.read', + { + authRequired: true, + validateParams: isSubscriptionsReadProps, + }, + { + post() { + Meteor.call('readMessages', this.bodyParams.rid); + + return API.v1.success(); + }, + }, +); + +API.v1.addRoute( + 'subscriptions.unread', + { + authRequired: true, + validateParams: isSubscriptionsUnreadProps, + }, + { + post() { + Meteor.call('unreadMessages', (this.bodyParams as any).firstUnreadMessage, (this.bodyParams as any).roomId); + + return API.v1.success(); + }, + }, +); diff --git a/apps/meteor/app/invites/server/functions/validateInviteToken.js b/apps/meteor/app/invites/server/functions/validateInviteToken.js index 385d55ca0ee1..99603999f5ae 100644 --- a/apps/meteor/app/invites/server/functions/validateInviteToken.js +++ b/apps/meteor/app/invites/server/functions/validateInviteToken.js @@ -1,7 +1,6 @@ import { Meteor } from 'meteor/meteor'; -import { Rooms } from '../../../models'; -import { Invites } from '../../../models/server/raw'; +import { Rooms, Invites } from '../../../models/server/raw'; export const validateInviteToken = async (token) => { if (!token || typeof token !== 'string') { @@ -19,7 +18,7 @@ export const validateInviteToken = async (token) => { }); } - const room = Rooms.findOneById(inviteData.rid); + const room = await Rooms.findOneById(inviteData.rid); if (!room) { throw new Meteor.Error('error-invalid-room', 'The invite token is invalid.', { method: 'validateInviteToken', diff --git a/apps/meteor/client/views/invite/InvitePage.tsx b/apps/meteor/client/views/invite/InvitePage.tsx index 98e3448dbe82..319f997ba089 100644 --- a/apps/meteor/client/views/invite/InvitePage.tsx +++ b/apps/meteor/client/views/invite/InvitePage.tsx @@ -36,9 +36,9 @@ const InvitePage = (): ReactElement => { try { const { valid } = await APIClient.v1.post< - OperationParams<'POST', '/v1/validateInviteToken'>, + OperationParams<'POST', 'validateInviteToken'>, never, - OperationResult<'POST', '/v1/validateInviteToken'> + OperationResult<'POST', 'validateInviteToken'> >('validateInviteToken', { token }); return valid; @@ -65,9 +65,9 @@ const InvitePage = (): ReactElement => { try { const result = await APIClient.v1.post< - OperationParams<'POST', '/v1/useInviteToken'>, + OperationParams<'POST', 'useInviteToken'>, never, - OperationResult<'POST', '/v1/useInviteToken'> + OperationResult<'POST', 'useInviteToken'> >('useInviteToken', { token }); if (!result?.room.name) { dispatchToastMessage({ type: 'error', message: t('Failed_to_activate_invite_token') }); diff --git a/apps/meteor/definition/externals/meteor/ddp-rate-limiter.d.ts b/apps/meteor/definition/externals/meteor/ddp-rate-limiter.d.ts new file mode 100644 index 000000000000..784abd6def11 --- /dev/null +++ b/apps/meteor/definition/externals/meteor/ddp-rate-limiter.d.ts @@ -0,0 +1,10 @@ +declare module 'meteor/ddp-rate-limiter' { + namespace DDPRateLimiter { + function _increment(number: DDPRateLimiter.Matcher): void; + function _check(number: DDPRateLimiter.Matcher): { + allowed: boolean; + timeToReset: number; + }; + function getErrorMessage(result: { allowed: boolean }): string; + } +} diff --git a/apps/meteor/tests/end-to-end/api/00-miscellaneous.js b/apps/meteor/tests/end-to-end/api/00-miscellaneous.js index 674ddccdf96a..35057ab54b3a 100644 --- a/apps/meteor/tests/end-to-end/api/00-miscellaneous.js +++ b/apps/meteor/tests/end-to-end/api/00-miscellaneous.js @@ -564,6 +564,12 @@ describe('miscellaneous', function () { updateSetting('API_Enable_Shields', false).then(() => { request .get(api('shield.svg')) + .query({ + type: 'online', + icon: true, + channel: 'general', + name: 'Rocket.Chat', + }) .expect('Content-Type', 'application/json') .expect(400) .expect((res) => { diff --git a/apps/meteor/tests/end-to-end/api/04-direct-message.js b/apps/meteor/tests/end-to-end/api/04-direct-message.js index 9eb450539ab9..3610b073c34b 100644 --- a/apps/meteor/tests/end-to-end/api/04-direct-message.js +++ b/apps/meteor/tests/end-to-end/api/04-direct-message.js @@ -617,6 +617,8 @@ describe('[Direct Messages]', function () { .set(userCredentials) .send({ message: JSON.stringify({ + id: 'id', + msg: 'method', method: 'saveUserPreferences', params: [{ emailNotificationMode: 'nothing' }], }), diff --git a/apps/meteor/tests/end-to-end/api/23-invites.js b/apps/meteor/tests/end-to-end/api/23-invites.js index 0b2cbc7e6c15..1adcfc5d99e9 100644 --- a/apps/meteor/tests/end-to-end/api/23-invites.js +++ b/apps/meteor/tests/end-to-end/api/23-invites.js @@ -142,7 +142,7 @@ describe('Invites', function () { .expect(400) .expect((res) => { expect(res.body).to.have.property('success', false); - expect(res.body).to.have.property('errorType', 'error-invalid-token'); + expect(res.body).to.have.property('errorType', 'invalid-params'); }) .end(done); }); diff --git a/apps/meteor/tests/end-to-end/api/24-methods.js b/apps/meteor/tests/end-to-end/api/24-methods.js index 65c74c18f17e..67d8f8253c87 100644 --- a/apps/meteor/tests/end-to-end/api/24-methods.js +++ b/apps/meteor/tests/end-to-end/api/24-methods.js @@ -80,6 +80,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'getThreadMessages', params: [], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -99,6 +101,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'getThreadMessages', params: [{ tmid: firstMessage._id }], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -188,6 +192,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'getMessages', params: [], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -207,6 +213,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'getMessages', params: [], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -231,6 +239,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'getMessages', params: [[firstMessage._id]], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -254,6 +264,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'getMessages', params: [[firstMessage._id, lastMessage._id]], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -343,6 +355,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'loadHistory', params: [], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -362,6 +376,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'loadHistory', params: [], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -384,6 +400,8 @@ describe('Meteor.methods', function () { .set(credentials) .send({ message: JSON.stringify({ + id: 'id', + msg: 'method', method: 'loadHistory', params: [rid], }), @@ -410,6 +428,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'loadHistory', params: [rid, postMessageDate], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -434,6 +454,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'loadHistory', params: [rid, { $date: new Date().getTime() }, 1], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -458,6 +480,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'loadHistory', params: [rid, null, 20, lastMessage], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -547,6 +571,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'loadNextMessages', params: [], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -566,6 +592,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'loadNextMessages', params: [], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -590,6 +618,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'loadNextMessages', params: [rid], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -614,6 +644,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'loadNextMessages', params: [rid, postMessageDate], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -638,6 +670,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'loadNextMessages', params: [rid, startDate, 1], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -715,6 +749,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'getUsersOfRoom', params: [], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -734,6 +770,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'getUsersOfRoom', params: [], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -757,6 +795,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'getUsersOfRoom', params: [rid], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -781,6 +821,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'getUserRoles', params: [], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -800,6 +842,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'getUserRoles', params: [], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -823,6 +867,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'listCustomUserStatus', params: [], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -842,6 +888,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'listCustomUserStatus', params: [], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -869,6 +917,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'permissions/get', params: [date], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -888,6 +938,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'permissions/get', params: [], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -911,6 +963,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'permissions/get', params: [date], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1000,6 +1054,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'loadMissedMessages', params: [rid, date], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1019,6 +1075,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'loadMissedMessages', params: ['', date], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1038,6 +1096,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'loadMissedMessages', params: [rid], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1057,6 +1117,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'loadMissedMessages', params: [rid, { $date: new Date().getTime() }], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1080,6 +1142,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'loadMissedMessages', params: [rid, date], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1103,6 +1167,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'loadMissedMessages', params: [rid, postMessageDate], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1131,6 +1197,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'public-settings/get', params: [date], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1150,6 +1218,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'public-settings/get', params: [date], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1177,6 +1247,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'private-settings/get', params: [date], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1200,6 +1272,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'private-settings/get', params: [date], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1225,6 +1299,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'private-settings/get', params: [date], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1254,6 +1330,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'private-settings/get', params: [date], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1284,6 +1362,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'subscriptions/get', params: [date], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1303,6 +1383,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'subscriptions/get', params: [], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1326,6 +1408,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'subscriptions/get', params: [date], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1376,6 +1460,7 @@ describe('Meteor.methods', function () { method: 'sendMessage', params: [{ _id: `${Date.now() + Math.random()}`, rid, msg: 'test message' }], id: 1000, + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1405,6 +1490,8 @@ describe('Meteor.methods', function () { msg: 'test message with https://github.com', }, ], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1463,6 +1550,8 @@ describe('Meteor.methods', function () { msg: 'test message with https://github.com', }, ], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1494,6 +1583,8 @@ describe('Meteor.methods', function () { msg: 'test message with ```https://github.com```', }, ], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1517,6 +1608,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'updateMessage', params: [{ _id: messageId, rid, msg: 'https://github.com updated' }], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1544,6 +1637,8 @@ describe('Meteor.methods', function () { msg: 'test message with ```https://github.com``` updated', }, ], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1629,6 +1724,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'createDirectMessage', params: [testUser.username], + id: 'id', + msg: 'method', }), }) .end((err, res) => { @@ -1649,6 +1746,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'createDirectMessage', params: [testUser2.username], + id: 'id', + msg: 'method', }), }) .end((err, res) => { @@ -1669,6 +1768,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'setUserActiveStatus', params: [testUser._id, false, false], + id: 'id', + msg: 'method', }), }) .end((err, res) => { @@ -1687,6 +1788,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'setUserActiveStatus', params: [testUser2._id, false, false], + id: 'id', + msg: 'method', }), }) .end((err, res) => { @@ -1705,6 +1808,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'getRoomByTypeAndName', params: ['d', dmId], + id: 'id', + msg: 'method', }), }) .end((err, res) => { @@ -1723,6 +1828,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'setUserActiveStatus', params: [testUser._id, true, false], + id: 'id', + msg: 'method', }), }) .end((err, res) => { @@ -1741,6 +1848,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'getRoomByTypeAndName', params: ['d', dmId], + id: 'id', + msg: 'method', }), }) .end((err, res) => { @@ -1772,6 +1881,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'getRoomByTypeAndName', params: ['d', dmTestId], + id: 'id', + msg: 'method', }), }) .end((err, res) => { @@ -1792,6 +1903,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'setUserActiveStatus', params: [testUser2._id, true, false], + id: 'id', + msg: 'method', }), }) .end((err, res) => { @@ -1810,6 +1923,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'getRoomByTypeAndName', params: ['d', dmTestId], + id: 'id', + msg: 'method', }), }) .end((err, res) => { @@ -1828,6 +1943,8 @@ describe('Meteor.methods', function () { message: JSON.stringify({ method: 'getRoomByTypeAndName', params: ['d', dmTestId], + id: 'id', + msg: 'method', }), }) .end((err, res) => { diff --git a/apps/meteor/tests/end-to-end/api/25-teams.js b/apps/meteor/tests/end-to-end/api/25-teams.js index e549e761c6c8..4b45f73ef5da 100644 --- a/apps/meteor/tests/end-to-end/api/25-teams.js +++ b/apps/meteor/tests/end-to-end/api/25-teams.js @@ -1230,6 +1230,8 @@ describe('[Teams]', () => { message: JSON.stringify({ method: 'addUsersToRoom', params: [{ rid: privateRoom3._id, users: [testUser.username] }], + id: 'id', + msg: 'method', }), }) .expect('Content-Type', 'application/json') @@ -1584,6 +1586,8 @@ describe('[Teams]', () => { message: JSON.stringify({ method: 'saveUserPreferences', params: [{ emailNotificationMode: 'nothing' }], + id: 'id', + msg: 'method', }), }) .expect(200); diff --git a/packages/rest-typings/src/index.ts b/packages/rest-typings/src/index.ts index 0a9dbb202fe3..5c2b9fa56e4d 100644 --- a/packages/rest-typings/src/index.ts +++ b/packages/rest-typings/src/index.ts @@ -35,6 +35,7 @@ import type { VoipEndpoints } from './v1/voip'; import type { EmailInboxEndpoints } from './v1/email-inbox'; import type { WebdavEndpoints } from './v1/webdav'; import type { OAuthAppsEndpoint } from './v1/oauthapps'; +import type { SubscriptionsEndpoints } from './v1/subscriptionsEndpoints'; import type { CommandsEndpoints } from './v1/commands'; // eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/interface-name-prefix @@ -73,6 +74,7 @@ export interface Endpoints EmailInboxEndpoints, WebdavEndpoints, OAuthAppsEndpoint, + SubscriptionsEndpoints, AutoTranslateEndpoints {} type OperationsByPathPattern = TPathPattern extends any @@ -157,6 +159,11 @@ export * from './v1/channels/ChannelsModeratorsProps'; export * from './v1/channels/ChannelsConvertToTeamProps'; export * from './v1/channels/ChannelsSetReadOnlyProps'; export * from './v1/channels/ChannelsDeleteProps'; + +export * from './v1/subscriptionsEndpoints'; +export * from './v1/misc'; +export * from './v1/invites'; + export * from './v1/dm'; export * from './v1/dm/DmHistoryProps'; export * from './v1/integrations'; diff --git a/packages/rest-typings/src/v1/invites.ts b/packages/rest-typings/src/v1/invites.ts index fda658af2267..d123c3d18df7 100644 --- a/packages/rest-typings/src/v1/invites.ts +++ b/packages/rest-typings/src/v1/invites.ts @@ -5,11 +5,11 @@ const ajv = new Ajv({ coerceTypes: true, }); -type v1UseInviteTokenProps = { +type UseInviteTokenProps = { token: string; }; -const v1UseInviteTokenPropsSchema = { +const UseInviteTokenPropsSchema = { type: 'object', properties: { token: { @@ -20,13 +20,13 @@ const v1UseInviteTokenPropsSchema = { additionalProperties: false, }; -export const isV1UseInviteTokenProps = ajv.compile(v1UseInviteTokenPropsSchema); +export const isUseInviteTokenProps = ajv.compile(UseInviteTokenPropsSchema); -type v1ValidateInviteTokenProps = { +type ValidateInviteTokenProps = { token: string; }; -const v1ValidateInviteTokenPropsSchema = { +const ValidateInviteTokenPropsSchema = { type: 'object', properties: { token: { @@ -37,17 +37,38 @@ const v1ValidateInviteTokenPropsSchema = { additionalProperties: false, }; -export const isV1ValidateInviteTokenProps = ajv.compile(v1ValidateInviteTokenPropsSchema); +export const isValidateInviteTokenProps = ajv.compile(ValidateInviteTokenPropsSchema); + +type FindOrCreateInviteParams = { rid: IRoom['_id']; days: number; maxUses: number }; + +const FindOrCreateInviteParamsSchema = { + type: 'object', + properties: { + rid: { + type: 'string', + }, + days: { + type: 'integer', + }, + maxUses: { + type: 'integer', + }, + }, + required: ['rid', 'days', 'maxUses'], + additionalProperties: false, +}; + +export const isFindOrCreateInviteParams = ajv.compile(FindOrCreateInviteParamsSchema); export type InvitesEndpoints = { 'listInvites': { GET: () => Array; }; 'removeInvite/:_id': { - DELETE: () => void; + DELETE: () => boolean; }; - '/v1/useInviteToken': { - POST: (params: v1UseInviteTokenProps) => { + 'useInviteToken': { + POST: (params: UseInviteTokenProps) => { room: { rid: IRoom['_id']; prid: IRoom['prid']; @@ -57,7 +78,10 @@ export type InvitesEndpoints = { }; }; }; - '/v1/validateInviteToken': { - POST: (params: v1ValidateInviteTokenProps) => { valid: boolean }; + 'validateInviteToken': { + POST: (params: ValidateInviteTokenProps) => { valid: boolean }; + }; + 'findOrCreateInvite': { + POST: (params: FindOrCreateInviteParams) => IInvite; }; }; diff --git a/packages/rest-typings/src/v1/misc.ts b/packages/rest-typings/src/v1/misc.ts index 06d59cd754b6..f41ffe905860 100644 --- a/packages/rest-typings/src/v1/misc.ts +++ b/packages/rest-typings/src/v1/misc.ts @@ -1,3 +1,175 @@ +import type { IRoom, ITeam, IUser } from '@rocket.chat/core-typings'; +import Ajv from 'ajv'; + +import type { PaginatedRequest } from '../helpers/PaginatedRequest'; +import type { PaginatedResult } from '../helpers/PaginatedResult'; + +const ajv = new Ajv({ + coerceTypes: true, +}); + +type ShieldSvg = { + type?: string; + icon?: 'true' | 'false'; + channel: string; + name: string; +}; + +const ShieldSvgSchema = { + type: 'object', + properties: { + type: { + type: 'string', + nullable: true, + }, + icon: { + type: 'string', + enum: ['true', 'false'], + nullable: true, + }, + channel: { + type: 'string', + }, + name: { + type: 'string', + }, + }, + required: ['name', 'channel'], + additionalProperties: false, +}; + +export const isShieldSvgProps = ajv.compile(ShieldSvgSchema); + +type Spotlight = { query: string; limit: number; offset: number }; + +const SpotlightSchema = { + type: 'object', + properties: { + query: { + type: 'string', + }, + limit: { + type: 'number', + nullable: true, + }, + offset: { + type: 'number', + nullable: true, + }, + }, + required: ['query'], + additionalProperties: false, +}; + +export const isSpotlightProps = ajv.compile(SpotlightSchema); + +type Directory = PaginatedRequest<{ + text: string; + type: string; + workspace: string; +}>; + +const DirectorySchema = { + type: 'object', + properties: { + text: { + type: 'string', + nullable: true, + }, + type: { + type: 'string', + nullable: true, + }, + workspace: { + type: 'string', + nullable: true, + }, + count: { + type: 'number', + nullable: true, + }, + offset: { + type: 'number', + nullable: true, + }, + sort: { + type: 'string', + nullable: true, + }, + query: { + type: 'string', + nullable: true, + }, + }, + required: [], + additionalProperties: false, +}; + +export const isDirectoryProps = ajv.compile(DirectorySchema); + +type MethodCall = { method: string; params: unknown[]; id: string; msg: 'string' }; + +const MethodCallSchema = { + type: 'object', + properties: { + method: { + type: 'string', + }, + params: { + type: 'array', + }, + id: { + type: 'string', + }, + msg: { + type: 'string', + enum: ['method'], + }, + }, + required: ['method', 'params', 'id', 'msg'], + additionalProperties: false, +}; + +export const isMethodCallProps = ajv.compile(MethodCallSchema); + +export const isMeteorCall = ajv.compile<{ + message: string; +}>({ + type: 'object', + properties: { + message: { + type: 'string', + }, + }, + required: ['message'], + additionalProperties: false, +}); + +type MethodCallAnon = { method: string; params: unknown[]; id: string; msg: 'method' }; + +const MethodCallAnonSchema = { + type: 'object', + properties: { + method: { + type: 'string', + }, + params: { + type: 'array', + }, + id: { + type: 'string', + }, + msg: { + type: 'string', + enum: ['method'], + }, + }, + required: ['method', 'params', 'id', 'msg'], + additionalProperties: false, +}; + +export const isMethodCallAnonProps = ajv.compile(MethodCallAnonSchema); + export type MiscEndpoints = { 'stdout.queue': { GET: () => { @@ -8,4 +180,45 @@ export type MiscEndpoints = { }[]; }; }; + 'me': { + GET: (params: { fields: { [k: string]: number }; user: IUser }) => IUser & { + email?: string; + settings: { + profile: {}; + preferences: unknown; + }; + avatarUrl: string; + }; + }; + + 'shield.svg': { + GET: (params: ShieldSvg) => { + svg: string; + }; + }; + + 'spotlight': { + GET: (params: Spotlight) => { + users: Pick[]; + rooms: IRoom[]; + }; + }; + + 'directory': { + GET: (params: Directory) => PaginatedResult<{ + result: (IUser | IRoom | ITeam)[]; + }>; + }; + + 'method.call': { + POST: (params: MethodCall) => { + result: unknown; + }; + }; + + 'method.callAnon': { + POST: (params: MethodCallAnon) => { + result: unknown; + }; + }; }; diff --git a/packages/rest-typings/src/v1/subscriptionsEndpoints.ts b/packages/rest-typings/src/v1/subscriptionsEndpoints.ts new file mode 100644 index 000000000000..d913fb8ebc1d --- /dev/null +++ b/packages/rest-typings/src/v1/subscriptionsEndpoints.ts @@ -0,0 +1,111 @@ +import type { ISubscription, IMessage, IRoom } from '@rocket.chat/core-typings'; +import Ajv from 'ajv'; + +type SubscriptionsGet = { updatedSince?: string }; + +type SubscriptionsGetOne = { roomId: IRoom['_id'] }; + +type SubscriptionsRead = { rid: IRoom['_id'] }; + +type SubscriptionsUnread = { roomId: IRoom['_id'] } | { firstUnreadMessage: Pick }; + +const ajv = new Ajv({ + coerceTypes: true, +}); + +const SubscriptionsGetSchema = { + type: 'object', + properties: { + updatedSince: { + type: 'string', + nullable: true, + }, + }, + required: [], + additionalProperties: false, +}; + +export const isSubscriptionsGetProps = ajv.compile(SubscriptionsGetSchema); + +const SubscriptionsGetOneSchema = { + type: 'object', + properties: { + roomId: { + type: 'string', + }, + }, + required: ['roomId'], + additionalProperties: false, +}; + +export const isSubscriptionsGetOneProps = ajv.compile(SubscriptionsGetOneSchema); + +const SubscriptionsReadSchema = { + type: 'object', + properties: { + rid: { + type: 'string', + }, + }, + required: ['rid'], + additionalProperties: false, +}; + +export const isSubscriptionsReadProps = ajv.compile(SubscriptionsReadSchema); + +const SubscriptionsUnreadSchema = { + anyOf: [ + { + type: 'object', + properties: { + roomId: { + type: 'string', + }, + }, + required: ['roomId'], + additionalProperties: false, + }, + { + type: 'object', + properties: { + firstUnreadMessage: { + type: 'object', + properties: { + _id: { + type: 'string', + }, + }, + required: ['_id'], + additionalProperties: false, + }, + }, + required: ['firstUnreadMessage'], + additionalProperties: false, + }, + ], +}; + +export const isSubscriptionsUnreadProps = ajv.compile(SubscriptionsUnreadSchema); + +export type SubscriptionsEndpoints = { + 'subscriptions.get': { + GET: (params: SubscriptionsGet) => { + update: ISubscription[]; + remove: (Pick & { _deletedAt: Date })[]; + }; + }; + + 'subscriptions.getOne': { + GET: (params: SubscriptionsGetOne) => { + subscription: ISubscription | null; + }; + }; + + 'subscriptions.read': { + POST: (params: SubscriptionsRead) => void; + }; + + 'subscriptions.unread': { + POST: (params: SubscriptionsUnread) => void; + }; +}; From 150c6b4a63a037c615a142c38d906a620af69778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Albuquerque?= Date: Tue, 7 Jun 2022 17:12:47 -0300 Subject: [PATCH 02/59] Chore: API test on method GET with params as a number. (#25769) --- .../tests/end-to-end/api/02-channels.js | 201 +++++++++++++++--- apps/meteor/tests/end-to-end/api/03-groups.js | 181 +++++++++++----- apps/meteor/tests/end-to-end/api/05-chat.js | 92 ++++++++ .../tests/end-to-end/api/08-settings.js | 16 ++ apps/meteor/tests/end-to-end/api/09-rooms.js | 64 ++++++ .../tests/end-to-end/api/12-emoji-custom.js | 17 ++ .../tests/end-to-end/api/16-commands.js | 18 ++ .../tests/end-to-end/api/17-custom-sounds.js | 17 ++ .../end-to-end/api/17-custom-user-status.js | 17 ++ .../tests/end-to-end/api/19-statistics.js | 20 ++ apps/meteor/tests/end-to-end/api/25-teams.js | 44 ++++ .../end-to-end/api/livechat/01-department.js | 23 ++ .../end-to-end/api/livechat/custom-fields.js | 21 ++ .../tests/end-to-end/api/livechat/queue.js | 21 ++ 14 files changed, 671 insertions(+), 81 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/02-channels.js b/apps/meteor/tests/end-to-end/api/02-channels.js index c049602aad99..80e7435c8e8f 100644 --- a/apps/meteor/tests/end-to-end/api/02-channels.js +++ b/apps/meteor/tests/end-to-end/api/02-channels.js @@ -184,6 +184,27 @@ describe('[Channels]', function () { }) .end(done); }); + it('should return all channels messages where the last message of array should have the "star" array with USERS star ONLY even requested with count and offset params', (done) => { + request + .get(api('channels.messages')) + .set(credentials) + .query({ + roomId: testChannel._id, + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('messages').and.to.be.an('array'); + const { messages } = res.body; + const lastMessage = messages.filter((message) => message._id === channelMessage._id)[0]; + expect(lastMessage).to.have.property('starred').and.to.be.an('array'); + expect(lastMessage.starred[0]._id).to.be.equal(adminUsername); + }) + .end(done); + }); }); describe('[/channels.online]', () => { @@ -340,6 +361,24 @@ describe('[Channels]', function () { .end(done); }); + it('should succeed when searching by roomId even requested with count and offset params', (done) => { + request + .get(api('channels.files')) + .set(credentials) + .query({ + roomId: 'GENERAL', + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('files').and.to.be.an('array'); + }) + .end(done); + }); + it('should succeed when searching by roomName', (done) => { request .get(api('channels.files')) @@ -355,6 +394,24 @@ describe('[Channels]', function () { }) .end(done); }); + + it('should succeed when searching by roomName even requested with count and offset params', (done) => { + request + .get(api('channels.files')) + .set(credentials) + .query({ + roomName: 'general', + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('files').and.to.be.an('array'); + }) + .end(done); + }); }); describe('[/channels.join]', () => { @@ -818,20 +875,40 @@ describe('[Channels]', function () { }); }); - it('/channels.history', (done) => { - request - .get(api('channels.history')) - .set(credentials) - .query({ - roomId: channel._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('messages'); - }) - .end(done); + describe('/channels.history', () => { + it('should return an array of members by channel', (done) => { + request + .get(api('channels.history')) + .set(credentials) + .query({ + roomId: channel._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('messages'); + }) + .end(done); + }); + + it('should return an array of members by channel even requested with count and offset params', (done) => { + request + .get(api('channels.history')) + .set(credentials) + .query({ + roomId: channel._id, + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('messages'); + }) + .end(done); + }); }); it('/channels.archive', (done) => { @@ -964,23 +1041,45 @@ describe('[Channels]', function () { }) .end(done); }); - it('/channels.members', (done) => { - request - .get(api('channels.members')) - .set(credentials) - .query({ - roomId: channel._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('members').and.to.be.an('array'); - expect(res.body).to.have.property('count'); - expect(res.body).to.have.property('total'); - expect(res.body).to.have.property('offset'); - }) - .end(done); + describe('/channels.members', () => { + it('should return an array of members by channel', (done) => { + request + .get(api('channels.members')) + .set(credentials) + .query({ + roomId: channel._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('members').and.to.be.an('array'); + expect(res.body).to.have.property('count'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('offset'); + }) + .end(done); + }); + it('should return an array of members by channel even requested with count and offset params', (done) => { + request + .get(api('channels.members')) + .set(credentials) + .query({ + roomId: channel._id, + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('members').and.to.be.an('array'); + expect(res.body).to.have.property('count'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('offset'); + }) + .end(done); + }); }); it('/channels.rename', async () => { @@ -1531,7 +1630,7 @@ describe('[Channels]', function () { }); describe('/channels.getAllUserMentionsByChannel', () => { - it('should return and array of mentions by channel', (done) => { + it('should return an array of mentions by channel', (done) => { request .get(api('channels.getAllUserMentionsByChannel')) .set(credentials) @@ -1549,6 +1648,26 @@ describe('[Channels]', function () { }) .end(done); }); + it('should return an array of mentions by channel even requested with count and offset params', (done) => { + request + .get(api('channels.getAllUserMentionsByChannel')) + .set(credentials) + .query({ + roomId: channel._id, + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('mentions').and.to.be.an('array'); + expect(res.body).to.have.property('count'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('total'); + }) + .end(done); + }); }); describe('/channels.roles', () => { @@ -1714,6 +1833,24 @@ describe('[Channels]', function () { .end(done); }); }); + it('should return the messages list when the setting "Accounts_AllowAnonymousRead" is enabled even requested with count and offset params', (done) => { + updateSetting('Accounts_AllowAnonymousRead', true).then(() => { + request + .get(api('channels.anonymousread')) + .query({ + roomId: 'GENERAL', + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.a.property('success', true); + expect(res.body).to.have.a.property('messages').that.is.an('array'); + }) + .end(done); + }); + }); }); describe('/channels.convertToTeam', () => { diff --git a/apps/meteor/tests/end-to-end/api/03-groups.js b/apps/meteor/tests/end-to-end/api/03-groups.js index 9a7ffc64c3ad..3e5bd508dacb 100644 --- a/apps/meteor/tests/end-to-end/api/03-groups.js +++ b/apps/meteor/tests/end-to-end/api/03-groups.js @@ -227,6 +227,27 @@ describe('[Groups]', function () { }) .end(done); }); + it('should return all groups messages where the last message of array should have the "star" array with USERS star ONLY even requested with count and offset params', (done) => { + request + .get(api('groups.messages')) + .set(credentials) + .query({ + roomId: testGroup._id, + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('messages').and.to.be.an('array'); + const { messages } = res.body; + const lastMessage = messages.filter((message) => message._id === groupMessage._id)[0]; + expect(lastMessage).to.have.property('starred').and.to.be.an('array'); + expect(lastMessage.starred[0]._id).to.be.equal(adminUsername); + }) + .end(done); + }); }); it('/groups.invite', async () => { @@ -503,20 +524,39 @@ describe('[Groups]', function () { }); }); - it('/groups.history', (done) => { - request - .get(api('groups.history')) - .set(credentials) - .query({ - roomId: group._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('messages'); - }) - .end(done); + describe('/groups.history', () => { + it('should return groups history when searching by roomId', (done) => { + request + .get(api('groups.history')) + .set(credentials) + .query({ + roomId: group._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('messages'); + }) + .end(done); + }); + it('should return groups history when searching by roomId even requested with count and offset params', (done) => { + request + .get(api('groups.history')) + .set(credentials) + .query({ + roomId: group._id, + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('messages'); + }) + .end(done); + }); }); it('/groups.archive', (done) => { @@ -705,43 +745,86 @@ describe('[Groups]', function () { }); }); }); - - it('/groups.members', (done) => { - request - .get(api('groups.members')) - .set(credentials) - .query({ - roomId: group._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('count'); - expect(res.body).to.have.property('total'); - expect(res.body).to.have.property('offset'); - expect(res.body).to.have.property('members').and.to.be.an('array'); - }) - .end(done); + describe('/groups.files', () => { + it('should return group members when searching by roomId', (done) => { + request + .get(api('groups.members')) + .set(credentials) + .query({ + roomId: group._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('count'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('members').and.to.be.an('array'); + }) + .end(done); + }); + it('should return group members when searching by roomId even requested with count and offset params', (done) => { + request + .get(api('groups.members')) + .set(credentials) + .query({ + roomId: group._id, + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('count'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('members').and.to.be.an('array'); + }) + .end(done); + }); }); - it('/groups.files', (done) => { - request - .get(api('groups.files')) - .set(credentials) - .query({ - roomId: group._id, - }) - .expect('Content-Type', 'application/json') - .expect(200) - .expect((res) => { - expect(res.body).to.have.property('success', true); - expect(res.body).to.have.property('count'); - expect(res.body).to.have.property('total'); - expect(res.body).to.have.property('offset'); - expect(res.body).to.have.property('files').and.to.be.an('array'); - }) - .end(done); + describe('/groups.files', () => { + it('should return group files when searching by roomId', (done) => { + request + .get(api('groups.files')) + .set(credentials) + .query({ + roomId: group._id, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('count'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('files').and.to.be.an('array'); + }) + .end(done); + }); + it('should return group files when searching by roomId even requested with count and offset params', (done) => { + request + .get(api('groups.files')) + .set(credentials) + .query({ + roomId: group._id, + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('count'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('files').and.to.be.an('array'); + }) + .end(done); + }); }); describe('/groups.listAll', () => { diff --git a/apps/meteor/tests/end-to-end/api/05-chat.js b/apps/meteor/tests/end-to-end/api/05-chat.js index de689e8bfef6..4b7720913ad1 100644 --- a/apps/meteor/tests/end-to-end/api/05-chat.js +++ b/apps/meteor/tests/end-to-end/api/05-chat.js @@ -2153,6 +2153,26 @@ describe('[Chat]', function () { }) .end(done); }); + it('should return the discussions of a room even requested with count and offset params', (done) => { + request + .get(api('chat.getDiscussions')) + .set(credentials) + .query({ + roomId: 'GENERAL', + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.messages).to.be.an('array'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('count'); + }) + .end(done); + }); function filterDiscussionsByText(text) { it(`should return the room's discussion list filtered by the text '${text}'`, (done) => { @@ -2176,6 +2196,30 @@ describe('[Chat]', function () { }) .end(done); }); + + it(`should return the room's discussion list filtered by the text '${text}' even requested with count and offset params`, (done) => { + request + .get(api('chat.getDiscussions')) + .set(credentials) + .query({ + roomId: testChannel._id, + text, + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('messages').and.to.be.an('array'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('count'); + expect(res.body.messages).to.have.lengthOf(1); + expect(res.body.messages[0].drid).to.be.equal(discussionRoom.rid); + }) + .end(done); + }); } messageWords.forEach((text) => { @@ -2318,6 +2362,31 @@ describe('Threads', () => { }); }); + it("should return the room's thread list even requested with count and offset params", (done) => { + updatePermission('view-c-room', ['admin', 'user']).then(() => { + request + .get(api('chat.getThreadsList')) + .set(credentials) + .query({ + rid: testChannel._id, + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('threads').and.to.be.an('array'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('count'); + expect(res.body.threads).to.have.lengthOf(1); + expect(res.body.threads[0]._id).to.be.equal(threadMessage.tmid); + }) + .end(done); + }); + }); + function filterThreadsByText(text) { it(`should return the room's thread list filtered by the text '${text}'`, (done) => { request @@ -2340,6 +2409,29 @@ describe('Threads', () => { }) .end(done); }); + it(`should return the room's thread list filtered by the text '${text}' even requested with count and offset params`, (done) => { + request + .get(api('chat.getThreadsList')) + .set(credentials) + .query({ + rid: testChannel._id, + text, + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('threads').and.to.be.an('array'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('count'); + expect(res.body.threads).to.have.lengthOf(1); + expect(res.body.threads[0]._id).to.be.equal(threadMessage.tmid); + }) + .end(done); + }); } messageWords.forEach((text) => { diff --git a/apps/meteor/tests/end-to-end/api/08-settings.js b/apps/meteor/tests/end-to-end/api/08-settings.js index 61e48cd8e456..3753e9e4394e 100644 --- a/apps/meteor/tests/end-to-end/api/08-settings.js +++ b/apps/meteor/tests/end-to-end/api/08-settings.js @@ -20,6 +20,22 @@ describe('[Settings]', function () { }) .end(done); }); + it('should return public settings even requested with count and offset params', (done) => { + request + .get(api('settings.public')) + .query({ + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('settings'); + expect(res.body).to.have.property('count'); + }) + .end(done); + }); }); describe('[/settings]', () => { 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 0bc97d9bbf65..ec168a7104ce 100644 --- a/apps/meteor/tests/end-to-end/api/09-rooms.js +++ b/apps/meteor/tests/end-to-end/api/09-rooms.js @@ -954,6 +954,52 @@ describe('[Rooms]', function () { .end(done); }); }); + describe('[/rooms.autocomplete.channelAndPrivate.withPagination]', () => { + it('should return an error when the required parameter "selector" is not provided', (done) => { + request + .get(api('rooms.autocomplete.channelAndPrivate.withPagination')) + .set(credentials) + .query({}) + .expect('Content-Type', 'application/json') + .expect(400) + .expect((res) => { + expect(res.body).to.have.property('success', false); + expect(res.body.error).to.be.equal("The 'selector' param is required"); + }) + .end(done); + }); + it('should return the rooms to fill auto complete', (done) => { + request + .get(api('rooms.autocomplete.channelAndPrivate.withPagination?selector={}')) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('items').and.to.be.an('array'); + expect(res.body).to.have.property('total'); + }) + .end(done); + }); + it('should return the rooms to fill auto complete even requested with count and offset params', (done) => { + request + .get(api('rooms.autocomplete.channelAndPrivate.withPagination?selector={}')) + .set(credentials) + .query({ + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('items').and.to.be.an('array'); + expect(res.body).to.have.property('total'); + }) + .end(done); + }); + }); + describe('[/rooms.autocomplete.availableForTeams]', () => { it('should return the rooms to fill auto complete', (done) => { request @@ -1071,6 +1117,24 @@ describe('[Rooms]', function () { }) .end(done); }); + it('should return a list of admin rooms even requested with count and offset params', (done) => { + request + .get(api('rooms.adminRooms')) + .set(credentials) + .query({ + count: 5, + offset: 0, + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('rooms').and.to.be.an('array'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('count'); + }) + .end(done); + }); }); describe('update group dms name', () => { diff --git a/apps/meteor/tests/end-to-end/api/12-emoji-custom.js b/apps/meteor/tests/end-to-end/api/12-emoji-custom.js index f4fc4df56470..a9a088985ae7 100644 --- a/apps/meteor/tests/end-to-end/api/12-emoji-custom.js +++ b/apps/meteor/tests/end-to-end/api/12-emoji-custom.js @@ -263,6 +263,23 @@ describe('[EmojiCustom]', function () { }) .end(done); }); + it('should return emojis even requested with count and offset params', (done) => { + request + .get(api('emoji-custom.all')) + .set(credentials) + .query({ + count: 5, + offset: 0, + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('emojis').and.to.be.an('array'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('count'); + }) + .end(done); + }); }); describe('[/emoji-custom.delete]', () => { diff --git a/apps/meteor/tests/end-to-end/api/16-commands.js b/apps/meteor/tests/end-to-end/api/16-commands.js index 1671abda739f..70c0e90f0dae 100644 --- a/apps/meteor/tests/end-to-end/api/16-commands.js +++ b/apps/meteor/tests/end-to-end/api/16-commands.js @@ -70,6 +70,24 @@ describe('[Commands]', function () { }) .end(done); }); + it('should return a list of commands even requested with count and offset params', (done) => { + request + .get(api('commands.list')) + .set(credentials) + .query({ + count: 5, + offset: 0, + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('count'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('commands').and.to.be.an('array'); + }) + .end(done); + }); }); describe('[/commands.run]', () => { diff --git a/apps/meteor/tests/end-to-end/api/17-custom-sounds.js b/apps/meteor/tests/end-to-end/api/17-custom-sounds.js index 34eca545f3f8..b0b2ba7e1029 100644 --- a/apps/meteor/tests/end-to-end/api/17-custom-sounds.js +++ b/apps/meteor/tests/end-to-end/api/17-custom-sounds.js @@ -21,5 +21,22 @@ describe('[CustomSounds]', function () { }) .end(done); }); + it('should return custom sounds even requested with count and offset params', (done) => { + request + .get(api('custom-sounds.list')) + .set(credentials) + .expect(200) + .query({ + count: 5, + offset: 0, + }) + .expect((res) => { + expect(res.body).to.have.property('sounds').and.to.be.an('array'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('count'); + }) + .end(done); + }); }); }); diff --git a/apps/meteor/tests/end-to-end/api/17-custom-user-status.js b/apps/meteor/tests/end-to-end/api/17-custom-user-status.js index 30734c90a526..d5d8951f2050 100644 --- a/apps/meteor/tests/end-to-end/api/17-custom-user-status.js +++ b/apps/meteor/tests/end-to-end/api/17-custom-user-status.js @@ -21,5 +21,22 @@ describe('[CustomUserStatus]', function () { }) .end(done); }); + it('should return custom user status even requested with count and offset params', (done) => { + request + .get(api('custom-user-status.list')) + .set(credentials) + .expect(200) + .query({ + count: 5, + offset: 0, + }) + .expect((res) => { + expect(res.body).to.have.property('statuses').and.to.be.an('array'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('count'); + }) + .end(done); + }); }); }); diff --git a/apps/meteor/tests/end-to-end/api/19-statistics.js b/apps/meteor/tests/end-to-end/api/19-statistics.js index 82ba16dc44a6..9f59936c807a 100644 --- a/apps/meteor/tests/end-to-end/api/19-statistics.js +++ b/apps/meteor/tests/end-to-end/api/19-statistics.js @@ -83,5 +83,25 @@ describe('[Statistics]', function () { .end(done); }); }); + it('should return an array with the statistics even requested with count and offset params', (done) => { + updatePermission('view-statistics', ['admin']).then(() => { + request + .get(api('statistics.list')) + .set(credentials) + .query({ + count: 5, + offset: 0, + }) + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('statistics').and.to.be.an('array'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('count'); + }) + .end(done); + }); + }); }); }); diff --git a/apps/meteor/tests/end-to-end/api/25-teams.js b/apps/meteor/tests/end-to-end/api/25-teams.js index 4b45f73ef5da..41722083e608 100644 --- a/apps/meteor/tests/end-to-end/api/25-teams.js +++ b/apps/meteor/tests/end-to-end/api/25-teams.js @@ -1350,6 +1350,27 @@ describe('[Teams]', () => { .end(done); }); }); + it('should return all rooms for public team even requested with count and offset params', (done) => { + updatePermission('view-all-team-channels', ['user']).then(() => { + request + .get(api('teams.listRooms')) + .set(testUserCredentials) + .query({ + teamId: publicTeam._id, + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('rooms'); + expect(res.body.rooms).to.be.an('array'); + expect(res.body.rooms.length).to.equal(2); + }) + .end(done); + }); + }); it('should return public rooms for private team', (done) => { updatePermission('view-all-team-channels', []).then(() => { @@ -1372,6 +1393,29 @@ describe('[Teams]', () => { }); }); }); + it('should return public rooms for private team even requested with count and offset params', (done) => { + updatePermission('view-all-team-channels', []).then(() => { + updatePermission('view-all-teams', ['admin']).then(() => { + request + .get(api('teams.listRooms')) + .set(credentials) + .query({ + teamId: privateTeam._id, + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('rooms'); + expect(res.body.rooms).to.be.an('array'); + expect(res.body.rooms.length).to.equal(2); + }) + .end(done); + }); + }); + }); }); describe('/teams.updateRoom', () => { diff --git a/apps/meteor/tests/end-to-end/api/livechat/01-department.js b/apps/meteor/tests/end-to-end/api/livechat/01-department.js index a38baa5c716f..84822885ec63 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/01-department.js +++ b/apps/meteor/tests/end-to-end/api/livechat/01-department.js @@ -56,6 +56,29 @@ describe('LIVECHAT - departments', function () { .end(done); }); }); + it('should return an array of departments even requested with count and offset params', (done) => { + updatePermission('view-l-room', ['admin']) + .then(() => updatePermission('view-livechat-departments', ['admin'])) + .then(() => { + request + .get(api('livechat/department')) + .set(credentials) + .query({ + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.departments).to.be.an('array'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('count'); + }) + .end(done); + }); + }); }); describe('livechat/department/id', () => { diff --git a/apps/meteor/tests/end-to-end/api/livechat/custom-fields.js b/apps/meteor/tests/end-to-end/api/livechat/custom-fields.js index 50adcff68712..a9ef318921d2 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/custom-fields.js +++ b/apps/meteor/tests/end-to-end/api/livechat/custom-fields.js @@ -44,6 +44,27 @@ describe('LIVECHAT - custom fields', function () { .end(done); }); }); + it('should return an array of custom fields even requested with count and offset params', (done) => { + updatePermission('view-l-room', ['admin']).then(() => { + request + .get(api('livechat/custom-fields')) + .set(credentials) + .query({ + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.customFields).to.be.an('array'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('count'); + }) + .end(done); + }); + }); }); describe('livechat/custom-fields/id', () => { diff --git a/apps/meteor/tests/end-to-end/api/livechat/queue.js b/apps/meteor/tests/end-to-end/api/livechat/queue.js index c373af585c8a..1340517a0db7 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/queue.js +++ b/apps/meteor/tests/end-to-end/api/livechat/queue.js @@ -44,5 +44,26 @@ describe('LIVECHAT - Queue', function () { .end(done); }); }); + it('should return an array of queued metrics even requested with count and offset params', (done) => { + updatePermission('view-l-room', ['admin']).then(() => { + request + .get(api('livechat/queue')) + .set(credentials) + .query({ + count: 5, + offset: 0, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body.queue).to.be.an('array'); + expect(res.body).to.have.property('offset'); + expect(res.body).to.have.property('total'); + expect(res.body).to.have.property('count'); + }) + .end(done); + }); + }); }); }); From 707a62263e378dd93fa7060dee3bb7b66035d99f Mon Sep 17 00:00:00 2001 From: Jean Brito Date: Tue, 7 Jun 2022 17:14:43 -0300 Subject: [PATCH 03/59] chore: Convert to TS RoomAutoComplete (#25536) Co-authored-by: Guilherme Gazzo --- apps/meteor/client/components/RoomAutoComplete/Avatar.tsx | 8 ++++---- .../components/RoomAutoComplete/RoomAutoComplete.tsx | 2 +- apps/meteor/client/components/avatar/RoomAvatar.tsx | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/meteor/client/components/RoomAutoComplete/Avatar.tsx b/apps/meteor/client/components/RoomAutoComplete/Avatar.tsx index d90fa71772b4..49041e23d821 100644 --- a/apps/meteor/client/components/RoomAutoComplete/Avatar.tsx +++ b/apps/meteor/client/components/RoomAutoComplete/Avatar.tsx @@ -1,16 +1,16 @@ import { Options } from '@rocket.chat/fuselage'; -import React, { ReactElement } from 'react'; +import React, { FC } from 'react'; import RoomAvatar from '../avatar/RoomAvatar'; type AvatarProps = { value: string; type: string; - avatarETag?: string | undefined; + avatarETag?: string; }; -const Avatar = ({ value, type, avatarETag, ...props }: AvatarProps): ReactElement => ( - +const Avatar: FC = ({ value, type, avatarETag, ...props }) => ( + ); export default Avatar; diff --git a/apps/meteor/client/components/RoomAutoComplete/RoomAutoComplete.tsx b/apps/meteor/client/components/RoomAutoComplete/RoomAutoComplete.tsx index 731d3a724cb5..4cf8077e9853 100644 --- a/apps/meteor/client/components/RoomAutoComplete/RoomAutoComplete.tsx +++ b/apps/meteor/client/components/RoomAutoComplete/RoomAutoComplete.tsx @@ -1,5 +1,5 @@ import { AutoComplete, Option, Box } from '@rocket.chat/fuselage'; -import React, { ComponentProps, memo, ReactElement, useMemo, useState } from 'react'; +import React, { memo, useMemo, useState, ReactElement, ComponentProps } from 'react'; import { useEndpointData } from '../../hooks/useEndpointData'; import RoomAvatar from '../avatar/RoomAvatar'; diff --git a/apps/meteor/client/components/avatar/RoomAvatar.tsx b/apps/meteor/client/components/avatar/RoomAvatar.tsx index 0c4a1105a721..750776a00b5a 100644 --- a/apps/meteor/client/components/avatar/RoomAvatar.tsx +++ b/apps/meteor/client/components/avatar/RoomAvatar.tsx @@ -14,7 +14,7 @@ type RoomAvatarProps = { room: { _id: string; type?: string; - t: string; + t?: string; avatarETag?: string; }; }; From b89ebfff4f66d6ac76832346a05e197b22176dee Mon Sep 17 00:00:00 2001 From: Yash Rajpal <58601732+yash-rajpal@users.noreply.github.com> Date: Wed, 8 Jun 2022 02:11:38 +0530 Subject: [PATCH 04/59] [FIX] Sanitize styles in message (#25744) --- apps/meteor/app/markdown/lib/parser/marked/marked.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/app/markdown/lib/parser/marked/marked.js b/apps/meteor/app/markdown/lib/parser/marked/marked.js index 7739fe77b7da..22f7974fcace 100644 --- a/apps/meteor/app/markdown/lib/parser/marked/marked.js +++ b/apps/meteor/app/markdown/lib/parser/marked/marked.js @@ -100,7 +100,7 @@ export const marked = (message, { marked: { gfm, tables, breaks, pedantic, smart const window = getGlobalWindow(); const DomPurify = createDOMPurify(window); - message.html = DomPurify.sanitize(message.html, { ADD_ATTR: ['target'] }); + message.html = DomPurify.sanitize(message.html, { ADD_ATTR: ['target'], FORBID_ATTR: ['style'], FORBID_TAGS: ['style'] }); return message; }; From 6c3b3b4a2c75d9b135ebbb998f48777c88fd82bf Mon Sep 17 00:00:00 2001 From: Tiago Evangelista Pinto Date: Tue, 7 Jun 2022 18:04:44 -0300 Subject: [PATCH 05/59] [FIX] Broken Omnichannel>Agents page (#25731) --- .../views/omnichannel/agents/AddAgent.tsx | 7 +- .../omnichannel/agents/AgentEditWithData.tsx | 6 +- .../omnichannel/agents/AgentInfoActions.tsx | 10 +- .../views/omnichannel/agents/AgentsPage.tsx | 134 +++++++++--- .../omnichannel/agents/AgentsPageRow.tsx | 62 ++++++ .../views/omnichannel/agents/AgentsRoute.tsx | 204 ------------------ .../views/omnichannel/agents/AgentsTab.tsx | 35 +++ .../omnichannel/agents/RemoveAgentButton.tsx | 11 +- .../omnichannel/agents/hooks/useQuery.ts | 30 +++ .../currentChats/CurrentChatsPage.tsx | 21 +- .../currentChats/CurrentChatsRoute.tsx | 9 +- .../meteor/client/views/omnichannel/routes.ts | 2 +- 12 files changed, 265 insertions(+), 266 deletions(-) create mode 100644 apps/meteor/client/views/omnichannel/agents/AgentsPageRow.tsx delete mode 100644 apps/meteor/client/views/omnichannel/agents/AgentsRoute.tsx create mode 100644 apps/meteor/client/views/omnichannel/agents/AgentsTab.tsx create mode 100644 apps/meteor/client/views/omnichannel/agents/hooks/useQuery.ts diff --git a/apps/meteor/client/views/omnichannel/agents/AddAgent.tsx b/apps/meteor/client/views/omnichannel/agents/AddAgent.tsx index e0bbee433a4e..dc1e7a9166d9 100644 --- a/apps/meteor/client/views/omnichannel/agents/AddAgent.tsx +++ b/apps/meteor/client/views/omnichannel/agents/AddAgent.tsx @@ -1,17 +1,16 @@ import { Button, Box, Field } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useTranslation } from '@rocket.chat/ui-contexts'; -import React, { useState, FC } from 'react'; +import React, { useState, ReactElement } from 'react'; import UserAutoComplete from '../../../components/UserAutoComplete'; import { useEndpointAction } from '../../../hooks/useEndpointAction'; type AddAgentProps = { reload: () => void; - pi?: 'x24'; }; -const AddAgent: FC = ({ reload, ...props }) => { +const AddAgent = ({ reload }: AddAgentProps): ReactElement => { const t = useTranslation(); const [username, setUsername] = useState(''); @@ -29,7 +28,7 @@ const AddAgent: FC = ({ reload, ...props }) => { setUsername(username); }); return ( - + {t('Username')} diff --git a/apps/meteor/client/views/omnichannel/agents/AgentEditWithData.tsx b/apps/meteor/client/views/omnichannel/agents/AgentEditWithData.tsx index 04357e546900..3ff846618622 100644 --- a/apps/meteor/client/views/omnichannel/agents/AgentEditWithData.tsx +++ b/apps/meteor/client/views/omnichannel/agents/AgentEditWithData.tsx @@ -1,6 +1,6 @@ import { Box } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; -import React, { FC } from 'react'; +import React, { ReactElement } from 'react'; import { FormSkeleton } from '../../../components/Skeleton'; import { AsyncStatePhase } from '../../../hooks/useAsyncState'; @@ -12,7 +12,7 @@ type AgentEditWithDataProps = { reload: () => void; }; -const AgentEditWithData: FC = ({ uid, reload }) => { +const AgentEditWithData = ({ uid, reload }: AgentEditWithDataProps): ReactElement => { const t = useTranslation(); const { value: data, phase: state, error } = useEndpointData(`livechat/users/agent/${uid}`); const { @@ -35,7 +35,7 @@ const AgentEditWithData: FC = ({ uid, reload }) => { } if (error || userDepartmentsError || availableDepartmentsError || !data || !data.user) { - return {t('User_not_found')}; + return {t('User_not_found')}; } return ; diff --git a/apps/meteor/client/views/omnichannel/agents/AgentInfoActions.tsx b/apps/meteor/client/views/omnichannel/agents/AgentInfoActions.tsx index ddd50628da4d..089bb43078ff 100644 --- a/apps/meteor/client/views/omnichannel/agents/AgentInfoActions.tsx +++ b/apps/meteor/client/views/omnichannel/agents/AgentInfoActions.tsx @@ -1,14 +1,12 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useSetModal, useToastMessageDispatch, useRouteParameter, useRoute, useTranslation } from '@rocket.chat/ui-contexts'; -import React, { VFC } from 'react'; +import React, { ReactElement } from 'react'; import GenericModal from '../../../components/GenericModal'; import { useEndpointAction } from '../../../hooks/useEndpointAction'; import AgentInfo from './AgentInfo'; -const AgentInfoActions: VFC<{ - reload: () => void; -}> = ({ reload }) => { +const AgentInfoActions = ({ reload }: { reload: () => void }): ReactElement => { const t = useTranslation(); const _id = useRouteParameter('id'); const agentsRoute = useRoute('omnichannel-agents'); @@ -48,8 +46,8 @@ const AgentInfoActions: VFC<{ return ( <> - , - , + + ); }; diff --git a/apps/meteor/client/views/omnichannel/agents/AgentsPage.tsx b/apps/meteor/client/views/omnichannel/agents/AgentsPage.tsx index 84293e15582f..94045e8e982f 100644 --- a/apps/meteor/client/views/omnichannel/agents/AgentsPage.tsx +++ b/apps/meteor/client/views/omnichannel/agents/AgentsPage.tsx @@ -1,39 +1,113 @@ -import React, { FC, Key, ReactNode, ReactElement } from 'react'; +import { Box, Pagination } from '@rocket.chat/fuselage'; +import { useDebouncedValue, useMediaQuery, useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { usePermission, useRouteParameter, useTranslation } from '@rocket.chat/ui-contexts'; +import React, { ReactElement, useMemo, useState } from 'react'; import FilterByText from '../../../components/FilterByText'; -import GenericTable from '../../../components/GenericTable'; +import { GenericTableBody, GenericTableHeader, GenericTableHeaderCell, GenericTableLoadingTable } from '../../../components/GenericTable'; +import { GenericTable } from '../../../components/GenericTable/V2/GenericTable'; +import { usePagination } from '../../../components/GenericTable/hooks/usePagination'; +import { useSort } from '../../../components/GenericTable/hooks/useSort'; import Page from '../../../components/Page'; +import { useEndpointData } from '../../../hooks/useEndpointData'; +import { AsyncStatePhase } from '../../../lib/asyncState'; +import NotAuthorizedPage from '../../notAuthorized/NotAuthorizedPage'; import AddAgent from './AddAgent'; +import AgentsPageRow from './AgentsPageRow'; +import AgentsTab from './AgentsTab'; +import { useQuery } from './hooks/useQuery'; -type AgentPageProps = { - reload: () => void; - data: any; - header: ReactNode; - setParams: (params: any) => void; - params: any; - title: string; - renderRow: (props: { _id?: Key }) => ReactElement; -}; +const AgentsPage = (): ReactElement => { + const t = useTranslation(); + const canViewAgents = usePermission('manage-livechat-agents'); + const mediaQuery = useMediaQuery('(min-width: 1024px)'); + + const context = useRouteParameter('context'); + const id = useRouteParameter('id'); + + const { sortBy, sortDirection, setSort } = useSort<'name' | 'username' | 'emails.address' | 'statusLivechat'>('name'); + const [filter, setFilter] = useState(''); + const debouncedFilter = useDebouncedValue(filter, 500); + const debouncedSort = useDebouncedValue( + useMemo(() => [sortBy, sortDirection], [sortBy, sortDirection]), + 500, + ) as ['name' | 'username' | 'emails.address' | 'statusLivechat', 'asc' | 'desc']; + + const { current, itemsPerPage, setItemsPerPage, setCurrent, ...paginationProps } = usePagination(); -const AgentsPage: FC = ({ data, reload, header, setParams, params, title, renderRow, children }) => ( - - - - - - } - /> - + const query = useQuery({ text: debouncedFilter, current, itemsPerPage }, debouncedSort); + const { reload, ...result } = useEndpointData('livechat/users/agent', query); + + const onHeaderClick = useMutableCallback((id) => { + if (sortBy === id) { + setSort(id, sortDirection === 'asc' ? 'desc' : 'asc'); + return; + } + setSort(id, 'asc'); + }); + + if (!canViewAgents) { + return ; + } + + return ( + + + + + + setFilter(text)} /> + + + + + + {t('Name')} + + {mediaQuery && ( + + {t('Username')} + + )} + + {t('Email')} + + + {t('Livechat_status')} + + {t('Remove')} + + + {result.phase === AsyncStatePhase.LOADING && } + {result.phase === AsyncStatePhase.RESOLVED && + result.value.users.map((user) => )} + + + {result.phase === AsyncStatePhase.RESOLVED && ( + + )} + + + {context && id && } - {children} - -); + ); +}; export default AgentsPage; diff --git a/apps/meteor/client/views/omnichannel/agents/AgentsPageRow.tsx b/apps/meteor/client/views/omnichannel/agents/AgentsPageRow.tsx new file mode 100644 index 000000000000..b67fa10033c6 --- /dev/null +++ b/apps/meteor/client/views/omnichannel/agents/AgentsPageRow.tsx @@ -0,0 +1,62 @@ +import { Box } from '@rocket.chat/fuselage'; +import { useRoute, useTranslation } from '@rocket.chat/ui-contexts'; +import React, { ReactElement, useCallback } from 'react'; + +import { GenericTableRow, GenericTableCell } from '../../../components/GenericTable'; +import UserAvatar from '../../../components/avatar/UserAvatar'; +import RemoveAgentButton from './RemoveAgentButton'; + +const AgentsPageRow = ({ + user: { _id, name, username, avatarETag, emails, statusLivechat }, + mediaQuery, + reload, +}: { + user: { _id: string; name?: string; username?: string; avatarETag?: string; emails?: { address: string }[]; statusLivechat: string }; + mediaQuery: boolean; + reload: () => void; +}): ReactElement => { + const t = useTranslation(); + const agentsRoute = useRoute('omnichannel-agents'); + + const onRowClick = useCallback(() => { + agentsRoute.push({ + context: 'info', + id: _id, + }); + }, [_id, agentsRoute]); + + return ( + + + + {username && } + + + + {name || username} + + {!mediaQuery && name && ( + + {`@${username}`} + + )} + + + + + {mediaQuery && ( + + + {username} + + + + )} + {emails?.length && emails[0].address} + {statusLivechat === 'available' ? t('Available') : t('Not_Available')} + + + ); +}; + +export default AgentsPageRow; diff --git a/apps/meteor/client/views/omnichannel/agents/AgentsRoute.tsx b/apps/meteor/client/views/omnichannel/agents/AgentsRoute.tsx deleted file mode 100644 index c47c96f596a5..000000000000 --- a/apps/meteor/client/views/omnichannel/agents/AgentsRoute.tsx +++ /dev/null @@ -1,204 +0,0 @@ -import { Box, Table } from '@rocket.chat/fuselage'; -import { useDebouncedValue, useMediaQuery, useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { PaginatedRequest } from '@rocket.chat/rest-typings'; -import { useRouteParameter, useRoute, usePermission, useTranslation } from '@rocket.chat/ui-contexts'; -import React, { useMemo, useCallback, useState, FC, ReactElement } from 'react'; - -import GenericTable from '../../../components/GenericTable'; -import { useSort } from '../../../components/GenericTable/hooks/useSort'; -import VerticalBar from '../../../components/VerticalBar'; -import UserAvatar from '../../../components/avatar/UserAvatar'; -import { useEndpointData } from '../../../hooks/useEndpointData'; -import NotAuthorizedPage from '../../notAuthorized/NotAuthorizedPage'; -import AgentEditWithData from './AgentEditWithData'; -import AgentInfo from './AgentInfo'; -import AgentInfoActions from './AgentInfoActions'; -import AgentsPage from './AgentsPage'; -import RemoveAgentButton from './RemoveAgentButton'; - -const sortDir = (sortDir: 'asc' | 'desc'): 1 | -1 => (sortDir === 'asc' ? 1 : -1); - -const useQuery = ( - { - text, - itemsPerPage, - current, - }: { - text: string; - itemsPerPage: number; - current: number; - }, - [column, direction]: [string, 'asc' | 'desc'], -): PaginatedRequest<{ text: string }> => - useMemo( - () => ({ - fields: JSON.stringify({ name: 1, username: 1, emails: 1, avatarETag: 1 }), - text, - sort: JSON.stringify({ - [column]: sortDir(direction), - usernames: column === 'name' ? sortDir(direction) : undefined, - }), - ...(itemsPerPage && { count: itemsPerPage }), - ...(current && { offset: current }), - }), - [text, itemsPerPage, current, column, direction], - ); - -const AgentsRoute: FC = () => { - const t = useTranslation(); - const canViewAgents = usePermission('manage-livechat-agents'); - - const [params, setParams] = useState({ text: '', current: 0, itemsPerPage: 25 }); - const { sortBy, sortDirection, setSort } = useSort<'name' | 'username' | 'emails.address' | 'statusLivechat'>('name'); - - const mediaQuery = useMediaQuery('(min-width: 1024px)'); - - const debouncedParams = useDebouncedValue(params, 500); - const debouncedSort = useDebouncedValue([sortBy, sortDirection], 500) as [ - 'name' | 'username' | 'emails.address' | 'statusLivechat', - 'asc' | 'desc', - ]; - const query = useQuery(debouncedParams, debouncedSort); - const agentsRoute = useRoute('omnichannel-agents'); - const context = useRouteParameter('context'); - const id = useRouteParameter('id'); - - if (!id) { - throw new Error('Agent id is required'); - } - - const onHeaderClick = useMutableCallback((id) => { - if (sortBy === id) { - setSort(id, sortDirection === 'asc' ? 'desc' : 'asc'); - return; - } - setSort(id, 'asc'); - }); - - const onRowClick = useMutableCallback( - (id) => (): void => - agentsRoute.push({ - context: 'info', - id, - }), - ); - - const { value: data, reload } = useEndpointData('livechat/users/agent', query); - - const header = useMemo( - () => - [ - - {t('Name')} - , - mediaQuery && ( - - {t('Username')} - - ), - - {t('Email')} - , - - {t('Livechat_status')} - , - - {t('Remove')} - , - ].filter(Boolean), - [sortDirection, sortBy, onHeaderClick, t, mediaQuery], - ) as ReactElement[]; - - const renderRow = useCallback( - ({ emails, _id, username, name, avatarETag, statusLivechat }) => ( - - - - - - - - {name || username} - - {!mediaQuery && name && ( - - {' '} - {`@${username}`}{' '} - - )} - - - - - {mediaQuery && ( - - - {username} - {' '} - - - )} - {emails?.length && emails[0].address} - {statusLivechat === 'available' ? t('Available') : t('Not_Available')} - - - ), - [mediaQuery, reload, onRowClick, t], - ); - - const EditAgentsTab = useCallback((): ReactElement => { - if (!context) { - return <>; - } - const handleVerticalBarCloseButtonClick = (): void => { - agentsRoute.push({}); - }; - - return ( - - - {context === 'edit' && t('Edit_User')} - {context === 'info' && t('User_Info')} - - - - {context === 'edit' && } - {context === 'info' && ( - - - - )} - - ); - }, [t, context, id, agentsRoute, reload]); - - if (!canViewAgents) { - return ; - } - - return ( - - - - ); -}; - -export default AgentsRoute; diff --git a/apps/meteor/client/views/omnichannel/agents/AgentsTab.tsx b/apps/meteor/client/views/omnichannel/agents/AgentsTab.tsx new file mode 100644 index 000000000000..7662c1f6840c --- /dev/null +++ b/apps/meteor/client/views/omnichannel/agents/AgentsTab.tsx @@ -0,0 +1,35 @@ +import { useRoute, useTranslation } from '@rocket.chat/ui-contexts'; +import React, { ReactElement, useCallback } from 'react'; + +import VerticalBar from '../../../components/VerticalBar'; +import AgentEditWithData from './AgentEditWithData'; +import AgentInfo from './AgentInfo'; +import AgentInfoActions from './AgentInfoActions'; + +const AgentsTab = ({ reload, context, id }: { reload: () => void; context: string; id: string }): ReactElement => { + const t = useTranslation(); + const agentsRoute = useRoute('omnichannel-agents'); + + const handleVerticalBarCloseButtonClick = useCallback((): void => { + agentsRoute.push({}); + }, [agentsRoute]); + + return ( + + + {context === 'edit' && t('Edit_User')} + {context === 'info' && t('User_Info')} + + + + {context === 'edit' && } + {context === 'info' && ( + + + + )} + + ); +}; + +export default AgentsTab; diff --git a/apps/meteor/client/views/omnichannel/agents/RemoveAgentButton.tsx b/apps/meteor/client/views/omnichannel/agents/RemoveAgentButton.tsx index b37303a3ca59..a5f48da05a38 100644 --- a/apps/meteor/client/views/omnichannel/agents/RemoveAgentButton.tsx +++ b/apps/meteor/client/views/omnichannel/agents/RemoveAgentButton.tsx @@ -1,9 +1,10 @@ -import { Table, Icon, Button } from '@rocket.chat/fuselage'; +import { Icon, Button } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useSetModal, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; -import React, { FC } from 'react'; +import React, { ReactElement } from 'react'; import GenericModal from '../../../components/GenericModal'; +import { GenericTableCell } from '../../../components/GenericTable'; import { useEndpointAction } from '../../../hooks/useEndpointAction'; type RemoveAgentButtonProps = { @@ -11,7 +12,7 @@ type RemoveAgentButtonProps = { reload: () => void; }; -const RemoveAgentButton: FC = ({ _id, reload }) => { +const RemoveAgentButton = ({ _id, reload }: RemoveAgentButtonProps): ReactElement => { const deleteAction = useEndpointAction('DELETE', `livechat/users/agent/${_id}`); const setModal = useSetModal(); const dispatchToastMessage = useToastMessageDispatch(); @@ -40,11 +41,11 @@ const RemoveAgentButton: FC = ({ _id, reload }) => { }); return ( - + - + ); }; diff --git a/apps/meteor/client/views/omnichannel/agents/hooks/useQuery.ts b/apps/meteor/client/views/omnichannel/agents/hooks/useQuery.ts new file mode 100644 index 000000000000..fa98e888ca86 --- /dev/null +++ b/apps/meteor/client/views/omnichannel/agents/hooks/useQuery.ts @@ -0,0 +1,30 @@ +import { PaginatedRequest } from '@rocket.chat/rest-typings'; +import { useMemo } from 'react'; + +const sortDir = (sortDir: 'asc' | 'desc'): 1 | -1 => (sortDir === 'asc' ? 1 : -1); + +export const useQuery = ( + { + text, + itemsPerPage, + current, + }: { + text: string; + itemsPerPage: number; + current: number; + }, + [column, direction]: [string, 'asc' | 'desc'], +): PaginatedRequest<{ text: string }> => + useMemo( + () => ({ + fields: JSON.stringify({ name: 1, username: 1, emails: 1, avatarETag: 1 }), + text, + sort: JSON.stringify({ + [column]: sortDir(direction), + usernames: column === 'name' ? sortDir(direction) : undefined, + }), + ...(itemsPerPage && { count: itemsPerPage }), + ...(current && { offset: current }), + }), + [text, itemsPerPage, current, column, direction], + ); diff --git a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx index b5a20852c467..c970bb2a4f15 100644 --- a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx +++ b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsPage.tsx @@ -1,6 +1,6 @@ import type { IOmnichannelRoom } from '@rocket.chat/core-typings'; import { Serialized } from '@rocket.chat/core-typings'; -import React, { Dispatch, FC, Key, memo, ReactElement, ReactNode, SetStateAction } from 'react'; +import React, { Key, memo, ReactElement, ReactNode } from 'react'; import GenericTable from '../../../components/GenericTable'; import Page from '../../../components/Page'; @@ -23,19 +23,25 @@ type CurrentChatsPageDataParams = { to: string; customFields: any; current: number; - itemsPerPage: number; + itemsPerPage: 25 | 50 | 100; tags: string[]; }; -const CurrentChatsPage: FC<{ +const CurrentChatsPage = ({ + data, + header, + setParams, + params, + title, + renderRow, +}: { data?: CurrentChatsPageData; header: ReactNode; - setParams: Dispatch>; + setParams: (params: any) => void; // TODO: Change to GenericTable V2 params: CurrentChatsPageDataParams; title: string; renderRow: (props: { _id?: Key }) => ReactElement; - reload: () => void; -}> = ({ data, header, setParams, params, title, renderRow, reload }) => ( +}): ReactElement => ( @@ -46,8 +52,7 @@ const CurrentChatsPage: FC<{ total={data?.total} params={params} setParams={setParams} - reload={reload} - renderFilter={({ onChange, ...props }: any): any => } + renderFilter={({ onChange, ...props }: any): ReactElement => } /> diff --git a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsRoute.tsx b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsRoute.tsx index fc69981b09a9..f2e7ad337ec7 100644 --- a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsRoute.tsx +++ b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsRoute.tsx @@ -2,7 +2,7 @@ import { Table } from '@rocket.chat/fuselage'; import { useDebouncedValue, useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useRoute, useRouteParameter, usePermission, useTranslation } from '@rocket.chat/ui-contexts'; import moment from 'moment'; -import React, { useMemo, useCallback, useState, FC } from 'react'; +import React, { useMemo, useCallback, useState, ReactElement } from 'react'; import GenericTable from '../../../components/GenericTable'; import { useEndpointData } from '../../../hooks/useEndpointData'; @@ -22,7 +22,7 @@ type useQueryType = ( to: string; tags: any[]; customFields: any; - itemsPerPage: number; + itemsPerPage: 25 | 50 | 100; current: number; }, debouncedSort: any[], @@ -90,7 +90,7 @@ const useQuery: useQueryType = ( return query; }, [guest, column, direction, itemsPerPage, current, from, to, status, servedBy, department, tags, customFields]); -const CurrentChatsRoute: FC = () => { +const CurrentChatsRoute = (): ReactElement => { const t = useTranslation(); const canViewCurrentChats = usePermission('view-livechat-current-chats'); const canRemoveClosedChats = usePermission('remove-closed-livechat-room'); @@ -107,7 +107,7 @@ const CurrentChatsRoute: FC = () => { to: '', customFields: {}, current: 0, - itemsPerPage: 25, + itemsPerPage: 25 as 25 | 50 | 100, tags: [] as string[], }); const [sort, setSort] = useState<[string, 'asc' | 'desc' | undefined]>(['ts', 'desc']); @@ -207,7 +207,6 @@ const CurrentChatsRoute: FC = () => { setParams={setParams} params={params} data={data} - reload={reload} header={header} renderRow={renderRow} title={t('Current_Chats')} diff --git a/apps/meteor/client/views/omnichannel/routes.ts b/apps/meteor/client/views/omnichannel/routes.ts index d7ab9012aca7..0ff9756b311d 100644 --- a/apps/meteor/client/views/omnichannel/routes.ts +++ b/apps/meteor/client/views/omnichannel/routes.ts @@ -20,7 +20,7 @@ registerOmnichannelRoute('/managers', { registerOmnichannelRoute('/agents/:context?/:id?', { name: 'omnichannel-agents', - component: lazy(() => import('./agents/AgentsRoute')), + component: lazy(() => import('./agents/AgentsPage')), }); registerOmnichannelRoute('/webhooks', { From e675d526ecb2ec711f8fe26cc74f139a1c2d5277 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 7 Jun 2022 23:19:54 -0300 Subject: [PATCH 06/59] Chore: Testing Kodiak feature (#25794) --- .kodiak.toml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .kodiak.toml diff --git a/.kodiak.toml b/.kodiak.toml new file mode 100644 index 000000000000..6a621f4aa218 --- /dev/null +++ b/.kodiak.toml @@ -0,0 +1,17 @@ +# .kodiak.toml +version = 1 + + +merge.automerge_label = ["stat: ready to merge", "QA tested"] + +[merge.message] +title = "pull_request_title" # default: "github_default" +body = "pull_request_body" # default: "github_default" +[merge.automerge_dependencies] +versions = ["minor", "patch"] +usernames = ["dependabot"] + +[merge] +method = "squash" + [message] + include_coauthors=true From 7ea6491ccf381cd28adb7e31574b130c5b153138 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 7 Jun 2022 23:43:58 -0300 Subject: [PATCH 07/59] Update .kodiak.toml --- .kodiak.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.kodiak.toml b/.kodiak.toml index 6a621f4aa218..03f596013ce7 100644 --- a/.kodiak.toml +++ b/.kodiak.toml @@ -2,8 +2,6 @@ version = 1 -merge.automerge_label = ["stat: ready to merge", "QA tested"] - [merge.message] title = "pull_request_title" # default: "github_default" body = "pull_request_body" # default: "github_default" @@ -15,3 +13,4 @@ usernames = ["dependabot"] method = "squash" [message] include_coauthors=true +automerge_label = ["stat: ready to merge", "QA tested", "automerge"] From ccc869fd104446514f0dccf01463d93bdc875074 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Tue, 7 Jun 2022 23:50:47 -0300 Subject: [PATCH 08/59] Update .kodiak.toml --- .kodiak.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.kodiak.toml b/.kodiak.toml index 03f596013ce7..bbbfca713a5e 100644 --- a/.kodiak.toml +++ b/.kodiak.toml @@ -2,15 +2,15 @@ version = 1 +[merge] +method = "squash" +automerge_label = ["stat: ready to merge", "QA tested", "automerge"] + [merge.message] title = "pull_request_title" # default: "github_default" body = "pull_request_body" # default: "github_default" +include_coauthors=true [merge.automerge_dependencies] versions = ["minor", "patch"] usernames = ["dependabot"] -[merge] -method = "squash" - [message] - include_coauthors=true -automerge_label = ["stat: ready to merge", "QA tested", "automerge"] From 4391ca64affcd6957450e7c684f280d3dff93043 Mon Sep 17 00:00:00 2001 From: Hugo Costa Date: Wed, 8 Jun 2022 00:28:03 -0300 Subject: [PATCH 09/59] [FIX] Discussion alphabetical ordering (#25788) ## Proposed changes (including videos or screenshots) Added a validation in the prop used for sorting (loweCaseName) checking for a prop that only exists in discussions (prid) ## Issue(s) ## Steps to test or reproduce Steps to reproduce: 1. Log in to the [rocket.chat](http://rocket.chat/) (version 4.8.0-rc.1) 2. Click on the button Display and select the option Sort by : Name Actual result: - The discussions were not sorted by name in all types of Group: Unread, Favorites and Types ![image](https://user-images.githubusercontent.com/20212776/172384754-3d33c76b-926b-4814-bb2f-22c7f0f914ae.png) Expected result: The discussions should be sorted by name ## Further comments --- apps/meteor/app/ui-sidenav/client/roomList.js | 2 +- apps/meteor/client/sidebar/hooks/useQueryOptions.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/meteor/app/ui-sidenav/client/roomList.js b/apps/meteor/app/ui-sidenav/client/roomList.js index 3dfdf1189230..23bb768cf3e8 100644 --- a/apps/meteor/app/ui-sidenav/client/roomList.js +++ b/apps/meteor/app/ui-sidenav/client/roomList.js @@ -129,7 +129,7 @@ const getLowerCaseNames = (room, nameDefault = '', fnameDefault = '') => { const name = room.name || nameDefault; const fname = room.fname || fnameDefault || name; return { - lowerCaseName: String(name).toLowerCase(), + lowerCaseName: String(!room.prid ? name : fname).toLowerCase(), lowerCaseFName: String(fname).toLowerCase(), }; }; diff --git a/apps/meteor/client/sidebar/hooks/useQueryOptions.js b/apps/meteor/client/sidebar/hooks/useQueryOptions.js index 6bd31aad23f2..812ef6936832 100644 --- a/apps/meteor/client/sidebar/hooks/useQueryOptions.js +++ b/apps/meteor/client/sidebar/hooks/useQueryOptions.js @@ -10,8 +10,7 @@ export const useQueryOptions = () => { sort: { ...(sortBy === 'activity' && { lm: -1 }), ...(sortBy !== 'activity' && { - ...(showRealName && { lowerCaseFName: /descending/.test(sortBy) ? -1 : 1 }), - ...(!showRealName && { lowerCaseName: /descending/.test(sortBy) ? -1 : 1 }), + ...(showRealName ? { lowerCaseFName: 1 } : { lowerCaseName: 1 }), }), }, }), From b021a2fe922c595782f4b90debb131b25d1ee267 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Tue, 7 Jun 2022 22:48:10 -0600 Subject: [PATCH 10/59] [FIX] Fix prom-client new promise usage (#25781) ## Proposed changes (including videos or screenshots) ## Issue(s) ## Steps to test or reproduce ## Further comments Looks like the update of de dep had breaking changes and now uses promises at some calls. This PR fixes those promise usages and converts the file to TS (hopefully it helps us to spot the error in the future) --- .../{collectMetrics.js => collectMetrics.ts} | 51 ++++++++++--------- .../externals/meteor/facts-base.d.ts | 5 ++ .../definition/externals/meteor/mongo.d.ts | 1 + apps/meteor/package.json | 1 + yarn.lock | 8 +++ 5 files changed, 43 insertions(+), 23 deletions(-) rename apps/meteor/app/metrics/server/lib/{collectMetrics.js => collectMetrics.ts} (77%) create mode 100644 apps/meteor/definition/externals/meteor/facts-base.d.ts diff --git a/apps/meteor/app/metrics/server/lib/collectMetrics.js b/apps/meteor/app/metrics/server/lib/collectMetrics.ts similarity index 77% rename from apps/meteor/app/metrics/server/lib/collectMetrics.js rename to apps/meteor/app/metrics/server/lib/collectMetrics.ts index 46e32dd1d1d8..61eacebba057 100644 --- a/apps/meteor/app/metrics/server/lib/collectMetrics.js +++ b/apps/meteor/app/metrics/server/lib/collectMetrics.ts @@ -15,21 +15,23 @@ import { SystemLogger } from '../../../../server/lib/logger/system'; import { metrics } from './metrics'; import { getAppsStatistics } from '../../../statistics/server/lib/getAppsStatistics'; -Facts.incrementServerFact = function (pkg, fact, increment) { +Facts.incrementServerFact = function (pkg: 'pkg' | 'fact', fact: string | number, increment: number): void { metrics.meteorFacts.inc({ pkg, fact }, increment); }; -const setPrometheusData = async () => { +const setPrometheusData = async (): Promise => { metrics.info.set( { version: Info.version, - unique_id: settings.get('uniqueID'), - site_url: settings.get('Site_Url'), + // eslint-disable-next-line @typescript-eslint/camelcase + unique_id: settings.get('uniqueID'), + // eslint-disable-next-line @typescript-eslint/camelcase + site_url: settings.get('Site_Url'), }, 1, ); - const sessions = Array.from(Meteor.server.sessions.values()); + const sessions = Array.from<{ userId: string }>(Meteor.server.sessions.values()); const authenticatedSessions = sessions.filter((s) => s.userId); metrics.ddpSessions.set(Meteor.server.sessions.size); metrics.ddpAuthenticatedSessions.set(authenticatedSessions.length); @@ -53,7 +55,7 @@ const setPrometheusData = async () => { metrics.version.set({ version: statistics.version }, 1); metrics.migration.set(getControl().version); metrics.instanceCount.set(statistics.instanceCount); - metrics.oplogEnabled.set({ enabled: statistics.oplogEnabled }, 1); + metrics.oplogEnabled.set({ enabled: `${statistics.oplogEnabled}` }, 1); // User statistics metrics.totalUsers.set(statistics.totalUsers); @@ -85,17 +87,17 @@ const app = connect(); // const compression = require('compression'); // app.use(compression()); -app.use('/metrics', (req, res) => { +app.use('/metrics', (_req, res) => { res.setHeader('Content-Type', 'text/plain'); - const data = client.register.metrics(); + client.register.metrics().then((data) => { + metrics.metricsRequests.inc(); + metrics.metricsSize.set(data.length); - metrics.metricsRequests.inc(); - metrics.metricsSize.set(data.length); - - res.end(data); + res.end(data); + }); }); -app.use('/', (req, res) => { +app.use('/', (_req, res) => { const html = ` Rocket.Chat Prometheus Exporter @@ -112,8 +114,8 @@ app.use('/', (req, res) => { const server = http.createServer(app); -let timer; -let resetTimer; +let timer: number; +let resetTimer: number; let defaultMetricsInitiated = false; let gcStatsInitiated = false; const was = { @@ -122,19 +124,19 @@ const was = { resetInterval: 0, collectGC: false, }; -const updatePrometheusConfig = async () => { +const updatePrometheusConfig = async (): Promise => { const is = { port: process.env.PROMETHEUS_PORT || settings.get('Prometheus_Port'), - enabled: settings.get('Prometheus_Enabled'), - resetInterval: settings.get('Prometheus_Reset_Interval'), - collectGC: settings.get('Prometheus_Garbage_Collector'), + enabled: settings.get('Prometheus_Enabled'), + resetInterval: settings.get('Prometheus_Reset_Interval'), + collectGC: settings.get('Prometheus_Garbage_Collector'), }; if (Object.values(is).some((s) => s == null)) { return; } - if (Object.entries(is).every(([k, v]) => v === was[k])) { + if (Object.entries(is).every(([k, v]) => v === was[k as keyof typeof was])) { return; } @@ -162,8 +164,11 @@ const updatePrometheusConfig = async () => { Meteor.clearInterval(resetTimer); if (is.resetInterval) { resetTimer = Meteor.setInterval(() => { - client.register.getMetricsAsArray().forEach((metric) => { - metric.hashMap = {}; + client.register.getMetricsAsArray().then((metrics) => { + metrics.forEach((metric) => { + // @ts-expect-error + metric.hashMap = {}; + }); }); }, is.resetInterval); } @@ -177,7 +182,7 @@ const updatePrometheusConfig = async () => { } if (is.collectGC && gcStatsInitiated === false) { gcStatsInitiated = true; - gcStats()(); + gcStats(client.register)(); } } catch (error) { SystemLogger.error(error); diff --git a/apps/meteor/definition/externals/meteor/facts-base.d.ts b/apps/meteor/definition/externals/meteor/facts-base.d.ts new file mode 100644 index 000000000000..9b56d44d5ce2 --- /dev/null +++ b/apps/meteor/definition/externals/meteor/facts-base.d.ts @@ -0,0 +1,5 @@ +declare module 'meteor/facts-base' { + namespace Facts { + function incrementServerFact(pkg: 'pkg' | 'fact', fact: string | number, increment: number): void; + } +} diff --git a/apps/meteor/definition/externals/meteor/mongo.d.ts b/apps/meteor/definition/externals/meteor/mongo.d.ts index 7efb5b5b0860..7c973f888777 100644 --- a/apps/meteor/definition/externals/meteor/mongo.d.ts +++ b/apps/meteor/definition/externals/meteor/mongo.d.ts @@ -12,6 +12,7 @@ declare module 'meteor/mongo' { onSkippedEntries(callback: Function): void; waitUntilCaughtUp(): void; _defineTooFarBehind(value: number): void; + _entryQueue?: unknown[]; } interface MongoConnection { diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 0988c0b32247..993111666eba 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -115,6 +115,7 @@ "@types/nodemailer": "^6.4.4", "@types/parseurl": "^1.3.1", "@types/photoswipe": "^4.1.2", + "@types/prometheus-gc-stats": "^0.6.2", "@types/psl": "^1.1.0", "@types/react": "~17.0.42", "@types/react-dom": "~17.0.14", diff --git a/yarn.lock b/yarn.lock index 6009cdc66041..79f53cff45e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4848,6 +4848,7 @@ __metadata: "@types/object-path": ^0.11.1 "@types/parseurl": ^1.3.1 "@types/photoswipe": ^4.1.2 + "@types/prometheus-gc-stats": ^0.6.2 "@types/proxy-from-env": ^1.0.1 "@types/psl": ^1.1.0 "@types/react": ~17.0.42 @@ -7661,6 +7662,13 @@ __metadata: languageName: node linkType: hard +"@types/prometheus-gc-stats@npm:^0.6.2": + version: 0.6.2 + resolution: "@types/prometheus-gc-stats@npm:0.6.2" + checksum: 403b3dbd792b83e592376e2002260cf57fb18f98c8b8528a24dc65e545cb8d0e9bf9941dc28edfa397b670b9a7336913da991005ef0278611209dde9b51406db + languageName: node + linkType: hard + "@types/prop-types@npm:*": version: 15.7.4 resolution: "@types/prop-types@npm:15.7.4" From 9629831bd712e0b48eded92518cc44ddbc482d35 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 8 Jun 2022 09:46:37 -0300 Subject: [PATCH 11/59] Chore: Fix CI (#25797) --- apps/meteor/app/slashcommands-join/client/client.ts | 4 ++-- packages/core-typings/src/SlashCommands/index.ts | 2 +- packages/rest-typings/src/v1/commands.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/meteor/app/slashcommands-join/client/client.ts b/apps/meteor/app/slashcommands-join/client/client.ts index 3fb1ac1c949d..fcf56d6e55a6 100644 --- a/apps/meteor/app/slashcommands-join/client/client.ts +++ b/apps/meteor/app/slashcommands-join/client/client.ts @@ -10,8 +10,8 @@ slashCommands.add( params: '#channel', permission: 'view-c-room', }, - function (err: Meteor.Error, _result: unknown, params: Record) { - if (err.error === 'error-user-already-in-room') { + function (err, _result: unknown, params: Record) { + if ((err as Meteor.Error).error === 'error-user-already-in-room') { params.cmd = 'open'; params.msg.msg = params.msg.msg.replace('join', 'open'); return slashCommands.run('open', params.params, params.msg, ''); diff --git a/packages/core-typings/src/SlashCommands/index.ts b/packages/core-typings/src/SlashCommands/index.ts index b8e0d5d9a7f9..928d34008c5e 100644 --- a/packages/core-typings/src/SlashCommands/index.ts +++ b/packages/core-typings/src/SlashCommands/index.ts @@ -43,7 +43,7 @@ export type SlashCommand = { description: SlashCommandOptions['description']; permission: SlashCommandOptions['permission']; clientOnly?: SlashCommandOptions['clientOnly']; - result?: (err: Meteor.Error, result: never, data: { cmd: string; params: string; msg: IMessage }) => void; + result?: (err: unknown, result: never, data: { cmd: string; params: string; msg: IMessage }) => void; providesPreview: boolean; previewer?: SlashCommandPreviewer; previewCallback?: SlashCommandPreviewCallback; diff --git a/packages/rest-typings/src/v1/commands.ts b/packages/rest-typings/src/v1/commands.ts index 32e14b1c0393..aec14e57c669 100644 --- a/packages/rest-typings/src/v1/commands.ts +++ b/packages/rest-typings/src/v1/commands.ts @@ -5,7 +5,7 @@ import type { PaginatedResult } from '../helpers/PaginatedResult'; export type CommandsEndpoints = { 'commands.get': { GET: (params: { command: string }) => { - command: SlashCommand; + command: Pick; }; }; 'commands.list': { @@ -14,7 +14,7 @@ export type CommandsEndpoints = { fields?: string; }>, ) => PaginatedResult<{ - commands: SlashCommand[]; + commands: Pick[]; }>; }; 'commands.run': { From 2a292e4febdf2e9cddcfa86e37aea254918f4de1 Mon Sep 17 00:00:00 2001 From: Tiago Evangelista Pinto Date: Wed, 8 Jun 2022 15:05:01 -0300 Subject: [PATCH 12/59] [FIX] AccountBox checks for condition (#25708) ## Proposed changes (including videos or screenshots) ## Issue(s) Close: #25704 ## Steps to test or reproduce ## Further comments Fixes #25704 --- .../app/ui-utils/client/lib/AccountBox.ts | 36 +++++++++++++------ .../client/sidebar/header/UserDropdown.tsx | 6 ++-- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/apps/meteor/app/ui-utils/client/lib/AccountBox.ts b/apps/meteor/app/ui-utils/client/lib/AccountBox.ts index a0b2e8a6ac43..a3f42a8645bc 100644 --- a/apps/meteor/app/ui-utils/client/lib/AccountBox.ts +++ b/apps/meteor/app/ui-utils/client/lib/AccountBox.ts @@ -1,4 +1,4 @@ -import { IUActionButtonWhen, IUIActionButton } from '@rocket.chat/apps-engine/definition/ui/IUIActionButtonDescriptor'; +import { IUIActionButton, IUActionButtonWhen } from '@rocket.chat/apps-engine/definition/ui/IUIActionButtonDescriptor'; import { ReactiveVar } from 'meteor/reactive-var'; import { Tracker } from 'meteor/tracker'; import { Meteor } from 'meteor/meteor'; @@ -6,18 +6,28 @@ import { Meteor } from 'meteor/meteor'; import { SideNav } from './SideNav'; import { applyDropdownActionButtonFilters } from '../../../ui-message/client/actionButtons/lib/applyButtonFilters'; -export interface IAccountBoxItem extends Omit { +export interface IAppAccountBoxItem extends IUIActionButton { name: string; icon?: string; href?: string; sideNav?: string; isAppButtonItem?: boolean; - subItems?: [IAccountBoxItem]; + subItems?: [IAppAccountBoxItem]; when?: Omit; } +type AccountBoxItem = { + name: string; + icon: string; + href: string; + sideNav?: string; + condition: () => boolean; +}; + +export const isAppAccountBoxItem = (item: IAppAccountBoxItem | AccountBoxItem): item is IAppAccountBoxItem => 'isAppButtonItem' in item; + export class AccountBoxBase { - private items = new ReactiveVar([]); + private items = new ReactiveVar([]); private status = 0; @@ -48,25 +58,31 @@ export class AccountBoxBase { this.status = 0; } - public async addItem(newItem: IAccountBoxItem): Promise { + public async addItem(newItem: IAppAccountBoxItem): Promise { Tracker.nonreactive(() => { const actual = this.items.get(); - actual.push(newItem as never); + actual.push(newItem); this.items.set(actual); }); } - public async deleteItem(item: IAccountBoxItem): Promise { + public async deleteItem(item: IAppAccountBoxItem): Promise { Tracker.nonreactive(() => { const actual = this.items.get(); - const itemIndex = actual.findIndex((actualItem: IAccountBoxItem) => actualItem.appId === item.appId); + const itemIndex = actual.findIndex((actualItem: IAppAccountBoxItem) => actualItem.appId === item.appId); actual.splice(itemIndex, 1); this.items.set(actual); }); } - public getItems(): IAccountBoxItem[] { - return this.items.get().filter((item: IAccountBoxItem) => applyDropdownActionButtonFilters(item)); + public getItems(): (IAppAccountBoxItem | AccountBoxItem)[] { + return this.items.get().filter((item: IAppAccountBoxItem | AccountBoxItem) => { + if ('condition' in item) { + return item.condition(); + } + + return applyDropdownActionButtonFilters(item); + }); } } diff --git a/apps/meteor/client/sidebar/header/UserDropdown.tsx b/apps/meteor/client/sidebar/header/UserDropdown.tsx index a56c63d6abba..d74898a8e173 100644 --- a/apps/meteor/client/sidebar/header/UserDropdown.tsx +++ b/apps/meteor/client/sidebar/header/UserDropdown.tsx @@ -8,7 +8,7 @@ import React, { ReactElement } from 'react'; import { triggerActionButtonAction } from '../../../app/ui-message/client/ActionManager'; import { AccountBox, SideNav } from '../../../app/ui-utils/client'; -import { IAccountBoxItem } from '../../../app/ui-utils/client/lib/AccountBox'; +import { IAppAccountBoxItem, isAppAccountBoxItem } from '../../../app/ui-utils/client/lib/AccountBox'; import { userStatus } from '../../../app/user-status/client'; import { callbacks } from '../../../lib/callbacks'; import MarkdownText from '../../components/MarkdownText'; @@ -105,7 +105,7 @@ const UserDropdown = ({ user, onClose }: UserDropdownProps): ReactElement => { const accountBoxItems = useReactiveValue(getItems); - const appBoxItems = (): IAccountBoxItem[] => accountBoxItems.filter((item) => item.isAppButtonItem); + const appBoxItems = (): IAppAccountBoxItem[] => accountBoxItems.filter((item): item is IAppAccountBoxItem => isAppAccountBoxItem(item)); return ( @@ -176,7 +176,7 @@ const UserDropdown = ({ user, onClose }: UserDropdownProps): ReactElement => { {showAdmin && } {accountBoxItems - .filter((item) => !item.isAppButtonItem) + .filter((item) => !isAppAccountBoxItem(item)) .map((item, i) => { const action = (): void => { if (item.href) { From d5367a725b010342f81f286cdda3454272ebfba6 Mon Sep 17 00:00:00 2001 From: Duda Nogueira Date: Wed, 8 Jun 2022 15:22:49 -0300 Subject: [PATCH 13/59] [FIX] Wrong argument name preventing Omnichannel Chat Forward to User (#25723) --- .../client/components/Omnichannel/modals/ForwardChatModal.tsx | 2 +- packages/rest-typings/src/v1/users.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/meteor/client/components/Omnichannel/modals/ForwardChatModal.tsx b/apps/meteor/client/components/Omnichannel/modals/ForwardChatModal.tsx index 853dd7c7d293..e28465d697e2 100644 --- a/apps/meteor/client/components/Omnichannel/modals/ForwardChatModal.tsx +++ b/apps/meteor/client/components/Omnichannel/modals/ForwardChatModal.tsx @@ -59,7 +59,7 @@ const ForwardChatModal = ({ let uid; if (username) { - const { user } = await getUserData({ userName: username }); + const { user } = await getUserData({ username }); uid = user?._id; } diff --git a/packages/rest-typings/src/v1/users.ts b/packages/rest-typings/src/v1/users.ts index 4b311d1af3d2..dc6e6b736558 100644 --- a/packages/rest-typings/src/v1/users.ts +++ b/packages/rest-typings/src/v1/users.ts @@ -5,7 +5,7 @@ const ajv = new Ajv({ coerceTypes: true, }); -type UsersInfo = { userId?: IUser['_id']; userName?: IUser['username'] }; +type UsersInfo = { userId?: IUser['_id']; username?: IUser['username'] }; const UsersInfoSchema = { type: 'object', @@ -14,7 +14,7 @@ const UsersInfoSchema = { type: 'string', nullable: true, }, - userName: { + username: { type: 'string', nullable: true, }, From 611c0b31fbb865b52f6e8a4eb5febddcb33e9b79 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Wed, 8 Jun 2022 16:06:24 -0300 Subject: [PATCH 14/59] Chore: RestApiClient as Package (#25469) ## Proposed changes (including videos or screenshots) ## Issue(s) ## Steps to test or reproduce ## Further comments Co-authored-by: Tasso Evangelista <2263066+tassoevan@users.noreply.github.com> --- .../app/api/server/lib/getUploadFormData.js | 1 - .../v1/{emoji-custom.js => emoji-custom.ts} | 79 +++--- apps/meteor/app/api/server/v1/settings.ts | 2 +- .../app/apps/client/RealAppsEngineUIHost.js | 2 +- .../apps/client/communication/websockets.js | 2 +- .../app/apps/client/gameCenter/gameCenter.js | 2 +- .../apps/client/gameCenter/gameContainer.js | 4 +- apps/meteor/app/apps/client/orchestrator.ts | 36 +-- .../app/authorization/client/startup.js | 4 +- .../authorization/server/methods/saveRole.ts | 10 +- .../emoji-custom/client/lib/emojiCustom.js | 2 +- .../client/lib/stream/queueManager.js | 4 +- .../client/views/app/dialog/closeRoom.js | 6 +- .../client/views/app/livechatReadOnly.js | 6 +- .../client/views/app/tabbar/agentEdit.js | 6 +- .../client/views/app/tabbar/agentInfo.js | 6 +- .../views/app/tabbar/contactChatHistory.js | 6 +- .../app/tabbar/contactChatHistoryMessages.js | 18 +- .../client/views/app/tabbar/visitorEdit.js | 8 +- .../views/app/tabbar/visitorForward.html | 57 ++--- .../client/views/app/tabbar/visitorForward.js | 2 +- .../client/views/app/tabbar/visitorInfo.js | 8 +- .../views/app/tabbar/visitorNavigation.js | 7 +- .../views/app/tabbar/visitorTranscript.js | 4 +- .../client/views/mentionsFlexTab.js | 5 +- .../client/views/pinnedMessages.js | 5 +- .../client/page/snippetPage.js | 2 +- .../client/tabBar/views/snippetedMessages.js | 5 +- .../client/views/starredMessages.js | 5 +- .../client/autocomplete-client.js | 2 +- .../app/models/server/raw/EmojiCustom.ts | 2 +- .../client/oauth/oauth2-client.js | 2 +- .../app/otr/client/rocketchat.otr.room.js | 2 +- .../ui-message/client/ActionButtonSyncer.ts | 2 +- .../app/ui-message/client/ActionManager.js | 5 +- apps/meteor/app/ui/client/lib/chatMessages.js | 2 +- apps/meteor/app/ui/client/lib/fileUpload.js | 164 ------------ apps/meteor/app/ui/client/lib/fileUpload.ts | 236 ++++++++++++++++++ .../app/utils/client/lib/RestApiClient.ts | 52 ++++ ...RestApiClient.d.ts => _RestApiClient.d.ts} | 8 +- .../{RestApiClient.js => _RestApiClient.js} | 0 .../app/utils/lib/fileUploadRestrictions.js | 2 +- apps/meteor/app/utils/lib/slashCommand.ts | 2 +- .../app/videobridge/client/actionLink.js | 2 +- apps/meteor/app/webdav/client/startup/sync.js | 2 +- apps/meteor/app/webrtc/client/actionLink.tsx | 2 +- apps/meteor/app/webrtc/client/tabBar.tsx | 2 +- .../CreateDiscussion/CreateDiscussion.tsx | 2 +- .../DefaultParentRoomField.tsx | 2 +- .../client/components/Omnichannel/Tags.tsx | 2 +- .../Omnichannel/hooks/useAgentsList.ts | 3 +- .../hooks/useAvailableAgentsList.ts | 3 +- .../Omnichannel/hooks/useDepartmentsList.ts | 2 +- .../Omnichannel/modals/CloseChatModalData.tsx | 2 +- .../Omnichannel/modals/ForwardChatModal.tsx | 2 +- .../RoomAutoComplete/RoomAutoComplete.tsx | 2 +- .../RoomAutoComplete/hooks/useRoomsList.ts | 3 +- .../lib/OmnichannelRoomIcon.ts | 3 +- .../TwoFactorModal/TwoFactorEmailModal.tsx | 2 +- .../UserAutoComplete/UserAutoComplete.tsx | 2 +- .../UserAutoCompleteMultiple.tsx | 2 +- .../components/message/Metrics/Thread.tsx | 4 +- apps/meteor/client/hooks/useEndpointUpload.ts | 7 +- apps/meteor/client/hooks/useUpdateAvatar.ts | 6 +- apps/meteor/client/lib/meteorCallWrapper.ts | 6 +- apps/meteor/client/lib/presence.ts | 11 +- apps/meteor/client/lib/userData.ts | 2 +- .../providers/CallProvider/CallProvider.tsx | 8 +- .../CallProvider/hooks/useVoipClient.ts | 4 +- .../client/providers/ServerProvider.tsx | 17 +- .../client/sidebar/footer/voip/index.tsx | 2 +- .../sidebar/header/CreateChannelWithData.tsx | 4 +- .../sidebar/header/CreateDirectMessage.tsx | 2 +- apps/meteor/client/startup/banners.ts | 10 +- apps/meteor/client/startup/routes.tsx | 4 +- apps/meteor/client/startup/slashCommands.ts | 8 +- .../stories/contexts/ServerContextMock.tsx | 4 +- .../views/account/AccountProfilePage.js | 2 +- .../views/account/tokens/AccountTokensPage.js | 2 +- .../client/views/admin/cloud/PasteStep.tsx | 2 +- .../admin/customEmoji/AddCustomEmoji.tsx | 2 +- .../views/admin/customEmoji/CustomEmoji.tsx | 2 +- .../admin/customEmoji/EditCustomEmoji.tsx | 4 +- .../customEmoji/EditCustomEmojiWithData.tsx | 2 +- .../admin/customSounds/AdminSoundsRoute.tsx | 2 +- .../admin/customSounds/EditCustomSound.tsx | 2 +- .../CustomUserStatusFormWithData.tsx | 2 +- .../CustomUserStatusTable.tsx | 2 +- .../emailInbox/EmailInboxEditWithData.tsx | 2 +- .../views/admin/emailInbox/EmailInboxForm.js | 8 +- .../admin/emailInbox/EmailInboxTable.tsx | 2 +- .../views/admin/emailInbox/SendTestButton.tsx | 2 +- .../views/admin/import/ImportHistoryPage.js | 8 +- .../views/admin/import/ImportProgressPage.js | 4 +- .../views/admin/import/NewImportPage.js | 4 +- .../views/admin/import/PrepareImportPage.js | 6 +- .../views/admin/info/InformationRoute.tsx | 2 +- .../client/views/admin/info/LicenseCard.tsx | 2 +- .../admin/integrations/IntegrationsTable.js | 2 +- .../edit/EditIncomingWebhookWithData.js | 2 +- .../edit/EditOutgoingWebhookWithData.js | 2 +- .../edit/OutgoingWebhookHistoryPage.js | 2 +- .../client/views/admin/invites/InviteRow.tsx | 2 +- .../views/admin/invites/InvitesPage.tsx | 2 +- .../admin/oauthApps/EditOauthAppWithData.tsx | 2 +- .../views/admin/oauthApps/OAuthAppsTable.tsx | 2 +- .../views/admin/permissions/EditRolePage.tsx | 6 +- .../UsersInRole/UsersInRolePage.tsx | 2 +- .../UsersInRoleTable/UsersInRoleTable.tsx | 2 +- .../UsersInRoleTableWithData.tsx | 2 +- .../client/views/admin/rooms/EditRoom.tsx | 6 +- .../views/admin/rooms/EditRoomWithData.tsx | 2 +- .../client/views/admin/rooms/RoomsTable.tsx | 2 +- .../admin/settings/groups/LDAPGroupPage.tsx | 6 +- .../settings/groups/voip/AssignAgentModal.tsx | 4 +- .../groups/voip/RemoveAgentButton.tsx | 2 +- .../groups/voip/VoipExtensionsPage.tsx | 2 +- .../client/views/admin/users/AddUser.js | 2 +- .../client/views/admin/users/EditUser.js | 8 +- .../views/admin/users/EditUserWithData.js | 4 +- .../client/views/admin/users/UserInfo.js | 2 +- .../views/admin/users/UserInfoActions.js | 8 +- .../client/views/admin/users/UsersPage.js | 2 +- .../views/admin/viewLogs/ServerLogs.tsx | 2 +- .../client/views/directory/ChannelsTable.js | 2 +- .../client/views/directory/TeamsTable.js | 2 +- .../client/views/directory/UserTable.js | 2 +- .../views/hooks/useDepartmentsByUnitsList.ts | 3 +- .../client/views/hooks/useMembersList.ts | 6 +- .../client/views/hooks/useMonitorsList.ts | 4 +- .../client/views/hooks/useUpgradeTabParams.ts | 4 +- .../meteor/client/views/invite/InvitePage.tsx | 16 +- apps/meteor/client/views/meet/MeetPage.tsx | 15 +- .../omnichannel/DepartmentAutoComplete.js | 2 +- .../views/omnichannel/agents/AddAgent.tsx | 2 +- .../omnichannel/agents/AgentEditWithData.tsx | 6 +- .../views/omnichannel/agents/AgentInfo.tsx | 2 +- .../omnichannel/agents/AgentInfoActions.tsx | 2 +- .../views/omnichannel/agents/AgentsPage.tsx | 2 +- .../omnichannel/agents/RemoveAgentButton.tsx | 2 +- .../appearance/AppearancePageContainer.tsx | 2 +- .../businessHours/EditBusinessHoursPage.js | 2 +- .../omnichannel/components/CustomField.js | 2 +- .../currentChats/CurrentChatsRoute.tsx | 2 +- .../omnichannel/currentChats/FilterByText.tsx | 2 +- .../customFields/CustomFieldsRoute.js | 2 +- .../EditCustomFieldsPageContainer.js | 2 +- .../views/omnichannel/departments/AddAgent.js | 2 +- .../departments/DepartmentsRoute.js | 2 +- .../omnichannel/departments/EditDepartment.js | 2 +- .../EditDepartmentWithAllowedForwardData.js | 2 +- .../departments/EditDepartmentWithData.js | 2 +- .../departments/RemoveDepartmentButton.js | 2 +- .../directory/CallsContextualBarDirectory.tsx | 2 +- .../directory/ChatsContextualBar.tsx | 2 +- .../omnichannel/directory/calls/CallTable.tsx | 2 +- .../omnichannel/directory/chats/ChatTable.tsx | 2 +- .../chats/contextualBar/AgentField.js | 7 +- .../directory/chats/contextualBar/ChatInfo.js | 2 +- .../chats/contextualBar/ChatInfoDirectory.js | 2 +- .../chats/contextualBar/ContactField.js | 11 +- .../chats/contextualBar/DepartmentField.js | 2 +- .../chats/contextualBar/PriorityField.js | 11 +- .../directory/chats/contextualBar/RoomEdit.js | 4 +- .../chats/contextualBar/RoomEditWithData.js | 11 +- .../chats/contextualBar/VisitorClientInfo.js | 11 +- .../chats/contextualBar/VisitorData.js | 11 +- .../directory/contacts/ContactTable.js | 2 +- .../contextualBar/ContactEditWithData.js | 11 +- .../contacts/contextualBar/ContactInfo.js | 13 +- .../contacts/contextualBar/ContactNewEdit.js | 16 +- .../views/omnichannel/managers/AddManager.tsx | 2 +- .../omnichannel/managers/ManagersRoute.tsx | 2 +- .../managers/RemoveManagerButton.tsx | 2 +- .../views/omnichannel/queueList/index.tsx | 2 +- .../charts/AgentStatusChart.js | 2 +- .../charts/ChatDurationChart.js | 2 +- .../realTimeMonitoring/charts/ChatsChart.js | 2 +- .../charts/ChatsPerAgentChart.js | 2 +- .../charts/ChatsPerDepartmentChart.js | 2 +- .../charts/ResponseTimesChart.js | 2 +- .../overviews/AgentsOverview.js | 2 +- .../overviews/ChatsOverview.js | 2 +- .../overviews/ConversationOverview.js | 2 +- .../overviews/ProductivityOverview.js | 2 +- .../triggers/EditTriggerPageContainer.js | 2 +- .../triggers/TriggersTableContainer.js | 2 +- .../webhooks/WebhooksPageContainer.js | 2 +- .../QuickActions/hooks/useQuickActions.tsx | 4 +- .../Header/ParentRoomWithEndpointData.tsx | 2 +- .../client/views/room/Header/ParentTeam.tsx | 4 +- .../providers/MessageListProvider.tsx | 2 +- .../client/views/room/UserCard/index.js | 2 +- .../AutoTranslate/AutoTranslateWithData.tsx | 4 +- .../Discussions/useDiscussionsList.ts | 2 +- .../ExportMessages/FileExport.tsx | 2 +- .../ExportMessages/MailExportForm.tsx | 2 +- .../Info/EditRoomInfo/EditChannel.js | 4 +- .../PruneMessages/PruneMessagesWithData.tsx | 2 +- .../RoomFiles/hooks/useFilesList.ts | 12 +- .../InviteUsers/WrappedInviteUsers.js | 2 +- .../contextualBar/Threads/useThreadsList.ts | 2 +- .../UserInfo/UserInfoWithData.js | 2 +- .../views/room/hooks/useUserInfoActions.js | 4 +- .../views/room/threads/ThreadComponent.tsx | 2 +- .../providers/SetupWizardProvider.tsx | 2 +- .../steps/CloudAccountConfirmation.tsx | 2 +- .../ConvertToChannelModal.tsx | 2 +- .../teams/CreateTeamModal/CreateTeamModal.tsx | 2 +- .../teams/CreateTeamModal/UsersInput.tsx | 2 +- .../TeamAutocomplete/TeamAutocomplete.js | 2 +- .../AddExistingModal/AddExistingModal.tsx | 2 +- .../channels/AddExistingModal/RoomsInput.tsx | 2 +- .../channels/hooks/useTeamsChannelList.ts | 2 +- .../info/Delete/DeleteTeamModalWithRooms.tsx | 2 +- .../teams/contextualBar/info/Leave/index.js | 2 +- .../views/teams/contextualBar/info/index.js | 2 +- .../RemoveUsersModal/RemoveUsersModal.js | 4 +- .../externals/meteor/accounts-base.d.ts | 4 + .../definition/externals/meteor/session.d.ts | 7 + .../client/startup/responses.js | 2 +- .../visitorEditCustomFieldsForm.js | 2 +- .../customTemplates/visitorInfoCustomForm.js | 2 +- apps/meteor/ee/client/audit/AuditPageBase.js | 2 +- .../RoomAutoComplete/RoomAutoComplete.js | 2 +- .../ee/client/audit/VisitorAutoComplete.js | 2 +- apps/meteor/ee/client/ecdh.ts | 12 +- apps/meteor/ee/client/hooks/useAgentsList.ts | 2 +- apps/meteor/ee/client/hooks/useTagsList.ts | 2 +- apps/meteor/ee/client/lib/getFromRestApi.ts | 4 +- .../BusinessHoursTableContainer.js | 14 +- .../client/omnichannel/ContactManagerInfo.js | 7 +- .../DepartmentBusinessHours.js | 2 +- .../cannedResponses/CannedResponseEdit.tsx | 2 +- .../CannedResponseEditWithData.tsx | 2 +- .../CannedResponseEditWithDepartmentData.tsx | 2 +- .../cannedResponses/CannedResponsesRoute.tsx | 4 +- .../modals/CreateCannedResponse/index.tsx | 2 +- .../hooks/useCannedResponseFilterOptions.ts | 2 +- .../hooks/useCannedResponseList.ts | 4 +- .../omnichannel/monitors/MonitorsPage.js | 2 +- .../omnichannel/priorities/PrioritiesRoute.js | 2 +- .../priorities/PriorityEditWithData.js | 2 +- .../omnichannel/tags/TagEditWithData.js | 2 +- .../tags/TagEditWithDepartmentData.tsx | 2 +- .../ee/client/omnichannel/tags/TagNew.js | 2 +- .../ee/client/omnichannel/tags/TagsRoute.js | 2 +- .../omnichannel/units/UnitEditWithData.tsx | 10 +- .../ee/client/omnichannel/units/UnitNew.js | 4 +- .../ee/client/omnichannel/units/UnitsRoute.js | 2 +- .../EngagementDashboardRoute.tsx | 2 +- .../client/views/admin/users/useSeatsCap.ts | 2 +- .../rest/v1/omnichannel/businessHours.ts | 2 +- .../rest/v1/omnichannel/businessUnits.ts | 10 +- .../rest/v1/omnichannel/cannedResponses.ts | 4 +- apps/meteor/package.json | 3 + packages/api-client/.eslintrc | 4 + packages/api-client/package.json | 32 +++ .../api-client/src/RestClientInterface.ts | 61 +++++ packages/api-client/src/index.ts | 215 ++++++++++++++++ packages/api-client/tsconfig.json | 8 + .../src/ICustomEmojiDescriptor.ts | 2 +- packages/core-typings/src/IEmojiCustom.ts | 2 +- packages/rest-typings/src/apps/index.ts | 11 + packages/rest-typings/src/index.ts | 8 +- packages/rest-typings/src/v1/autoTranslate.ts | 6 +- packages/rest-typings/src/v1/banners.ts | 8 +- .../rest-typings/src/v1/channels/channels.ts | 58 ++--- packages/rest-typings/src/v1/chat.ts | 34 +-- packages/rest-typings/src/v1/cloud.ts | 8 +- packages/rest-typings/src/v1/commands.ts | 8 +- packages/rest-typings/src/v1/customSounds.ts | 2 +- .../rest-typings/src/v1/customUserStatus.ts | 8 +- packages/rest-typings/src/v1/directory.ts | 2 +- packages/rest-typings/src/v1/dm/dm.ts | 26 +- packages/rest-typings/src/v1/dm/im.ts | 30 +-- packages/rest-typings/src/v1/dns.ts | 4 +- packages/rest-typings/src/v1/e2e.ts | 10 +- packages/rest-typings/src/v1/email-inbox.ts | 10 +- packages/rest-typings/src/v1/emojiCustom.ts | 32 ++- packages/rest-typings/src/v1/groups.ts | 28 +-- packages/rest-typings/src/v1/instances.ts | 2 +- packages/rest-typings/src/v1/invites.ts | 10 +- packages/rest-typings/src/v1/ldap.ts | 6 +- packages/rest-typings/src/v1/licenses.ts | 8 +- packages/rest-typings/src/v1/me.ts | 35 +++ packages/rest-typings/src/v1/misc.ts | 2 +- packages/rest-typings/src/v1/oauthapps.ts | 4 +- packages/rest-typings/src/v1/omnichannel.ts | 71 +++--- packages/rest-typings/src/v1/permissions.ts | 4 +- packages/rest-typings/src/v1/push.ts | 4 +- packages/rest-typings/src/v1/roles.ts | 16 +- packages/rest-typings/src/v1/rooms.ts | 42 ++-- packages/rest-typings/src/v1/settings.ts | 12 +- packages/rest-typings/src/v1/statistics.ts | 6 +- packages/rest-typings/src/v1/teams/index.ts | 36 +-- packages/rest-typings/src/v1/users.ts | 21 +- .../rest-typings/src/v1/videoConference.ts | 2 +- packages/rest-typings/src/v1/voip.ts | 28 +-- packages/rest-typings/src/v1/webdav.ts | 2 +- .../src/ServerContext/ServerContext.ts | 5 +- packages/ui-contexts/src/hooks/useUpload.ts | 7 +- yarn.lock | 55 ++++ 303 files changed, 1598 insertions(+), 938 deletions(-) rename apps/meteor/app/api/server/v1/{emoji-custom.js => emoji-custom.ts} (69%) delete mode 100644 apps/meteor/app/ui/client/lib/fileUpload.js create mode 100644 apps/meteor/app/ui/client/lib/fileUpload.ts create mode 100644 apps/meteor/app/utils/client/lib/RestApiClient.ts rename apps/meteor/app/utils/client/lib/{RestApiClient.d.ts => _RestApiClient.d.ts} (74%) rename apps/meteor/app/utils/client/lib/{RestApiClient.js => _RestApiClient.js} (100%) create mode 100644 apps/meteor/definition/externals/meteor/session.d.ts create mode 100644 packages/api-client/.eslintrc create mode 100644 packages/api-client/package.json create mode 100644 packages/api-client/src/RestClientInterface.ts create mode 100644 packages/api-client/src/index.ts create mode 100644 packages/api-client/tsconfig.json create mode 100644 packages/rest-typings/src/v1/me.ts diff --git a/apps/meteor/app/api/server/lib/getUploadFormData.js b/apps/meteor/app/api/server/lib/getUploadFormData.js index 39b0e7436c13..553ebe0a27ba 100644 --- a/apps/meteor/app/api/server/lib/getUploadFormData.js +++ b/apps/meteor/app/api/server/lib/getUploadFormData.js @@ -9,7 +9,6 @@ export const getUploadFormData = async ({ request }) => bb.on('file', (fieldname, file, { filename, encoding, mimeType: mimetype }) => { const fileData = []; - console.log(file); file.on('data', (data) => fileData.push(data)); file.on('end', () => { diff --git a/apps/meteor/app/api/server/v1/emoji-custom.js b/apps/meteor/app/api/server/v1/emoji-custom.ts similarity index 69% rename from apps/meteor/app/api/server/v1/emoji-custom.js rename to apps/meteor/app/api/server/v1/emoji-custom.ts index 6a5fb2260e1b..fe16e3ed2392 100644 --- a/apps/meteor/app/api/server/v1/emoji-custom.js +++ b/apps/meteor/app/api/server/v1/emoji-custom.ts @@ -10,27 +10,29 @@ API.v1.addRoute( 'emoji-custom.list', { authRequired: true }, { - get() { + async get() { const { query } = this.parseJsonQuery(); const { updatedSince } = this.queryParams; - let updatedSinceDate; if (updatedSince) { + const updatedSinceDate = new Date(updatedSince); if (isNaN(Date.parse(updatedSince))) { throw new Meteor.Error('error-roomId-param-invalid', 'The "updatedSince" query parameter must be a valid date.'); - } else { - updatedSinceDate = new Date(updatedSince); } + const [update, remove] = await Promise.all([ + EmojiCustom.find({ ...query, _updatedAt: { $gt: updatedSinceDate } }).toArray(), + EmojiCustom.trashFindDeletedAfter(updatedSinceDate).toArray(), + ]); return API.v1.success({ emojis: { - update: Promise.await(EmojiCustom.find({ ...query, _updatedAt: { $gt: updatedSinceDate } }).toArray()), - remove: Promise.await(EmojiCustom.trashFindDeletedAfter(updatedSinceDate).toArray()), + update, + remove, }, }); } return API.v1.success({ emojis: { - update: Promise.await(EmojiCustom.find(query).toArray()), + update: await EmojiCustom.find(query).toArray(), remove: [], }, }); @@ -42,21 +44,19 @@ API.v1.addRoute( 'emoji-custom.all', { authRequired: true }, { - get() { + async get() { const { offset, count } = this.getPaginationItems(); const { sort, query } = this.parseJsonQuery(); return API.v1.success( - Promise.await( - findEmojisCustom({ - query, - pagination: { - offset, - count, - sort, - }, - }), - ), + await findEmojisCustom({ + query, + pagination: { + offset, + count, + sort, + }, + }), ); }, }, @@ -66,18 +66,16 @@ API.v1.addRoute( 'emoji-custom.create', { authRequired: true }, { - post() { - const { emoji, ...fields } = Promise.await( - getUploadFormData({ - request: this.request, - }), - ); + async post() { + const { emoji, ...fields } = await getUploadFormData({ + request: this.request, + }); if (!emoji) { throw new Meteor.Error('invalid-field'); } - const isUploadable = Promise.await(Media.isImage(emoji.fileBuffer)); + const isUploadable = await Media.isImage(emoji.fileBuffer); if (!isUploadable) { throw new Meteor.Error('emoji-is-not-image', "Emoji file provided cannot be uploaded since it's not an image"); } @@ -88,10 +86,10 @@ API.v1.addRoute( fields.newFile = true; fields.aliases = fields.aliases || ''; - Meteor.runAsUser(this.userId, () => { - Meteor.call('insertOrUpdateEmoji', fields); - Meteor.call('uploadEmojiCustom', emoji.fileBuffer, emoji.mimetype, fields); - }); + Meteor.call('insertOrUpdateEmoji', fields); + Meteor.call('uploadEmojiCustom', emoji.fileBuffer, emoji.mimetype, fields); + + return API.v1.success(); }, }, ); @@ -100,12 +98,10 @@ API.v1.addRoute( 'emoji-custom.update', { authRequired: true }, { - post() { - const { emoji, ...fields } = Promise.await( - getUploadFormData({ - request: this.request, - }), - ); + async post() { + const { emoji, ...fields } = await getUploadFormData({ + request: this.request, + }); if (!fields._id) { throw new Meteor.Error('The required "_id" query param is missing.'); @@ -133,12 +129,11 @@ API.v1.addRoute( fields.extension = emojiToUpdate.extension; } - Meteor.runAsUser(this.userId, () => { - Meteor.call('insertOrUpdateEmoji', fields); - if (fields.newFile) { - Meteor.call('uploadEmojiCustom', emoji.fileBuffer, emoji.mimetype, fields); - } - }); + Meteor.call('insertOrUpdateEmoji', fields); + if (fields.newFile) { + Meteor.call('uploadEmojiCustom', emoji.fileBuffer, emoji.mimetype, fields); + } + return API.v1.success(); }, }, ); @@ -153,7 +148,7 @@ API.v1.addRoute( return API.v1.failure('The "emojiId" params is required!'); } - Meteor.runAsUser(this.userId, () => Meteor.call('deleteEmojiCustom', emojiId)); + Meteor.call('deleteEmojiCustom', emojiId); return API.v1.success(); }, diff --git a/apps/meteor/app/api/server/v1/settings.ts b/apps/meteor/app/api/server/v1/settings.ts index f8bdb46c352f..f91c09d2166a 100644 --- a/apps/meteor/app/api/server/v1/settings.ts +++ b/apps/meteor/app/api/server/v1/settings.ts @@ -154,7 +154,7 @@ API.v1.addRoute( }, post: { twoFactorRequired: true, - async action(): Promise> { + async action(): Promise> { if (!hasPermission(this.userId, 'edit-privileged-setting')) { return API.v1.unauthorized(); } diff --git a/apps/meteor/app/apps/client/RealAppsEngineUIHost.js b/apps/meteor/app/apps/client/RealAppsEngineUIHost.js index c7d15fd467ef..6a73e7f38a7e 100644 --- a/apps/meteor/app/apps/client/RealAppsEngineUIHost.js +++ b/apps/meteor/app/apps/client/RealAppsEngineUIHost.js @@ -29,7 +29,7 @@ export class RealAppsEngineUIHost extends AppsEngineUIHost { let cachedMembers = []; try { - const { members } = await APIClient.get('v1/groups.members', { roomId: id }); + const { members } = await APIClient.get('/v1/groups.members', { roomId: id }); cachedMembers = members.map(({ _id, username }) => ({ id: _id, diff --git a/apps/meteor/app/apps/client/communication/websockets.js b/apps/meteor/app/apps/client/communication/websockets.js index 090fb059edaf..6c2707af230d 100644 --- a/apps/meteor/app/apps/client/communication/websockets.js +++ b/apps/meteor/app/apps/client/communication/websockets.js @@ -50,7 +50,7 @@ export class AppWebsocketReceiver extends Emitter { } onCommandAddedOrUpdated = (command) => { - APIClient.v1.get('commands.get', { command }).then((result) => { + APIClient.get('/v1/commands.get', { command }).then((result) => { slashCommands.commands[command] = result.command; }); }; diff --git a/apps/meteor/app/apps/client/gameCenter/gameCenter.js b/apps/meteor/app/apps/client/gameCenter/gameCenter.js index bd3e19669302..6486573f4974 100644 --- a/apps/meteor/app/apps/client/gameCenter/gameCenter.js +++ b/apps/meteor/app/apps/client/gameCenter/gameCenter.js @@ -8,7 +8,7 @@ import { handleError } from '../../../../client/lib/utils/handleError'; const getExternalComponents = async (instance) => { try { - const { externalComponents } = await APIClient.get('apps/externalComponents'); + const { externalComponents } = await APIClient.get('/apps/externalComponents'); instance.games.set(externalComponents); } catch (e) { handleError(e); diff --git a/apps/meteor/app/apps/client/gameCenter/gameContainer.js b/apps/meteor/app/apps/client/gameCenter/gameContainer.js index 7ee2285ee901..d954c06d1c25 100644 --- a/apps/meteor/app/apps/client/gameCenter/gameContainer.js +++ b/apps/meteor/app/apps/client/gameCenter/gameContainer.js @@ -62,7 +62,7 @@ Template.GameContainer.events({ Template.GameContainer.onCreated(async () => { const externalComponent = await getExternalComponent(); - APIClient.post('apps/externalComponentEvent', { + APIClient.post('/apps/externalComponentEvent', { event: 'IPostExternalComponentOpened', externalComponent, }); @@ -71,7 +71,7 @@ Template.GameContainer.onCreated(async () => { Template.GameContainer.onDestroyed(async () => { const externalComponent = await getExternalComponent(); - APIClient.post('apps/externalComponentEvent', { + APIClient.post('/apps/externalComponentEvent', { event: 'IPostExternalComponentClosed', externalComponent, }); diff --git a/apps/meteor/app/apps/client/orchestrator.ts b/apps/meteor/app/apps/client/orchestrator.ts index ecbe4c9ed818..0062d6206990 100644 --- a/apps/meteor/app/apps/client/orchestrator.ts +++ b/apps/meteor/app/apps/client/orchestrator.ts @@ -124,7 +124,7 @@ class AppClientOrchestrator { } public screenshots(appId: string): IAppScreenshots { - return APIClient.get(`apps/${appId}/screenshots`); + return APIClient.get(`/v1/apps/${appId}/screenshots`); } public isEnabled(): Promise | undefined { @@ -132,12 +132,12 @@ class AppClientOrchestrator { } public async getApps(): Promise { - const { apps } = await APIClient.get('apps'); + const { apps } = await APIClient.get('/v1/apps'); return apps; } public async getAppsFromMarketplace(): Promise { - const appsOverviews: IAppFromMarketplace[] = await APIClient.get('apps', { marketplace: 'true' }); + const appsOverviews: IAppFromMarketplace[] = await APIClient.get('/v1/apps', { marketplace: 'true' }); return appsOverviews.map((app: IAppFromMarketplace) => { const { latest, price, pricingPlans, purchaseType, isEnterpriseOnly, modifiedAt } = app; return { @@ -152,22 +152,22 @@ class AppClientOrchestrator { } public async getAppsOnBundle(bundleId: string): Promise { - const { apps } = await APIClient.get(`apps/bundles/${bundleId}/apps`); + const { apps } = await APIClient.get(`/v1/apps/bundles/${bundleId}/apps`); return apps; } public async getAppsLanguages(): Promise { - const { apps } = await APIClient.get('apps/languages'); + const { apps } = await APIClient.get('/v1/apps/languages'); return apps; } public async getApp(appId: string): Promise { - const { app } = await APIClient.get(`apps/${appId}`); + const { app } = await APIClient.get(`/v1/apps/${appId}`); return app; } public async getAppFromMarketplace(appId: string, version: string): Promise { - const { app } = await APIClient.get(`apps/${appId}`, { + const { app } = await APIClient.get(`/v1/apps/${appId}`, { marketplace: 'true', version, }); @@ -175,7 +175,7 @@ class AppClientOrchestrator { } public async getLatestAppFromMarketplace(appId: string, version: string): Promise { - const { app } = await APIClient.get(`apps/${appId}`, { + const { app } = await APIClient.get(`/v1/apps/${appId}`, { marketplace: 'true', update: 'true', appVersion: version, @@ -184,27 +184,27 @@ class AppClientOrchestrator { } public async getAppSettings(appId: string): Promise { - const { settings } = await APIClient.get(`apps/${appId}/settings`); + const { settings } = await APIClient.get(`/v1/apps/${appId}/settings`); return settings; } public async setAppSettings(appId: string, settings: ISettingsPayload): Promise { - const { updated } = await APIClient.post(`apps/${appId}/settings`, undefined, { settings }); + const { updated } = await APIClient.post(`/v1/apps/${appId}/settings`, undefined, { settings }); return updated; } public async getAppApis(appId: string): Promise { - const { apis } = await APIClient.get(`apps/${appId}/apis`); + const { apis } = await APIClient.get(`/v1/apps/${appId}/apis`); return apis; } public async getAppLanguages(appId: string): Promise { - const { languages } = await APIClient.get(`apps/${appId}/languages`); + const { languages } = await APIClient.get(`/v1/apps/${appId}/languages`); return languages; } public async installApp(appId: string, version: string, permissionsGranted: IPermission[]): Promise { - const { app } = await APIClient.post('apps/', { + const { app } = await APIClient.post('/v1/apps/', { appId, marketplace: true, version, @@ -214,7 +214,7 @@ class AppClientOrchestrator { } public async updateApp(appId: string, version: string, permissionsGranted: IPermission[]): Promise { - const { app } = await APIClient.post(`apps/${appId}`, { + const { app } = await APIClient.post(`/v1/apps/${appId}`, { appId, marketplace: true, version, @@ -228,11 +228,11 @@ class AppClientOrchestrator { } public syncApp(appId: string): IAppSynced { - return APIClient.post(`apps/${appId}/sync`); + return APIClient.post(`/v1/apps/${appId}/sync`); } public async setAppStatus(appId: string, status: AppStatus): Promise { - const { status: effectiveStatus } = await APIClient.post(`apps/${appId}/status`, { status }); + const { status: effectiveStatus } = await APIClient.post(`/v1/apps/${appId}/status`, { status }); return effectiveStatus; } @@ -245,7 +245,7 @@ class AppClientOrchestrator { } public buildExternalUrl(appId: string, purchaseType = 'buy', details = false): IAppExternalURL { - return APIClient.get('apps', { + return APIClient.get('/v1/apps', { buildExternalUrl: 'true', appId, purchaseType, @@ -254,7 +254,7 @@ class AppClientOrchestrator { } public async getCategories(): Promise { - const categories = await APIClient.get('apps', { categories: 'true' }); + const categories = await APIClient.get('/v1/apps', { categories: 'true' }); return categories; } diff --git a/apps/meteor/app/authorization/client/startup.js b/apps/meteor/app/authorization/client/startup.js index 7b1643224acd..6a7d79cb79cc 100644 --- a/apps/meteor/app/authorization/client/startup.js +++ b/apps/meteor/app/authorization/client/startup.js @@ -2,13 +2,13 @@ import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; import { CachedCollectionManager } from '../../ui-cached-collection'; -import { APIClient } from '../../utils/client'; +import { APIClient } from '../../utils/client/lib/RestApiClient'; import { Roles } from '../../models/client'; import { rolesStreamer } from './lib/streamer'; Meteor.startup(() => { CachedCollectionManager.onLogin(async () => { - const { roles } = await APIClient.v1.get('roles.list'); + const { roles } = await APIClient.get('/v1/roles.list'); // if a role is checked before this collection is populated, it will return undefined Roles._collection._docs._map = new Map(roles.map((record) => [Roles._collection._docs._idStringify(record._id), record])); Object.values(Roles._collection.queries).forEach((query) => Roles._collection._recomputeResults(query)); diff --git a/apps/meteor/app/authorization/server/methods/saveRole.ts b/apps/meteor/app/authorization/server/methods/saveRole.ts index 3b0517852865..5a4b485a5e15 100644 --- a/apps/meteor/app/authorization/server/methods/saveRole.ts +++ b/apps/meteor/app/authorization/server/methods/saveRole.ts @@ -13,16 +13,16 @@ Meteor.methods({ methodDeprecationLogger.warn('authorization:saveRole will be deprecated in future versions of Rocket.Chat'); const userId = Meteor.userId(); - if (!userId || !hasPermission(userId, 'access-permissions')) { - throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed', { + if (!isRoleCreateProps(roleData)) { + throw new Meteor.Error('error-invalid-role-properties', 'The role properties are invalid.', { method: 'authorization:saveRole', - action: 'Accessing_permissions', }); } - if (!isRoleCreateProps(roleData)) { - throw new Meteor.Error('error-invalid-role-properties', 'The role properties are invalid.', { + if (!userId || !hasPermission(userId, 'access-permissions')) { + throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed', { method: 'authorization:saveRole', + action: 'Accessing_permissions', }); } diff --git a/apps/meteor/app/emoji-custom/client/lib/emojiCustom.js b/apps/meteor/app/emoji-custom/client/lib/emojiCustom.js index 46cb53fcbf56..a3df7135cc5f 100644 --- a/apps/meteor/app/emoji-custom/client/lib/emojiCustom.js +++ b/apps/meteor/app/emoji-custom/client/lib/emojiCustom.js @@ -184,7 +184,7 @@ Meteor.startup(() => try { const { emojis: { update: emojis }, - } = await APIClient.v1.get('emoji-custom.list'); + } = await APIClient.get('/v1/emoji-custom.list'); emoji.packages.emojiCustom.emojisByCategory = { rocket: [] }; for (const currentEmoji of emojis) { diff --git a/apps/meteor/app/livechat/client/lib/stream/queueManager.js b/apps/meteor/app/livechat/client/lib/stream/queueManager.js index f76e5679d279..1a17c2169b30 100644 --- a/apps/meteor/app/livechat/client/lib/stream/queueManager.js +++ b/apps/meteor/app/livechat/client/lib/stream/queueManager.js @@ -45,7 +45,7 @@ const updateCollection = (inquiry) => { }; const getInquiriesFromAPI = async () => { - const { inquiries } = await APIClient.v1.get('livechat/inquiries.queuedForUser?sort={"ts": 1}'); + const { inquiries } = await APIClient.get('/v1/livechat/inquiries.queuedForUser?sort={"ts": 1}'); return inquiries; }; @@ -68,7 +68,7 @@ const updateInquiries = async (inquiries = []) => inquiries.forEach((inquiry) => LivechatInquiry.upsert({ _id: inquiry._id }, { ...inquiry, _updatedAt: new Date(inquiry._updatedAt) })); const getAgentsDepartments = async (userId) => { - const { departments } = await APIClient.v1.get(`livechat/agents/${userId}/departments?enabledDepartmentsOnly=true`); + const { departments } = await APIClient.get(`/v1/livechat/agents/${userId}/departments?enabledDepartmentsOnly=true`); return departments; }; diff --git a/apps/meteor/app/livechat/client/views/app/dialog/closeRoom.js b/apps/meteor/app/livechat/client/views/app/dialog/closeRoom.js index 3db0d930cfc9..779b7e9f5938 100644 --- a/apps/meteor/app/livechat/client/views/app/dialog/closeRoom.js +++ b/apps/meteor/app/livechat/client/views/app/dialog/closeRoom.js @@ -167,16 +167,16 @@ Template.closeRoom.onCreated(async function () { this.onEnterTag = () => this.invalidTags.set(!validateRoomTags(this.tagsRequired.get(), this.tags.get())); const { rid } = Template.currentData(); - const { room } = await APIClient.v1.get(`rooms.info?roomId=${rid}`); + const { room } = await APIClient.get(`/v1/rooms.info?roomId=${rid}`); this.tags.set(room?.tags || []); if (room?.departmentId) { - const { department } = await APIClient.v1.get(`livechat/department/${room.departmentId}?includeAgents=false`); + const { department } = await APIClient.get(`/v1/livechat/department/${room.departmentId}?includeAgents=false`); this.tagsRequired.set(department?.requestTagBeforeClosingChat); } const uid = Meteor.userId(); - const { departments } = await APIClient.v1.get(`livechat/agents/${uid}/departments`); + const { departments } = await APIClient.get(`/v1/livechat/agents/${uid}/departments`); const agentDepartments = departments.map((dept) => dept.departmentId); this.agentDepartments.set(agentDepartments); diff --git a/apps/meteor/app/livechat/client/views/app/livechatReadOnly.js b/apps/meteor/app/livechat/client/views/app/livechatReadOnly.js index fa38742ca152..a42d81f029bb 100644 --- a/apps/meteor/app/livechat/client/views/app/livechatReadOnly.js +++ b/apps/meteor/app/livechat/client/views/app/livechatReadOnly.js @@ -61,7 +61,7 @@ Template.livechatReadOnly.events({ event.stopPropagation(); try { - const { success } = (await APIClient.v1.get(`livechat/room.join?roomId=${this.rid}`)) || {}; + const { success } = (await APIClient.get(`/v1/livechat/room.join?roomId=${this.rid}`)) || {}; if (!success) { throw new Meteor.Error('error-join-room', 'Error joining room'); } @@ -99,13 +99,13 @@ Template.livechatReadOnly.onCreated(async function () { this.loadRoomAndInquiry = async (roomId) => { this.preparing.set(true); - const { inquiry } = await APIClient.v1.get(`livechat/inquiries.getOne?roomId=${roomId}`); + const { inquiry } = await APIClient.get(`/v1/livechat/inquiries.getOne?roomId=${roomId}`); this.inquiry.set(inquiry); if (inquiry && inquiry._id) { inquiryDataStream.on(inquiry._id, this.updateInquiry); } - const { room } = await APIClient.v1.get(`rooms.info?roomId=${roomId}`); + const { room } = await APIClient.get(`/v1/rooms.info?roomId=${roomId}`); this.room.set(room); if (room && room._id) { RoomManager.roomStream.on(roomId, (room) => this.room.set(room)); diff --git a/apps/meteor/app/livechat/client/views/app/tabbar/agentEdit.js b/apps/meteor/app/livechat/client/views/app/tabbar/agentEdit.js index ff14df8f7832..c1fef6d7fd76 100644 --- a/apps/meteor/app/livechat/client/views/app/tabbar/agentEdit.js +++ b/apps/meteor/app/livechat/client/views/app/tabbar/agentEdit.js @@ -133,7 +133,7 @@ Template.agentEdit.onCreated(async function () { this.availableDepartments = new ReactiveVar([]); this.back = Template.currentData().back; - const { departments } = await APIClient.v1.get('livechat/department?sort={"name": 1}'); + const { departments } = await APIClient.get('/v1/livechat/department?sort={"name": 1}'); this.departments.set(departments); this.availableDepartments.set(departments.filter(({ enabled }) => enabled)); @@ -146,8 +146,8 @@ Template.agentEdit.onCreated(async function () { return; } - const { user } = await APIClient.v1.get(`livechat/users/agent/${agentId}`); - const { departments } = await APIClient.v1.get(`livechat/agents/${agentId}/departments`); + const { user } = await APIClient.get(`/v1/livechat/users/agent/${agentId}`); + const { departments } = await APIClient.get(`/v1/livechat/agents/${agentId}/departments`); this.agent.set(user); this.agentDepartments.set((departments || []).map((department) => department.departmentId)); this.ready.set(true); diff --git a/apps/meteor/app/livechat/client/views/app/tabbar/agentInfo.js b/apps/meteor/app/livechat/client/views/app/tabbar/agentInfo.js index af15c9ba3f7e..725a2ec0dfea 100644 --- a/apps/meteor/app/livechat/client/views/app/tabbar/agentInfo.js +++ b/apps/meteor/app/livechat/client/views/app/tabbar/agentInfo.js @@ -153,14 +153,14 @@ Template.agentInfo.onCreated(async function () { this.tabBar = Template.currentData().tabBar; this.onRemoveAgent = Template.currentData().onRemoveAgent; - const { departments } = await APIClient.v1.get('livechat/department?sort={"name": 1}'); + const { departments } = await APIClient.get('/v1/livechat/department?sort={"name": 1}'); this.departments.set(departments); this.availableDepartments.set(departments.filter(({ enabled }) => enabled)); const loadAgentData = async (agentId) => { this.ready.set(false); - const { user } = await APIClient.v1.get(`livechat/users/agent/${agentId}`); - const { departments } = await APIClient.v1.get(`livechat/agents/${agentId}/departments`); + const { user } = await APIClient.get(`/v1/livechat/users/agent/${agentId}`); + const { departments } = await APIClient.get(`/v1/livechat/agents/${agentId}/departments`); this.agent.set(user); this.agentDepartments.set((departments || []).map((department) => department.departmentId)); this.ready.set(true); diff --git a/apps/meteor/app/livechat/client/views/app/tabbar/contactChatHistory.js b/apps/meteor/app/livechat/client/views/app/tabbar/contactChatHistory.js index 9dcd6d690ba1..10e05932b3fb 100644 --- a/apps/meteor/app/livechat/client/views/app/tabbar/contactChatHistory.js +++ b/apps/meteor/app/livechat/client/views/app/tabbar/contactChatHistory.js @@ -70,7 +70,7 @@ Template.contactChatHistory.onCreated(async function () { const offset = this.offset.get(); const searchTerm = this.searchTerm.get(); - let baseUrl = `livechat/visitors.searchChats/room/${ + let baseUrl = `/v1/livechat/visitors.searchChats/room/${ currentData.rid }/visitor/${this.visitorId.get()}?count=${limit}&offset=${offset}&closedChatsOnly=true&servedChatsOnly=true`; if (searchTerm) { @@ -78,14 +78,14 @@ Template.contactChatHistory.onCreated(async function () { } this.isLoading.set(true); - const { history, total } = await APIClient.v1.get(baseUrl); + const { history, total } = await APIClient.get(baseUrl); this.history.set(offset === 0 ? history : this.history.get().concat(history)); this.hasMore.set(total > this.history.get().length); this.isLoading.set(false); }); this.autorun(async () => { - const { room } = await APIClient.v1.get(`rooms.info?roomId=${currentData.rid}`); + const { room } = await APIClient.get(`/v1/rooms.info?roomId=${currentData.rid}`); if (room?.v) { this.visitorId.set(room.v._id); } diff --git a/apps/meteor/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.js b/apps/meteor/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.js index 2b668732fc5c..f00adaf28354 100644 --- a/apps/meteor/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.js +++ b/apps/meteor/app/livechat/client/views/app/tabbar/contactChatHistoryMessages.js @@ -81,12 +81,12 @@ Template.contactChatHistoryMessages.onCreated(function () { this.hasError = new ReactiveVar(false); this.error = new ReactiveVar(null); - this.loadMessages = async (url) => { + this.loadMessages = async (url, params) => { this.isLoading.set(true); const offset = this.offset.get(); try { - const { messages, total } = await APIClient.v1.get(url); + const { messages, total } = await APIClient.get(url, params); this.messages.set(offset === 0 ? messages : this.messages.get().concat(messages)); this.hasMore.set(total > this.messages.get().length); } catch (e) { @@ -103,10 +103,20 @@ Template.contactChatHistoryMessages.onCreated(function () { const searchTerm = this.searchTerm.get(); if (searchTerm !== '') { - return this.loadMessages(`chat.search/?roomId=${this.rid}&searchText=${searchTerm}&count=${limit}&offset=${offset}&sort={"ts": 1}`); + return this.loadMessages('/v1/chat.search', { + roomId: this.rid, + searchText: searchTerm, + count: limit, + offset, + sort: '{"ts": 1}', + }); } - this.loadMessages(`livechat/${this.rid}/messages?count=${limit}&offset=${offset}&sort={"ts": 1}`); + this.loadMessages(`/v1/livechat/${this.rid}/messages`, { + count: limit, + offset, + sort: '{"ts": 1}', + }); }); this.autorun(() => { diff --git a/apps/meteor/app/livechat/client/views/app/tabbar/visitorEdit.js b/apps/meteor/app/livechat/client/views/app/tabbar/visitorEdit.js index ce70d5e979b8..46f4f0cc2af4 100644 --- a/apps/meteor/app/livechat/client/views/app/tabbar/visitorEdit.js +++ b/apps/meteor/app/livechat/client/views/app/tabbar/visitorEdit.js @@ -114,7 +114,7 @@ Template.visitorEdit.onCreated(async function () { this.autorun(async () => { const { visitorId } = Template.currentData(); if (visitorId) { - const { visitor } = await APIClient.v1.get(`livechat/visitors.info?visitorId=${visitorId}`); + const { visitor } = await APIClient.get('/v1/livechat/visitors.info', { visitorId }); this.visitor.set(visitor); } }); @@ -122,15 +122,15 @@ Template.visitorEdit.onCreated(async function () { const rid = Template.currentData().roomId; this.autorun(async () => { - const { room } = await APIClient.v1.get(`rooms.info?roomId=${rid}`); - const { customFields } = await APIClient.v1.get(`livechat/custom-fields?count=${CUSTOM_FIELDS_COUNT}`); + const { room } = await APIClient.get('/v1/rooms.info', { roomId: rid }); + const { customFields } = await APIClient.get('/v1/livechat/custom-fields', { count: CUSTOM_FIELDS_COUNT }); this.room.set(room); this.tags.set((room && room.tags) || []); this.customFields.set(customFields || []); }); const uid = Meteor.userId(); - const { departments } = await APIClient.v1.get(`livechat/agents/${uid}/departments`); + const { departments } = await APIClient.get(`/v1/livechat/agents/${uid}/departments`); const agentDepartments = departments.map((dept) => dept.departmentId); this.agentDepartments.set(agentDepartments); Meteor.call('livechat:getTagsList', (err, tagsList) => { diff --git a/apps/meteor/app/livechat/client/views/app/tabbar/visitorForward.html b/apps/meteor/app/livechat/client/views/app/tabbar/visitorForward.html index 45c909a10ded..82ce65ae4706 100644 --- a/apps/meteor/app/livechat/client/views/app/tabbar/visitorForward.html +++ b/apps/meteor/app/livechat/client/views/app/tabbar/visitorForward.html @@ -3,34 +3,20 @@

{{_ "Forward_chat"}}

{{#with visitor}} -
- - {{username}} -
+
+ + {{username}} +
{{/with}}
- {{> livechatAutocompleteUser - onClickTag=onClickTagDepartment - list=selectedDepartments - onSelect=onSelectDepartments - collection='CachedDepartmentList' - endpoint='livechat/department.autocomplete' - field='name' - sort='name' - icon="queue" - label="Enter_a_department_name" - placeholder="Enter_a_department_name" - name="department" - noMatchTemplate="userSearchEmpty" - templateItem="popupList_item_channel" - template="roomSearch" - noMatchTemplate="roomSearchEmpty" - modifier=departmentModifier - conditions=departmentConditions - }} + {{> livechatAutocompleteUser onClickTag=onClickTagDepartment list=selectedDepartments onSelect=onSelectDepartments + collection='CachedDepartmentList' endpoint='livechat/department.autocomplete' field='name' sort='name' icon="queue" + label="Enter_a_department_name" placeholder="Enter_a_department_name" name="department" noMatchTemplate="userSearchEmpty" + templateItem="popupList_item_channel" template="roomSearch" noMatchTemplate="roomSearchEmpty" modifier=departmentModifier + conditions=departmentConditions }}
@@ -40,23 +26,10 @@

{{_ "Forward_chat"}}

- {{> livechatAutocompleteUser - onClickTag=onClickTagAgent - list=selectedAgents - onSelect=onSelectAgents - collection='UserAndRoom' - endpoint='users.autocomplete' - field='username' - sort='username' - label="Select_a_user" - placeholder="Select_a_user" - name="agent" - icon="at" - noMatchTemplate="userSearchEmpty" - templateItem="popupList_item_default" - modifier=agentModifier - conditions=agentConditions - }} + {{> livechatAutocompleteUser onClickTag=onClickTagAgent list=selectedAgents onSelect=onSelectAgents collection='UserAndRoom' + endpoint='/v1/users.autocomplete' field='username' sort='username' label="Select_a_user" placeholder="Select_a_user" + name="agent" icon="at" noMatchTemplate="userSearchEmpty" templateItem="popupList_item_default" modifier=agentModifier + conditions=agentConditions }}
@@ -66,8 +39,8 @@

{{_ "Forward_chat"}}

diff --git a/apps/meteor/app/livechat/client/views/app/tabbar/visitorForward.js b/apps/meteor/app/livechat/client/views/app/tabbar/visitorForward.js index b591ed84e52d..8e6c407bc03a 100644 --- a/apps/meteor/app/livechat/client/views/app/tabbar/visitorForward.js +++ b/apps/meteor/app/livechat/client/views/app/tabbar/visitorForward.js @@ -99,7 +99,7 @@ Template.visitorForward.onCreated(async function () { } }); - const { departments } = await APIClient.v1.get('livechat/department?enabled=true'); + const { departments } = await APIClient.get('/v1/livechat/department', { enabled: true }); this.departments.set(departments); }); diff --git a/apps/meteor/app/livechat/client/views/app/tabbar/visitorInfo.js b/apps/meteor/app/livechat/client/views/app/tabbar/visitorInfo.js index 4a5d71e94c14..475987d158b2 100644 --- a/apps/meteor/app/livechat/client/views/app/tabbar/visitorInfo.js +++ b/apps/meteor/app/livechat/client/views/app/tabbar/visitorInfo.js @@ -355,7 +355,7 @@ Template.visitorInfo.events({ confirmButtonText: t('Yes'), }, async () => { - const { success } = await APIClient.v1.post('livechat/room.onHold', { roomId: this.rid }); + const { success } = await APIClient.post('/v1/livechat/room.onHold', { roomId: this.rid }); if (success) { modal.open({ title: t('Chat_On_Hold'), @@ -382,7 +382,7 @@ Template.visitorInfo.onCreated(function () { this.room = new ReactiveVar({}); this.updateVisitor = async (visitorId) => { - const { visitor } = await APIClient.v1.get(`livechat/visitors.info?visitorId=${visitorId}`); + const { visitor } = await APIClient.get('/v1/livechat/visitors.info', { visitorId }); this.user.set(visitor); }; @@ -409,7 +409,7 @@ Template.visitorInfo.onCreated(function () { }); const loadRoomData = async (rid) => { - const { room } = await APIClient.v1.get(`rooms.info?roomId=${rid}`); + const { room } = await APIClient.get('/v1/rooms.info', { roomId: rid }); this.updateRoom(room); }; @@ -420,7 +420,7 @@ Template.visitorInfo.onCreated(function () { this.autorun(async () => { if (this.departmentId.get()) { - const { department } = await APIClient.v1.get(`livechat/department/${this.departmentId.get()}?includeAgents=false`); + const { department } = await APIClient.get(`/v1/livechat/department/${this.departmentId.get()}`, { includeAgents: false }); this.department.set(department); } }); diff --git a/apps/meteor/app/livechat/client/views/app/tabbar/visitorNavigation.js b/apps/meteor/app/livechat/client/views/app/tabbar/visitorNavigation.js index 3142aa92a423..3f5a9a77d55e 100644 --- a/apps/meteor/app/livechat/client/views/app/tabbar/visitorNavigation.js +++ b/apps/meteor/app/livechat/client/views/app/tabbar/visitorNavigation.js @@ -65,9 +65,10 @@ Template.visitorNavigation.onCreated(async function () { this.isLoading.set(true); const offset = this.offset.get(); if (currentData && currentData.rid) { - const { pages, total } = await APIClient.v1.get( - `livechat/visitors.pagesVisited/${currentData.rid}?count=${ITEMS_COUNT}&offset=${offset}`, - ); + const { pages, total } = await APIClient.get(`/v1/livechat/visitors.pagesVisited/${currentData.rid}`, { + count: ITEMS_COUNT, + offset, + }); this.isLoading.set(false); this.total.set(total); this.pages.set(this.pages.get().concat(pages)); diff --git a/apps/meteor/app/livechat/client/views/app/tabbar/visitorTranscript.js b/apps/meteor/app/livechat/client/views/app/tabbar/visitorTranscript.js index de70f61347f2..94f107319e9c 100644 --- a/apps/meteor/app/livechat/client/views/app/tabbar/visitorTranscript.js +++ b/apps/meteor/app/livechat/client/views/app/tabbar/visitorTranscript.js @@ -169,12 +169,12 @@ Template.visitorTranscript.onCreated(async function () { this.infoMessage = new ReactiveVar(''); this.autorun(async () => { - const { visitor } = await APIClient.v1.get(`livechat/visitors.info?visitorId=${Template.currentData().visitorId}`); + const { visitor } = await APIClient.get('/v1/livechat/visitors.info', { visitorId: Template.currentData().visitorId }); this.visitor.set(visitor); }); this.autorun(async () => { - const { room } = await APIClient.v1.get(`rooms.info?roomId=${Template.currentData().roomId}`); + const { room } = await APIClient.get('/v1/rooms.info', { roomId: Template.currentData().roomId }); this.room.set(room); if (room?.transcriptRequest) { diff --git a/apps/meteor/app/mentions-flextab/client/views/mentionsFlexTab.js b/apps/meteor/app/mentions-flextab/client/views/mentionsFlexTab.js index 97fd7e1c5659..92b10b8a57d0 100644 --- a/apps/meteor/app/mentions-flextab/client/views/mentionsFlexTab.js +++ b/apps/meteor/app/mentions-flextab/client/views/mentionsFlexTab.js @@ -61,7 +61,10 @@ Template.mentionsFlexTab.onCreated(function () { this.autorun(async () => { const limit = this.limit.get(); - const { messages, total } = await APIClient.v1.get(`chat.getMentionedMessages?roomId=${this.data.rid}&count=${limit}`); + const { messages, total } = await APIClient.get('/v1/chat.getMentionedMessages', { + roomId: this.data.rid, + count: limit, + }); upsertMessageBulk({ msgs: messages }, this.messages); diff --git a/apps/meteor/app/message-pin/client/views/pinnedMessages.js b/apps/meteor/app/message-pin/client/views/pinnedMessages.js index 104777b58eee..75fc482f913c 100644 --- a/apps/meteor/app/message-pin/client/views/pinnedMessages.js +++ b/apps/meteor/app/message-pin/client/views/pinnedMessages.js @@ -62,7 +62,10 @@ Template.pinnedMessages.onCreated(function () { this.autorun(async () => { const limit = this.limit.get(); - const { messages, total } = await APIClient.v1.get(`chat.getPinnedMessages?roomId=${this.rid}&count=${limit}`); + const { messages, total } = await APIClient.get('/v1/chat.getPinnedMessages', { + roomId: this.rid, + count: limit, + }); upsertMessageBulk({ msgs: messages }, this.messages); diff --git a/apps/meteor/app/message-snippet/client/page/snippetPage.js b/apps/meteor/app/message-snippet/client/page/snippetPage.js index 48e2706302aa..0cd662305e7d 100644 --- a/apps/meteor/app/message-snippet/client/page/snippetPage.js +++ b/apps/meteor/app/message-snippet/client/page/snippetPage.js @@ -39,6 +39,6 @@ Template.snippetPage.onCreated(async function () { const snippetId = FlowRouter.getParam('snippetId'); this.message = new ReactiveVar({}); - const { message } = await APIClient.v1.get(`chat.getSnippetedMessageById?messageId=${snippetId}`); + const { message } = await APIClient.get('/v1/chat.getSnippetedMessageById', { messageId: snippetId }); this.message.set(message); }); diff --git a/apps/meteor/app/message-snippet/client/tabBar/views/snippetedMessages.js b/apps/meteor/app/message-snippet/client/tabBar/views/snippetedMessages.js index 0dbefc6c01bb..4ee7fee88472 100644 --- a/apps/meteor/app/message-snippet/client/tabBar/views/snippetedMessages.js +++ b/apps/meteor/app/message-snippet/client/tabBar/views/snippetedMessages.js @@ -56,7 +56,10 @@ Template.snippetedMessages.onCreated(function () { this.autorun(async () => { const limit = this.limit.get(); - const { messages, total } = await APIClient.v1.get(`chat.getSnippetedMessages?roomId=${this.rid}&count=${limit}`); + const { messages, total } = await APIClient.get('/v1/chat.getSnippetedMessages', { + roomId: this.rid, + count: limit, + }); upsertMessageBulk({ msgs: messages }, this.messages); diff --git a/apps/meteor/app/message-star/client/views/starredMessages.js b/apps/meteor/app/message-star/client/views/starredMessages.js index a0906d92b604..8b44b85e3f7f 100644 --- a/apps/meteor/app/message-star/client/views/starredMessages.js +++ b/apps/meteor/app/message-star/client/views/starredMessages.js @@ -61,7 +61,10 @@ Template.starredMessages.onCreated(function () { this.autorun(async () => { const limit = this.limit.get(); - const { messages, total } = await APIClient.v1.get(`chat.getStarredMessages?roomId=${this.rid}&count=${limit}`); + const { messages, total } = await APIClient.get('/v1/chat.getStarredMessages', { + roomId: this.rid, + count: limit, + }); upsertMessageBulk({ msgs: messages }, this.messages); diff --git a/apps/meteor/app/meteor-autocomplete/client/autocomplete-client.js b/apps/meteor/app/meteor-autocomplete/client/autocomplete-client.js index ee50a510cbca..55db42563b26 100755 --- a/apps/meteor/app/meteor-autocomplete/client/autocomplete-client.js +++ b/apps/meteor/app/meteor-autocomplete/client/autocomplete-client.js @@ -131,7 +131,7 @@ export default class AutoComplete { // console.debug 'Subscribing to <%s> in <%s>.<%s>', filter, rule.collection, rule.field this.setLoaded(false); const endpointName = rule.endpoint || 'users.autocomplete'; - const { items } = await APIClient.v1.get(`${endpointName}?selector=${JSON.stringify(selector)}`); + const { items } = await APIClient.get(`/v1/${endpointName}`, { selector: JSON.stringify(selector) }); AutoCompleteRecords.remove({}); items.forEach((item) => AutoCompleteRecords.insert(item)); this.setLoaded(true); diff --git a/apps/meteor/app/models/server/raw/EmojiCustom.ts b/apps/meteor/app/models/server/raw/EmojiCustom.ts index 30186cbdd3e4..0a8005cb379f 100644 --- a/apps/meteor/app/models/server/raw/EmojiCustom.ts +++ b/apps/meteor/app/models/server/raw/EmojiCustom.ts @@ -43,7 +43,7 @@ export class EmojiCustomRaw extends BaseRaw { return this.updateOne({ _id }, update); } - setAliases(_id: string, aliases: string): Promise { + setAliases(_id: string, aliases: string[]): Promise { const update = { $set: { aliases, diff --git a/apps/meteor/app/oauth2-server-config/client/oauth/oauth2-client.js b/apps/meteor/app/oauth2-server-config/client/oauth/oauth2-client.js index 47e5f0e958d9..55d7162d7f3a 100644 --- a/apps/meteor/app/oauth2-server-config/client/oauth/oauth2-client.js +++ b/apps/meteor/app/oauth2-server-config/client/oauth/oauth2-client.js @@ -7,7 +7,7 @@ import { APIClient } from '../../../utils/client'; Template.authorize.onCreated(async function () { this.oauthApp = new ReactiveVar({}); - const { oauthApp } = await APIClient.v1.get(`oauth-apps.get?clientId=${this.data.client_id()}`); + const { oauthApp } = await APIClient.get(`/v1/oauth-apps.get?clientId=${this.data.client_id()}`); this.oauthApp.set(oauthApp); }); diff --git a/apps/meteor/app/otr/client/rocketchat.otr.room.js b/apps/meteor/app/otr/client/rocketchat.otr.room.js index 95b57ad70b78..cf9dcd37308d 100644 --- a/apps/meteor/app/otr/client/rocketchat.otr.room.js +++ b/apps/meteor/app/otr/client/rocketchat.otr.room.js @@ -62,7 +62,7 @@ OTR.Room = class { } acknowledge() { - APIClient.v1.post('statistics.telemetry', { params: [{ eventName: 'otrStats', timestamp: Date.now(), rid: this.roomId }] }); + APIClient.post('/v1/statistics.telemetry', { params: [{ eventName: 'otrStats', timestamp: Date.now(), rid: this.roomId }] }); Notifications.notifyUser(this.peerId, 'otr', 'acknowledge', { roomId: this.roomId, diff --git a/apps/meteor/app/ui-message/client/ActionButtonSyncer.ts b/apps/meteor/app/ui-message/client/ActionButtonSyncer.ts index 7410fa2deeca..0ec6f94d7fb0 100644 --- a/apps/meteor/app/ui-message/client/ActionButtonSyncer.ts +++ b/apps/meteor/app/ui-message/client/ActionButtonSyncer.ts @@ -46,7 +46,7 @@ export const removeButton = (button: IUIActionButton): void => { }; export const loadButtons = (): Promise => - APIClient.get('apps/actionButtons').then((value: Array) => { + APIClient.get('/apps/actionButtons').then((value) => { registeredButtons.forEach((button) => removeButton(button)); registeredButtons = []; value.map(addButton); diff --git a/apps/meteor/app/ui-message/client/ActionManager.js b/apps/meteor/app/ui-message/client/ActionManager.js index 1d422855dd73..f172a412dacd 100644 --- a/apps/meteor/app/ui-message/client/ActionManager.js +++ b/apps/meteor/app/ui-message/client/ActionManager.js @@ -8,10 +8,9 @@ import { UIKitInteractionTypes } from '@rocket.chat/core-typings'; import Notifications from '../../notifications/client/lib/Notifications'; import { CachedCollectionManager } from '../../ui-cached-collection'; import { modal } from '../../ui-utils/client/lib/modal'; -import { APIClient } from '../../utils'; +import { APIClient, t } from '../../utils/client'; import * as banners from '../../../client/lib/banners'; import { dispatchToastMessage } from '../../../client/lib/toast'; -import { t } from '../../utils/client'; const events = new Emitter(); @@ -169,7 +168,7 @@ export const triggerAction = async ({ type, actionId, appId, rid, mid, viewId, c setTimeout(reject, TRIGGER_TIMEOUT, [TRIGGER_TIMEOUT_ERROR, { triggerId, appId }]); - const { type: interactionType, ...data } = await APIClient.post(`apps/ui.interaction/${appId}`, { + const { type: interactionType, ...data } = await APIClient.post(`/apps/ui.interaction/${appId}`, { type, actionId, payload, diff --git a/apps/meteor/app/ui/client/lib/chatMessages.js b/apps/meteor/app/ui/client/lib/chatMessages.js index 145ef57d03dc..4dc81166f21e 100644 --- a/apps/meteor/app/ui/client/lib/chatMessages.js +++ b/apps/meteor/app/ui/client/lib/chatMessages.js @@ -428,7 +428,7 @@ export class ChatMessages { if (commandOptions.clientOnly) { commandOptions.callback(command, param, msgObject); } else { - APIClient.v1.post('statistics.telemetry', { params: [{ eventName: 'slashCommandsStats', timestamp: Date.now(), command }] }); + APIClient.post('/v1/statistics.telemetry', { params: [{ eventName: 'slashCommandsStats', timestamp: Date.now(), command }] }); const triggerId = generateTriggerId(slashCommands.commands[command].appId); Meteor.call('slashCommand', { cmd: command, params: param, msg: msgObject, triggerId }, (err, result) => { typeof commandOptions.result === 'function' && diff --git a/apps/meteor/app/ui/client/lib/fileUpload.js b/apps/meteor/app/ui/client/lib/fileUpload.js deleted file mode 100644 index 179bf5e5fd44..000000000000 --- a/apps/meteor/app/ui/client/lib/fileUpload.js +++ /dev/null @@ -1,164 +0,0 @@ -import { Tracker } from 'meteor/tracker'; -import { Session } from 'meteor/session'; -import { Random } from 'meteor/random'; -import { Meteor } from 'meteor/meteor'; - -import { settings } from '../../../settings/client'; -import { UserAction, USER_ACTIVITIES } from '../index'; -import { fileUploadIsValidContentType, APIClient } from '../../../utils'; -import { imperativeModal } from '../../../../client/lib/imperativeModal'; -import FileUploadModal from '../../../../client/views/room/modals/FileUploadModal'; -import { prependReplies } from '../../../../client/lib/utils/prependReplies'; -import { chatMessages } from '../views/app/room'; - -export const uploadFileWithMessage = async (rid, tmid, { description, fileName, msg, file }) => { - const data = new FormData(); - description && data.append('description', description); - msg && data.append('msg', msg); - tmid && data.append('tmid', tmid); - data.append('file', file.file, fileName); - - const uploads = Session.get('uploading') || []; - - const upload = { - id: Random.id(), - name: fileName, - percentage: 0, - }; - - uploads.push(upload); - Session.set('uploading', uploads); - - const { xhr, promise } = APIClient.upload(`v1/rooms.upload/${rid}`, {}, data, { - progress(progress) { - const uploads = Session.get('uploading') || []; - - if (progress === 100) { - return; - } - uploads - .filter((u) => u.id === upload.id) - .forEach((u) => { - u.percentage = Math.round(progress) || 0; - }); - Session.set('uploading', uploads); - }, - error(error) { - const uploads = Session.get('uploading') || []; - uploads - .filter((u) => u.id === upload.id) - .forEach((u) => { - u.error = error.message; - u.percentage = 0; - }); - Session.set('uploading', uploads); - }, - }); - if (Session.get('uploading').length) { - UserAction.performContinuously(rid, USER_ACTIVITIES.USER_UPLOADING, { tmid }); - } - - Tracker.autorun((computation) => { - const isCanceling = Session.get(`uploading-cancel-${upload.id}`); - if (!isCanceling) { - return; - } - computation.stop(); - Session.delete(`uploading-cancel-${upload.id}`); - - xhr.abort(); - - const uploads = Session.get('uploading') || {}; - Session.set( - 'uploading', - uploads.filter((u) => u.id !== upload.id), - ); - }); - - try { - await promise; - const uploads = Session.get('uploading') || []; - const remainingUploads = Session.set( - 'uploading', - uploads.filter((u) => u.id !== upload.id), - ); - - if (!Session.get('uploading').length) { - UserAction.stop(rid, USER_ACTIVITIES.USER_UPLOADING, { tmid }); - } - return remainingUploads; - } catch (error) { - const uploads = Session.get('uploading') || []; - uploads - .filter((u) => u.id === upload.id) - .forEach((u) => { - u.error = (error.xhr && error.xhr.responseJSON && error.xhr.responseJSON.error) || error.message; - u.percentage = 0; - }); - if (!uploads.length) { - UserAction.stop(rid, USER_ACTIVITIES.USER_UPLOADING, { tmid }); - } - Session.set('uploading', uploads); - } -}; - -export const fileUpload = async (files, input, { rid, tmid }) => { - const threadsEnabled = settings.get('Threads_enabled'); - - files = [].concat(files); - - const replies = $(input).data('reply') || []; - const mention = $(input).data('mention-user') || false; - - let msg = ''; - - if (!mention || !threadsEnabled) { - msg = await prependReplies('', replies, mention); - } - - if (mention && threadsEnabled && replies.length) { - tmid = replies[0]._id; - } - - const key = ['messagebox', rid, tmid].filter(Boolean).join('_'); - const messageBoxText = Meteor._localStorage.getItem(key) || ''; - - const uploadNextFile = () => { - const file = files.pop(); - if (!file) { - return; - } - - imperativeModal.open({ - component: FileUploadModal, - props: { - file: file.file, - fileName: file.name, - fileDescription: messageBoxText, - onClose: () => { - imperativeModal.close(); - uploadNextFile(); - }, - onSubmit: (fileName, description) => { - uploadFileWithMessage(rid, tmid, { - description, - fileName, - msg: msg || undefined, - file, - }); - const localStorageKey = ['messagebox', rid, tmid].filter(Boolean).join('_'); - const chatMessageKey = [rid, tmid].filter(Boolean).join('-'); - const { input } = chatMessages[chatMessageKey]; - input.value = null; - $(input).trigger('input'); - Meteor._localStorage.removeItem(localStorageKey); - imperativeModal.close(); - uploadNextFile(); - }, - invalidContentType: file.file.type && !fileUploadIsValidContentType(file.file.type), - }, - }); - }; - - uploadNextFile(); -}; diff --git a/apps/meteor/app/ui/client/lib/fileUpload.ts b/apps/meteor/app/ui/client/lib/fileUpload.ts new file mode 100644 index 000000000000..dc4d70cf3070 --- /dev/null +++ b/apps/meteor/app/ui/client/lib/fileUpload.ts @@ -0,0 +1,236 @@ +import { Tracker } from 'meteor/tracker'; +import { Session } from 'meteor/session'; +import { Random } from 'meteor/random'; +import { Meteor } from 'meteor/meteor'; + +import { settings } from '../../../settings/client'; +import { UserAction, USER_ACTIVITIES } from '../index'; +import { fileUploadIsValidContentType, APIClient } from '../../../utils/client'; +import { imperativeModal } from '../../../../client/lib/imperativeModal'; +import FileUploadModal from '../../../../client/views/room/modals/FileUploadModal'; +import { prependReplies } from '../../../../client/lib/utils/prependReplies'; +import { chatMessages } from '../views/app/room'; + +type Uploading = { + id: string; + name: string; + percentage: number; + error?: Error; +}; + +declare module 'meteor/session' { + // eslint-disable-next-line @typescript-eslint/interface-name-prefix + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Session { + function get(key: 'uploading'): Uploading[]; + function set(key: 'uploading', param: Uploading[]): void; + } +} + +Session.setDefault('uploading', []); + +export const uploadFileWithMessage = async ( + rid: string, + { + description, + msg, + file, + }: { + file: File; + description?: string; + msg?: string; + }, + tmid?: string, +): Promise => { + const uploads = Session.get('uploading'); + + const upload = { + id: Random.id(), + name: file.name, + percentage: 0, + }; + + uploads.push(upload); + Session.set('uploading', uploads); + + try { + await new Promise((resolve, reject) => { + const xhr = APIClient.upload( + `/v1/rooms.upload/${rid}`, + { + msg, + tmid, + file, + description, + }, + { + load: (event) => { + return resolve(event); + }, + progress: (event) => { + if (!event.lengthComputable) { + return; + } + const progress = (event.loaded / event.total) * 100; + if (progress === 100) { + return; + } + + const uploads = Session.get('uploading'); + + uploads + .filter((u) => u.id === upload.id) + .forEach((u) => { + u.percentage = Math.round(progress) || 0; + }); + Session.set('uploading', uploads); + }, + error: (error) => { + const uploads = Session.get('uploading'); + uploads + .filter((u) => u.id === upload.id) + .forEach((u) => { + u.error = new Error(xhr.responseText); + u.percentage = 0; + }); + Session.set('uploading', uploads); + reject(error); + }, + }, + ); + + if (Session.get('uploading').length) { + UserAction.performContinuously(rid, USER_ACTIVITIES.USER_UPLOADING, { tmid }); + } + + Tracker.autorun((computation) => { + const isCanceling = Session.get(`uploading-cancel-${upload.id}`); + if (!isCanceling) { + return; + } + computation.stop(); + Session.delete(`uploading-cancel-${upload.id}`); + + xhr.abort(); + + const uploads = Session.get('uploading'); + Session.set( + 'uploading', + uploads.filter((u) => u.id !== upload.id), + ); + }); + }); + + const uploads = Session.get('uploading'); + Session.set( + 'uploading', + uploads.filter((u) => u.id !== upload.id), + ); + + if (!Session.get('uploading').length) { + UserAction.stop(rid, USER_ACTIVITIES.USER_UPLOADING, { tmid }); + } + } catch (error) { + const uploads = Session.get('uploading'); + uploads + .filter((u) => u.id === upload.id) + .forEach((u) => { + u.error = (error.xhr && error.xhr.responseJSON && error.xhr.responseJSON.error) || error.message; + u.percentage = 0; + }); + if (!uploads.length) { + UserAction.stop(rid, USER_ACTIVITIES.USER_UPLOADING, { tmid }); + } + Session.set('uploading', uploads); + } +}; + +type SingleOrArray = T | T[]; + +type FileUploadProp = SingleOrArray<{ + file: File; + name: string; +}>; + +/* @deprecated */ +export const fileUpload = async ( + f: FileUploadProp, + input: HTMLInputElement, + { + rid, + tmid, + }: { + rid: string; + tmid?: string; + }, +): Promise => { + if (!f) { + throw new Error('No files to upload'); + } + + const threadsEnabled = settings.get('Threads_enabled'); + + const files = Array.isArray(f) ? f : [f]; + + const replies = $(input).data('reply') || []; + const mention = $(input).data('mention-user') || false; + + let msg = ''; + + if (!mention || !threadsEnabled) { + msg = await prependReplies('', replies, mention); + } + + if (mention && threadsEnabled && replies.length) { + tmid = replies[0]._id; + } + + const key = ['messagebox', rid, tmid].filter(Boolean).join('_'); + const messageBoxText = Meteor._localStorage.getItem(key) || ''; + + const uploadNextFile = (): void => { + const file = files.pop(); + if (!file) { + return; + } + + imperativeModal.open({ + component: FileUploadModal, + props: { + file: file.file, + fileName: file.name, + fileDescription: messageBoxText, + onClose: (): void => { + imperativeModal.close(); + uploadNextFile(); + }, + onSubmit: (fileName: string, description?: string): void => { + Object.defineProperty(file.file, 'name', { + writable: true, + value: fileName, + }); + uploadFileWithMessage( + rid, + { + description, + msg, + file: file.file, + }, + tmid, + ); + const localStorageKey = ['messagebox', rid, tmid].filter(Boolean).join('_'); + const chatMessageKey = [rid, tmid].filter(Boolean).join('-'); + const { input } = chatMessages[chatMessageKey]; + input.value = null; + $(input).trigger('input'); + Meteor._localStorage.removeItem(localStorageKey); + imperativeModal.close(); + uploadNextFile(); + }, + invalidContentType: Boolean(file.file.type && !fileUploadIsValidContentType(file.file.type)), + }, + }); + }; + + uploadNextFile(); +}; diff --git a/apps/meteor/app/utils/client/lib/RestApiClient.ts b/apps/meteor/app/utils/client/lib/RestApiClient.ts new file mode 100644 index 000000000000..0621fe9850da --- /dev/null +++ b/apps/meteor/app/utils/client/lib/RestApiClient.ts @@ -0,0 +1,52 @@ +import { RestClient } from '@rocket.chat/api-client'; +import { Meteor } from 'meteor/meteor'; +import { Accounts } from 'meteor/accounts-base'; + +import { baseURI } from '../../../../client/lib/baseURI'; +import { process2faReturn } from '../../../../client/lib/2fa/process2faReturn'; + +export class RestApiClient extends RestClient { + getCredentials(): + | { + 'X-User-Id': string; + 'X-Auth-Token': string; + } + | undefined { + const [uid, token] = [Meteor._localStorage.getItem(Accounts.USER_ID_KEY), Meteor._localStorage.getItem(Accounts.LOGIN_TOKEN_KEY)]; + + if (!uid || !token) { + return; + } + return { + 'X-User-Id': uid, + 'X-Auth-Token': token, + }; + } +} + +export const APIClient = new RestApiClient({ + baseUrl: baseURI.replace(/\/$/, ''), +}); + +APIClient.use(function (request, next) { + try { + return next(...request); + } catch (e) { + return new Promise((resolve, reject) => { + process2faReturn({ + error: e, + result: null, + emailOrUsername: undefined, + originalCallback: () => reject(e), + onCode(code, method) { + return resolve( + next(request[0], request[1], { + ...request[2], + headers: { ...request[2].headers, 'x-2fa-code': code, 'x-2fa-method': method }, + }), + ); + }, + }); + }); + } +}); diff --git a/apps/meteor/app/utils/client/lib/RestApiClient.d.ts b/apps/meteor/app/utils/client/lib/_RestApiClient.d.ts similarity index 74% rename from apps/meteor/app/utils/client/lib/RestApiClient.d.ts rename to apps/meteor/app/utils/client/lib/_RestApiClient.d.ts index c5789be139ce..ede0c5034e5f 100644 --- a/apps/meteor/app/utils/client/lib/RestApiClient.d.ts +++ b/apps/meteor/app/utils/client/lib/_RestApiClient.d.ts @@ -1,4 +1,5 @@ import { Serialized } from '@rocket.chat/core-typings'; +import { RestClientInterface } from '@rocket.chat/api-client'; export declare const APIClient: { delete(endpoint: string, params?: Serialized

): Promise>; @@ -18,11 +19,8 @@ export declare const APIClient: { 'X-Auth-Token': string; }; _jqueryCall(method?: string, endpoint?: string, params?: any, body?: any, headers?: Record, dataType?: string): any; + /* @deprecated */ v1: { - delete(endpoint: string, params?: Serialized

): Promise>; - get(endpoint: string, params?: Serialized

): Promise>; - post(endpoint: string, params?: Serialized

, body?: B): Promise>; - put(endpoint: string, params?: Serialized

, body?: B): Promise>; upload( endpoint: string, params?: Serialized

, @@ -32,5 +30,5 @@ export declare const APIClient: { error: (ev: ProgressEvent) => void; }, ): { promise: Promise> }; - }; + } & RestClientInterface; }; diff --git a/apps/meteor/app/utils/client/lib/RestApiClient.js b/apps/meteor/app/utils/client/lib/_RestApiClient.js similarity index 100% rename from apps/meteor/app/utils/client/lib/RestApiClient.js rename to apps/meteor/app/utils/client/lib/_RestApiClient.js diff --git a/apps/meteor/app/utils/lib/fileUploadRestrictions.js b/apps/meteor/app/utils/lib/fileUploadRestrictions.js index 8fad52cbb372..403f5503cf7a 100644 --- a/apps/meteor/app/utils/lib/fileUploadRestrictions.js +++ b/apps/meteor/app/utils/lib/fileUploadRestrictions.js @@ -8,7 +8,7 @@ if (Meteor.isClient) { settings = require('../../settings/server').settings; } -const fileUploadMediaWhiteList = function (customWhiteList) { +export const fileUploadMediaWhiteList = function (customWhiteList) { const mediaTypeWhiteList = customWhiteList || settings.get('FileUpload_MediaTypeWhiteList'); if (!mediaTypeWhiteList || mediaTypeWhiteList === '*') { diff --git a/apps/meteor/app/utils/lib/slashCommand.ts b/apps/meteor/app/utils/lib/slashCommand.ts index 9c1ed3b2741d..813272a460ef 100644 --- a/apps/meteor/app/utils/lib/slashCommand.ts +++ b/apps/meteor/app/utils/lib/slashCommand.ts @@ -27,7 +27,7 @@ export const slashCommands = { permission: options.permission, clientOnly: options.clientOnly || false, result, - providesPreview, + providesPreview: Boolean(providesPreview), previewer, previewCallback, } as SlashCommand; diff --git a/apps/meteor/app/videobridge/client/actionLink.js b/apps/meteor/app/videobridge/client/actionLink.js index e99981c072eb..e09db09aeb61 100644 --- a/apps/meteor/app/videobridge/client/actionLink.js +++ b/apps/meteor/app/videobridge/client/actionLink.js @@ -29,7 +29,7 @@ actionLinks.register('joinJitsiCall', function (message, params, instance) { const clickTime = new Date(); const jitsiTimeout = new Date(room.jitsiTimeout); - APIClient.v1.post('statistics.telemetry', { + APIClient.post('/v1/statistics.telemetry', { params: [{ eventName: 'updateCounter', timestamp: Date.now(), settingsId: 'Jitsi_Click_To_Join_Count' }], }); diff --git a/apps/meteor/app/webdav/client/startup/sync.js b/apps/meteor/app/webdav/client/startup/sync.js index c3547ee4bfe4..8f96b8234838 100644 --- a/apps/meteor/app/webdav/client/startup/sync.js +++ b/apps/meteor/app/webdav/client/startup/sync.js @@ -14,7 +14,7 @@ Tracker.autorun(async () => { if (!Meteor.userId()) { return; } - const { accounts } = await APIClient.v1.get('webdav.getMyAccounts'); + const { accounts } = await APIClient.get('/v1/webdav.getMyAccounts'); accounts.forEach((account) => WebdavAccounts.insert(account)); Notifications.onUser('webdav', ({ type, account }) => events[type](account)); }); diff --git a/apps/meteor/app/webrtc/client/actionLink.tsx b/apps/meteor/app/webrtc/client/actionLink.tsx index 1265d52b14b5..b787d9601b52 100644 --- a/apps/meteor/app/webrtc/client/actionLink.tsx +++ b/apps/meteor/app/webrtc/client/actionLink.tsx @@ -22,6 +22,6 @@ actionLinks.register('endLivechatWebRTCCall', async (message: IMessage) => { dispatchToastMessage({ type: 'info', message: TAPi18n.__('Call_Already_Ended') }); return; } - await APIClient.v1.put(`livechat/webrtc.call/${message._id}`, {}, { rid: _id, status: 'ended' }); + await APIClient.put(`/v1/livechat/webrtc.call/${message._id}`, { rid: _id, status: 'ended' }); Notifications.notifyRoom(_id, 'webrtc', 'callStatus', { callStatus: 'ended' }); }); diff --git a/apps/meteor/app/webrtc/client/tabBar.tsx b/apps/meteor/app/webrtc/client/tabBar.tsx index 13f5ff1e0329..a69df1935809 100644 --- a/apps/meteor/app/webrtc/client/tabBar.tsx +++ b/apps/meteor/app/webrtc/client/tabBar.tsx @@ -9,7 +9,7 @@ addAction('webRTCVideo', ({ room }) => { const handleClick = useCallback(async (): Promise => { if (!room.callStatus || room.callStatus === 'declined' || room.callStatus === 'ended') { - await APIClient.v1.get('livechat/webrtc.call', { rid: room._id }); + await APIClient.get('/v1/livechat/webrtc.call', { rid: room._id }); } window.open(`/meet/${room._id}`, room._id); }, [room._id, room.callStatus]); diff --git a/apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx b/apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx index 685b9f29fbf4..585c86cc8cb0 100644 --- a/apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx +++ b/apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx @@ -43,7 +43,7 @@ const CreateDiscussion = ({ onClose, defaultParentRoom, parentMessageId, nameSug const canCreate = (parentRoom || defaultParentRoom) && name; - const createDiscussion = useEndpointActionExperimental('POST', 'rooms.createDiscussion'); + const createDiscussion = useEndpointActionExperimental('POST', '/v1/rooms.createDiscussion'); const create = useMutableCallback(async (): Promise => { try { diff --git a/apps/meteor/client/components/CreateDiscussion/DefaultParentRoomField.tsx b/apps/meteor/client/components/CreateDiscussion/DefaultParentRoomField.tsx index bdbb32108457..3fda044a7914 100644 --- a/apps/meteor/client/components/CreateDiscussion/DefaultParentRoomField.tsx +++ b/apps/meteor/client/components/CreateDiscussion/DefaultParentRoomField.tsx @@ -9,7 +9,7 @@ import { roomCoordinator } from '../../lib/rooms/roomCoordinator'; const DefaultParentRoomField = ({ defaultParentRoom }: { defaultParentRoom: string }): ReactElement => { const t = useTranslation(); const { value, phase } = useEndpointData( - 'rooms.info', + '/v1/rooms.info', useMemo( () => ({ roomId: defaultParentRoom, diff --git a/apps/meteor/client/components/Omnichannel/Tags.tsx b/apps/meteor/client/components/Omnichannel/Tags.tsx index f0d13fec4df0..ad2d515f4373 100644 --- a/apps/meteor/client/components/Omnichannel/Tags.tsx +++ b/apps/meteor/client/components/Omnichannel/Tags.tsx @@ -23,7 +23,7 @@ const Tags = ({ const t = useTranslation(); const forms = useSubscription(formsSubscription); - const { value: tagsResult, phase: stateTags } = useEndpointData('livechat/tags.list'); + const { value: tagsResult, phase: stateTags } = useEndpointData('/v1/livechat/tags.list'); // TODO: Refactor the formsSubscription to use components instead of hooks (since the only thing the hook does is return a component) const { useCurrentChatTags } = forms; diff --git a/apps/meteor/client/components/Omnichannel/hooks/useAgentsList.ts b/apps/meteor/client/components/Omnichannel/hooks/useAgentsList.ts index c5436312abc8..732cfa615740 100644 --- a/apps/meteor/client/components/Omnichannel/hooks/useAgentsList.ts +++ b/apps/meteor/client/components/Omnichannel/hooks/useAgentsList.ts @@ -22,9 +22,8 @@ export const useAgentsList = ( const t = useTranslation(); const [itemsList, setItemsList] = useState(() => new RecordList()); const reload = useCallback(() => setItemsList(new RecordList()), []); - const endpoint = 'livechat/users/agent'; - const getAgents = useEndpoint('GET', endpoint); + const getAgents = useEndpoint('GET', '/v1/livechat/users/agent'); useComponentDidUpdate(() => { options && reload(); diff --git a/apps/meteor/client/components/Omnichannel/hooks/useAvailableAgentsList.ts b/apps/meteor/client/components/Omnichannel/hooks/useAvailableAgentsList.ts index d0c2db186100..f0ee99e153cc 100644 --- a/apps/meteor/client/components/Omnichannel/hooks/useAvailableAgentsList.ts +++ b/apps/meteor/client/components/Omnichannel/hooks/useAvailableAgentsList.ts @@ -21,9 +21,8 @@ export const useAvailableAgentsList = ( } => { const [itemsList, setItemsList] = useState(() => new RecordList()); const reload = useCallback(() => setItemsList(new RecordList()), []); - const endpoint = 'omnichannel/agents/available'; - const getAgents = useEndpoint('GET', endpoint); + const getAgents = useEndpoint('GET', '/v1/omnichannel/agents/available'); useComponentDidUpdate(() => { options && reload(); diff --git a/apps/meteor/client/components/Omnichannel/hooks/useDepartmentsList.ts b/apps/meteor/client/components/Omnichannel/hooks/useDepartmentsList.ts index 1d87a9f6e6da..0c9752891ab8 100644 --- a/apps/meteor/client/components/Omnichannel/hooks/useDepartmentsList.ts +++ b/apps/meteor/client/components/Omnichannel/hooks/useDepartmentsList.ts @@ -28,7 +28,7 @@ export const useDepartmentsList = ( const [itemsList, setItemsList] = useState(() => new RecordList()); const reload = useCallback(() => setItemsList(new RecordList()), []); - const getDepartments = useEndpoint('GET', 'livechat/department'); + const getDepartments = useEndpoint('GET', '/v1/livechat/department'); useComponentDidUpdate(() => { options && reload(); diff --git a/apps/meteor/client/components/Omnichannel/modals/CloseChatModalData.tsx b/apps/meteor/client/components/Omnichannel/modals/CloseChatModalData.tsx index 978931731889..ae1314a006fe 100644 --- a/apps/meteor/client/components/Omnichannel/modals/CloseChatModalData.tsx +++ b/apps/meteor/client/components/Omnichannel/modals/CloseChatModalData.tsx @@ -15,7 +15,7 @@ const CloseChatModalData = ({ onCancel: () => void; onConfirm: (comment?: string, tags?: string[]) => Promise; }): ReactElement => { - const { value: data, phase: state } = useEndpointData(`livechat/department/${departmentId}`); + const { value: data, phase: state } = useEndpointData(`/v1/livechat/department/${departmentId}`); if ([state].includes(AsyncStatePhase.LOADING)) { return ; diff --git a/apps/meteor/client/components/Omnichannel/modals/ForwardChatModal.tsx b/apps/meteor/client/components/Omnichannel/modals/ForwardChatModal.tsx index e28465d697e2..6fdd7ad9b5ed 100644 --- a/apps/meteor/client/components/Omnichannel/modals/ForwardChatModal.tsx +++ b/apps/meteor/client/components/Omnichannel/modals/ForwardChatModal.tsx @@ -22,7 +22,7 @@ const ForwardChatModal = ({ room: IOmnichannelRoom; }): ReactElement => { const t = useTranslation(); - const getUserData = useEndpoint('GET', 'users.info'); + const getUserData = useEndpoint('GET', '/v1/users.info'); const { getValues, handleSubmit, register, setFocus, setValue, watch } = useForm(); diff --git a/apps/meteor/client/components/RoomAutoComplete/RoomAutoComplete.tsx b/apps/meteor/client/components/RoomAutoComplete/RoomAutoComplete.tsx index 4cf8077e9853..9b5b43af8ced 100644 --- a/apps/meteor/client/components/RoomAutoComplete/RoomAutoComplete.tsx +++ b/apps/meteor/client/components/RoomAutoComplete/RoomAutoComplete.tsx @@ -20,7 +20,7 @@ type RoomAutoCompleteProps = Omit, 'value const RoomAutoComplete = (props: RoomAutoCompleteProps): ReactElement => { const [filter, setFilter] = useState(''); const { value: data } = useEndpointData( - 'rooms.autocomplete.channelAndPrivate', + '/v1/rooms.autocomplete.channelAndPrivate', useMemo(() => query(filter), [filter]), ); const options = useMemo( diff --git a/apps/meteor/client/components/RoomAutoComplete/hooks/useRoomsList.ts b/apps/meteor/client/components/RoomAutoComplete/hooks/useRoomsList.ts index 8b5cc7fbd320..6be189937373 100644 --- a/apps/meteor/client/components/RoomAutoComplete/hooks/useRoomsList.ts +++ b/apps/meteor/client/components/RoomAutoComplete/hooks/useRoomsList.ts @@ -20,9 +20,8 @@ export const useRoomsList = ( } => { const [itemsList, setItemsList] = useState(() => new RecordList()); const reload = useCallback(() => setItemsList(new RecordList()), []); - const endpoint = 'rooms.autocomplete.channelAndPrivate.withPagination'; - const getRooms = useEndpoint('GET', endpoint); + const getRooms = useEndpoint('GET', '/v1/rooms.autocomplete.channelAndPrivate.withPagination'); useComponentDidUpdate(() => { options && reload(); diff --git a/apps/meteor/client/components/RoomIcon/OmnichannelRoomIcon/lib/OmnichannelRoomIcon.ts b/apps/meteor/client/components/RoomIcon/OmnichannelRoomIcon/lib/OmnichannelRoomIcon.ts index 0567502ff315..b6c572c31a48 100644 --- a/apps/meteor/client/components/RoomIcon/OmnichannelRoomIcon/lib/OmnichannelRoomIcon.ts +++ b/apps/meteor/client/components/RoomIcon/OmnichannelRoomIcon/lib/OmnichannelRoomIcon.ts @@ -14,10 +14,11 @@ const OmnichannelRoomIcon = new (class extends Emitter { if (!appId || !icon) { return; } + if (this.icons.has(`${appId}-${icon}`)) { return `${appId}-${icon}`; } - APIClient.get(`apps/public/${appId}/get-sidebar-icon`, { icon }).then((response) => { + APIClient.get(`/apps/public/${appId}/get-sidebar-icon`, { icon }).then((response: any) => { this.icons.set( `${appId}-${icon}`, DOMPurify.sanitize(response, { diff --git a/apps/meteor/client/components/TwoFactorModal/TwoFactorEmailModal.tsx b/apps/meteor/client/components/TwoFactorModal/TwoFactorEmailModal.tsx index fa61a2a3dd12..0e4569694fe1 100644 --- a/apps/meteor/client/components/TwoFactorModal/TwoFactorEmailModal.tsx +++ b/apps/meteor/client/components/TwoFactorModal/TwoFactorEmailModal.tsx @@ -18,7 +18,7 @@ const TwoFactorEmailModal = ({ onConfirm, onClose, emailOrUsername }: TwoFactorE const [code, setCode] = useState(''); const ref = useAutoFocus(); - const sendEmailCode = useEndpoint('POST', 'users.2fa.sendEmailCode'); + const sendEmailCode = useEndpoint('POST', '/v1/users.2fa.sendEmailCode'); const onClickResendCode = async (): Promise => { try { diff --git a/apps/meteor/client/components/UserAutoComplete/UserAutoComplete.tsx b/apps/meteor/client/components/UserAutoComplete/UserAutoComplete.tsx index 376b055a7830..b8e23f6e44d3 100644 --- a/apps/meteor/client/components/UserAutoComplete/UserAutoComplete.tsx +++ b/apps/meteor/client/components/UserAutoComplete/UserAutoComplete.tsx @@ -25,7 +25,7 @@ const UserAutoComplete = ({ value, ...props }: UserAutoCompleteProps): ReactElem const [filter, setFilter] = useState(''); const debouncedFilter = useDebouncedValue(filter, 1000); const { value: data } = useEndpointData( - 'users.autocomplete', + '/v1/users.autocomplete', // eslint-disable-next-line react-hooks/exhaustive-deps useMemo(() => query(debouncedFilter, conditions), [filter]), ); diff --git a/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultiple.tsx b/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultiple.tsx index 5e3d7e03bbb7..b27c6d6034f9 100644 --- a/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultiple.tsx +++ b/apps/meteor/client/components/UserAutoCompleteMultiple/UserAutoCompleteMultiple.tsx @@ -22,7 +22,7 @@ const UserAutoCompleteMultiple = ({ onChange, ...props }: UserAutoCompleteMultip const [filter, setFilter] = useState(''); const debouncedFilter = useDebouncedValue(filter, 1000); const { value: data } = useEndpointData( - 'users.autocomplete', + '/v1/users.autocomplete', useMemo(() => query(debouncedFilter), [debouncedFilter]), ); diff --git a/apps/meteor/client/components/message/Metrics/Thread.tsx b/apps/meteor/client/components/message/Metrics/Thread.tsx index 1f33834f1f2a..702c508a59f0 100644 --- a/apps/meteor/client/components/message/Metrics/Thread.tsx +++ b/apps/meteor/client/components/message/Metrics/Thread.tsx @@ -22,8 +22,8 @@ type ThreadReplyOptions = { const ThreadMetric: FC = ({ unread, mention, all, rid, mid, counter, participants, following, lm, openThread }) => { const t = useTranslation(); - const followMessage = useEndpoint('POST', 'chat.followMessage'); - const unfollowMessage = useEndpoint('POST', 'chat.unfollowMessage'); + const followMessage = useEndpoint('POST', '/v1/chat.followMessage'); + const unfollowMessage = useEndpoint('POST', '/v1/chat.unfollowMessage'); const format = useTimeAgo(); const handleFollow = useCallback( diff --git a/apps/meteor/client/hooks/useEndpointUpload.ts b/apps/meteor/client/hooks/useEndpointUpload.ts index 8a699171aa92..c25e95000fcc 100644 --- a/apps/meteor/client/hooks/useEndpointUpload.ts +++ b/apps/meteor/client/hooks/useEndpointUpload.ts @@ -2,8 +2,7 @@ import { useToastMessageDispatch, UploadResult, useUpload } from '@rocket.chat/u import { useCallback } from 'react'; export const useEndpointUpload = ( - endpoint: string, - params = {}, + endpoint: Parameters[0], successMessage: string, ): ((formData: FormData) => Promise<{ success: boolean }>) => { const sendData = useUpload(endpoint); @@ -12,7 +11,7 @@ export const useEndpointUpload = ( return useCallback( async (formData: FormData) => { try { - const data = sendData(params, formData); + const data = sendData(formData); const promise = data instanceof Promise ? data : data.promise; @@ -30,6 +29,6 @@ export const useEndpointUpload = ( return { success: false }; } }, - [dispatchToastMessage, params, sendData, successMessage], + [dispatchToastMessage, sendData, successMessage], ); }; diff --git a/apps/meteor/client/hooks/useUpdateAvatar.ts b/apps/meteor/client/hooks/useUpdateAvatar.ts index ae9eb2336d3e..fdcac425c928 100644 --- a/apps/meteor/client/hooks/useUpdateAvatar.ts +++ b/apps/meteor/client/hooks/useUpdateAvatar.ts @@ -48,9 +48,9 @@ export const useUpdateAvatar = (avatarObj: AvatarObject, userId: IUser['_id']): [userId], ); - const saveAvatarAction = useEndpointUpload('users.setAvatar', saveAvatarQuery, successText); - const saveAvatarUrlAction = useEndpointAction('POST', 'users.setAvatar', saveAvatarQuery, successText); - const resetAvatarAction = useEndpointAction('POST', 'users.resetAvatar', resetAvatarQuery, successText); + const saveAvatarAction = useEndpointUpload('/v1/users.setAvatar', successText); + const saveAvatarUrlAction = useEndpointAction('POST', '/v1/users.setAvatar', saveAvatarQuery, successText); + const resetAvatarAction = useEndpointAction('POST', '/v1/users.resetAvatar', resetAvatarQuery, successText); const updateAvatar = useCallback(async () => { if (isAvatarReset(avatarObj)) { diff --git a/apps/meteor/client/lib/meteorCallWrapper.ts b/apps/meteor/client/lib/meteorCallWrapper.ts index 72e83529184e..2615b6c4385d 100644 --- a/apps/meteor/client/lib/meteorCallWrapper.ts +++ b/apps/meteor/client/lib/meteorCallWrapper.ts @@ -50,8 +50,10 @@ function wrapMeteorDDPCalls(): void { Meteor.connection.onMessage(_message); }; - APIClient.v1 - .post(`${endpoint}/${encodeURIComponent(message.method.replace(/\//g, ':'))}`, restParams) + APIClient.post( + `/v1/${endpoint}/${encodeURIComponent(message.method.replace(/\//g, ':'))}` as Parameters[0], + restParams as any, + ) .then(({ message: _message }) => { processResult(_message); if (message.method === 'login') { diff --git a/apps/meteor/client/lib/presence.ts b/apps/meteor/client/lib/presence.ts index c5d927a101e0..5c81820e7e4e 100644 --- a/apps/meteor/client/lib/presence.ts +++ b/apps/meteor/client/lib/presence.ts @@ -51,6 +51,15 @@ const notify = (presence: UserPresence): void => { } }; +declare module '@rocket.chat/rest-typings' { + // eslint-disable-next-line @typescript-eslint/interface-name-prefix + export interface Endpoints { + '/v1/users.presence': { + GET: (params: { ids: string[] }) => UsersPresencePayload; + }; + } +} + const getPresence = ((): ((uid: UserPresence['_id']) => void) => { let timer: ReturnType; @@ -81,7 +90,7 @@ const getPresence = ((): ((uid: UserPresence['_id']) => void) => { ids: [...currentUids], }; - const { users } = (await APIClient.v1.get('users.presence', params)) as UsersPresencePayload; + const { users } = await APIClient.get('/v1/users.presence', params); users.forEach((user) => { if (!store.has(user._id)) { diff --git a/apps/meteor/client/lib/userData.ts b/apps/meteor/client/lib/userData.ts index a1a5bad7b5ad..112028faa0cc 100644 --- a/apps/meteor/client/lib/userData.ts +++ b/apps/meteor/client/lib/userData.ts @@ -81,7 +81,7 @@ export const synchronizeUserData = async (uid: Meteor.User['_id']): Promise { const voipEnabled = useSetting('VoIP_Enabled'); const subscribeToNotifyUser = useStream('notify-user'); - const dispatchEvent = useEndpoint('POST', 'voip/events'); + const dispatchEvent = useEndpoint('POST', '/v1/voip/events'); const result = useVoipClient(); const user = useUser(); @@ -234,9 +234,9 @@ export const CallProvider: FC = ({ children }) => { }; }, [onNetworkConnected, onNetworkDisconnected, result.voipClient]); - const visitorEndpoint = useEndpoint('POST', 'livechat/visitor'); - const voipEndpoint = useEndpoint('GET', 'voip/room'); - const voipCloseRoomEndpoint = useEndpoint('POST', 'voip/room.close'); + const visitorEndpoint = useEndpoint('POST', '/v1/livechat/visitor'); + const voipEndpoint = useEndpoint('GET', '/v1/voip/room'); + const voipCloseRoomEndpoint = useEndpoint('POST', '/v1/voip/room.close'); const [roomInfo, setRoomInfo] = useState<{ v: { token?: string }; rid: string }>(); diff --git a/apps/meteor/client/providers/CallProvider/hooks/useVoipClient.ts b/apps/meteor/client/providers/CallProvider/hooks/useVoipClient.ts index bc154ac17cac..bcf7e2558f4b 100644 --- a/apps/meteor/client/providers/CallProvider/hooks/useVoipClient.ts +++ b/apps/meteor/client/providers/CallProvider/hooks/useVoipClient.ts @@ -22,8 +22,8 @@ export const useVoipClient = (): UseVoipClientResult => { const [voipEnabled, setVoipEnabled] = useSafely(useState(useSetting('VoIP_Enabled'))); const voipRetryCount = useSetting('VoIP_Retry_Count'); const enableKeepAlive = useSetting('VoIP_Enable_Keep_Alive_For_Unstable_Networks'); - const registrationInfo = useEndpoint('GET', 'connector.extension.getRegistrationInfoByUserId'); - const membership = useEndpoint('GET', 'voip/queues.getMembershipSubscription'); + const registrationInfo = useEndpoint('GET', '/v1/connector.extension.getRegistrationInfoByUserId'); + const membership = useEndpoint('GET', '/v1/voip/queues.getMembershipSubscription'); const user = useUser(); const subscribeToNotifyLoggedIn = useStream('notify-logged'); const iceServers = useWebRtcServers(); diff --git a/apps/meteor/client/providers/ServerProvider.tsx b/apps/meteor/client/providers/ServerProvider.tsx index 1c5ec443195d..18e6ac307680 100644 --- a/apps/meteor/client/providers/ServerProvider.tsx +++ b/apps/meteor/client/providers/ServerProvider.tsx @@ -28,31 +28,22 @@ const callEndpoint = >( path: TPath, params: Serialized>>, ): Promise>>> => { - const api = path[0] === '/' ? APIClient : APIClient.v1; - const endpointPath = path[0] === '/' ? path.slice(1) : path; - switch (method) { case 'GET': - return api.get(endpointPath, params); + return APIClient.get(path as Parameters[0], params) as any; case 'POST': - return api.post(endpointPath, {}, params); + return APIClient.post(path as Parameters[0], params) as ReturnType; case 'DELETE': - return api.delete(endpointPath, params); + return APIClient.delete(path as Parameters[0], params) as ReturnType; default: throw new Error('Invalid HTTP method'); } }; -const uploadToEndpoint = (endpoint: string, params: any, formData: any): Promise => { - if (endpoint[0] === '/') { - return APIClient.upload(endpoint.slice(1), params, formData).promise; - } - - return APIClient.v1.upload(endpoint, params, formData).promise; -}; +const uploadToEndpoint = (endpoint: PathFor<'POST'>, formData: any): Promise => APIClient.post(endpoint, formData); const getStream = (streamName: string, options: {} = {}): ((eventName: string, callback: (data: T) => void) => () => void) => { const streamer = Meteor.StreamerCentral.instances[streamName] diff --git a/apps/meteor/client/sidebar/footer/voip/index.tsx b/apps/meteor/client/sidebar/footer/voip/index.tsx index ec6f83ff6d0e..bf137fa946a1 100644 --- a/apps/meteor/client/sidebar/footer/voip/index.tsx +++ b/apps/meteor/client/sidebar/footer/voip/index.tsx @@ -16,7 +16,7 @@ export const VoipFooter = (): ReactElement | null => { const t = useTranslation(); const callerInfo = useCallerInfo(); const callActions = useCallActions(); - const dispatchEvent = useEndpoint('POST', 'voip/events'); + const dispatchEvent = useEndpoint('POST', '/v1/voip/events'); const createRoom = useCallCreateRoom(); const openRoom = useCallOpenRoom(); diff --git a/apps/meteor/client/sidebar/header/CreateChannelWithData.tsx b/apps/meteor/client/sidebar/header/CreateChannelWithData.tsx index 8af0453b5ed6..467f6c23be36 100644 --- a/apps/meteor/client/sidebar/header/CreateChannelWithData.tsx +++ b/apps/meteor/client/sidebar/header/CreateChannelWithData.tsx @@ -25,8 +25,8 @@ type UseFormValues = { }; const CreateChannelWithData = ({ onClose, teamId = '', reload }: CreateChannelWithDataProps): ReactElement => { - const createChannel = useEndpointActionExperimental('POST', 'channels.create'); - const createPrivateChannel = useEndpointActionExperimental('POST', 'groups.create'); + const createChannel = useEndpointActionExperimental('POST', '/v1/channels.create'); + const createPrivateChannel = useEndpointActionExperimental('POST', '/v1/groups.create'); const canCreateChannel = usePermission('create-c'); const canCreatePrivateChannel = usePermission('create-p'); const e2eEnabledForPrivateByDefault = useSetting('E2E_Enabled_Default_PrivateRooms'); diff --git a/apps/meteor/client/sidebar/header/CreateDirectMessage.tsx b/apps/meteor/client/sidebar/header/CreateDirectMessage.tsx index 01375f00eda3..719f38eb54db 100644 --- a/apps/meteor/client/sidebar/header/CreateDirectMessage.tsx +++ b/apps/meteor/client/sidebar/header/CreateDirectMessage.tsx @@ -18,7 +18,7 @@ const CreateDirectMessage: FC = ({ onClose }) => { const t = useTranslation(); const [users, setUsers] = useState>([]); - const createDirect = useEndpointActionExperimental('POST', 'dm.create'); + const createDirect = useEndpointActionExperimental('POST', '/v1/dm.create'); const onChangeUsers = useMutableCallback((value: Username | any, action: 'remove' | undefined) => { if (!action) { diff --git a/apps/meteor/client/startup/banners.ts b/apps/meteor/client/startup/banners.ts index 93fbedc7fe49..19ffaf103951 100644 --- a/apps/meteor/client/startup/banners.ts +++ b/apps/meteor/client/startup/banners.ts @@ -1,4 +1,4 @@ -import { IBanner, BannerPlatform, Serialized } from '@rocket.chat/core-typings'; +import { BannerPlatform } from '@rocket.chat/core-typings'; import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; @@ -7,9 +7,7 @@ import { APIClient } from '../../app/utils/client'; import * as banners from '../lib/banners'; const fetchInitialBanners = async (): Promise => { - const response: Serialized<{ - banners: IBanner[]; - }> = await APIClient.get('v1/banners', { + const response = await APIClient.get('/v1/banners', { platform: BannerPlatform.Web, }); @@ -22,9 +20,7 @@ const fetchInitialBanners = async (): Promise => { }; const handleBanner = async (event: { bannerId: string }): Promise => { - const response: Serialized<{ - banners: IBanner[]; - }> = await APIClient.get(`v1/banners/${event.bannerId}`, { + const response = await APIClient.get(`/v1/banners/${event.bannerId}`, { platform: BannerPlatform.Web, }); diff --git a/apps/meteor/client/startup/routes.tsx b/apps/meteor/client/startup/routes.tsx index b86b36053d3c..8aa9a42172fe 100644 --- a/apps/meteor/client/startup/routes.tsx +++ b/apps/meteor/client/startup/routes.tsx @@ -74,8 +74,8 @@ FlowRouter.route('/meet/:rid', { async action(_params, queryParams) { if (queryParams?.token !== undefined) { // visitor login - const visitor = await APIClient.v1.get(`livechat/visitor/${queryParams?.token}`); - if (visitor?.visitor) { + const result = await APIClient.get(`/v1/livechat/visitor/${queryParams.token}`); + if ('visitor' in result) { appLayout.render(); return; } diff --git a/apps/meteor/client/startup/slashCommands.ts b/apps/meteor/client/startup/slashCommands.ts index 40c291174568..97dc8c46aa79 100644 --- a/apps/meteor/client/startup/slashCommands.ts +++ b/apps/meteor/client/startup/slashCommands.ts @@ -6,12 +6,12 @@ import { slashCommands, APIClient } from '../../app/utils/client'; let oldUserId: IUser['_id'] | null = null; -Tracker.autorun(() => { +Tracker.autorun(async () => { const newUserId = Meteor.userId(); if (oldUserId === null && newUserId) { - APIClient.v1.get('commands.list').then((result) => { - result.commands.forEach((command: typeof slashCommands.commands[string]) => { - slashCommands.commands[command.command] = command; + APIClient.get('/v1/commands.list').then((result) => { + result.commands.forEach((command) => { + slashCommands.add(command.command); }); }); } diff --git a/apps/meteor/client/stories/contexts/ServerContextMock.tsx b/apps/meteor/client/stories/contexts/ServerContextMock.tsx index 0a01e883ace2..bba776e442df 100644 --- a/apps/meteor/client/stories/contexts/ServerContextMock.tsx +++ b/apps/meteor/client/stories/contexts/ServerContextMock.tsx @@ -9,8 +9,8 @@ const logAction = action('ServerContext'); const randomDelay = (): Promise => new Promise((resolve) => setTimeout(resolve, Math.random() * 1000)); -const uploadToEndpoint = (endpoint: string, params: any, formData: any): Promise => - Promise.resolve(logAction('uploadToEndpoint', endpoint, params, formData)).then(randomDelay); +const uploadToEndpoint = (endpoint: PathFor<'POST'>, formData: any): Promise => + Promise.resolve(logAction('uploadToEndpoint', endpoint, formData)).then(randomDelay); const getStream = (streamName: string, options: {} = {}): ((eventName: string, callback: (data: T) => void) => () => void) => { logAction('getStream', streamName, options); diff --git a/apps/meteor/client/views/account/AccountProfilePage.js b/apps/meteor/client/views/account/AccountProfilePage.js index 7238d21f5861..7694764d011b 100644 --- a/apps/meteor/client/views/account/AccountProfilePage.js +++ b/apps/meteor/client/views/account/AccountProfilePage.js @@ -47,7 +47,7 @@ const AccountProfilePage = () => { const logout = useLogout(); const [loggingOut, setLoggingOut] = useState(false); - const logoutOtherClients = useEndpoint('POST', 'users.logoutOtherClients'); + const logoutOtherClients = useEndpoint('POST', '/v1/users.logoutOtherClients'); const deleteOwnAccount = useMethod('deleteUserOwnAccount'); const saveFn = useMethod('saveUserProfile'); diff --git a/apps/meteor/client/views/account/tokens/AccountTokensPage.js b/apps/meteor/client/views/account/tokens/AccountTokensPage.js index ba7a939753d4..da4f878977ec 100644 --- a/apps/meteor/client/views/account/tokens/AccountTokensPage.js +++ b/apps/meteor/client/views/account/tokens/AccountTokensPage.js @@ -8,7 +8,7 @@ import AddToken from './AddToken'; const AccountTokensPage = () => { const t = useTranslation(); - const { value: data, reload } = useEndpointData('users.getPersonalAccessTokens'); + const { value: data, reload } = useEndpointData('/v1/users.getPersonalAccessTokens'); return ( diff --git a/apps/meteor/client/views/admin/cloud/PasteStep.tsx b/apps/meteor/client/views/admin/cloud/PasteStep.tsx index 254882680d32..c66923c8bbe8 100644 --- a/apps/meteor/client/views/admin/cloud/PasteStep.tsx +++ b/apps/meteor/client/views/admin/cloud/PasteStep.tsx @@ -18,7 +18,7 @@ const PasteStep: FC = ({ onBackButtonClick, onFinish }) => { setCloudKey(e.currentTarget.value); }; - const registerManually = useEndpoint('POST', 'cloud.manualRegister'); + const registerManually = useEndpoint('POST', '/v1/cloud.manualRegister'); const handleFinishButtonClick = async (): Promise => { setLoading(true); diff --git a/apps/meteor/client/views/admin/customEmoji/AddCustomEmoji.tsx b/apps/meteor/client/views/admin/customEmoji/AddCustomEmoji.tsx index 6566c69c2243..2e8b629a8332 100644 --- a/apps/meteor/client/views/admin/customEmoji/AddCustomEmoji.tsx +++ b/apps/meteor/client/views/admin/customEmoji/AddCustomEmoji.tsx @@ -28,7 +28,7 @@ const AddCustomEmoji = ({ close, onChange, ...props }: AddCustomEmojiProps): Rea [setEmojiFile], ); - const saveAction = useEndpointUpload('emoji-custom.create', {}, t('Custom_Emoji_Added_Successfully')); + const saveAction = useEndpointUpload('/v1/emoji-custom.create', t('Custom_Emoji_Added_Successfully')); const handleSave = useCallback(async () => { if (!name) { diff --git a/apps/meteor/client/views/admin/customEmoji/CustomEmoji.tsx b/apps/meteor/client/views/admin/customEmoji/CustomEmoji.tsx index 2bccdf19ba1c..cb80bbf7d148 100644 --- a/apps/meteor/client/views/admin/customEmoji/CustomEmoji.tsx +++ b/apps/meteor/client/views/admin/customEmoji/CustomEmoji.tsx @@ -45,7 +45,7 @@ const CustomEmoji: FC = function CustomEmoji({ onClick, reload 500, ); - const { value: data, phase, reload: reloadEndPoint } = useEndpointData('emoji-custom.all', query); + const { value: data, phase, reload: reloadEndPoint } = useEndpointData('/v1/emoji-custom.all', query); useEffect(() => { reload.current = reloadEndPoint; diff --git a/apps/meteor/client/views/admin/customEmoji/EditCustomEmoji.tsx b/apps/meteor/client/views/admin/customEmoji/EditCustomEmoji.tsx index 61472d809323..d67444b666d6 100644 --- a/apps/meteor/client/views/admin/customEmoji/EditCustomEmoji.tsx +++ b/apps/meteor/client/views/admin/customEmoji/EditCustomEmoji.tsx @@ -53,7 +53,7 @@ const EditCustomEmoji: FC = ({ close, onChange, data, ...p [previousName, name, aliases, previousAliases, emojiFile], ); - const saveAction = useEndpointUpload('emoji-custom.update', {}, t('Custom_Emoji_Updated_Successfully')); + const saveAction = useEndpointUpload('/v1/emoji-custom.update', t('Custom_Emoji_Updated_Successfully')); const handleSave = useCallback(async () => { if (!name) { @@ -82,7 +82,7 @@ const EditCustomEmoji: FC = ({ close, onChange, data, ...p const deleteAction = useEndpointAction( 'POST', - 'emoji-custom.delete', + '/v1/emoji-custom.delete', useMemo(() => ({ emojiId: _id }), [_id]), ); diff --git a/apps/meteor/client/views/admin/customEmoji/EditCustomEmojiWithData.tsx b/apps/meteor/client/views/admin/customEmoji/EditCustomEmojiWithData.tsx index 8bff47185cb7..681ea859fc18 100644 --- a/apps/meteor/client/views/admin/customEmoji/EditCustomEmojiWithData.tsx +++ b/apps/meteor/client/views/admin/customEmoji/EditCustomEmojiWithData.tsx @@ -25,7 +25,7 @@ const EditCustomEmojiWithData: FC = ({ _id, onChan phase: state, error, reload, - } = useEndpointData('emoji-custom.list', query); + } = useEndpointData('/v1/emoji-custom.list', query); if (state === AsyncStatePhase.LOADING) { return ( diff --git a/apps/meteor/client/views/admin/customSounds/AdminSoundsRoute.tsx b/apps/meteor/client/views/admin/customSounds/AdminSoundsRoute.tsx index e489eb7b3eeb..66b2ea019358 100644 --- a/apps/meteor/client/views/admin/customSounds/AdminSoundsRoute.tsx +++ b/apps/meteor/client/views/admin/customSounds/AdminSoundsRoute.tsx @@ -48,7 +48,7 @@ function CustomSoundsRoute(): ReactElement { 500, ); - const { reload, ...result } = useEndpointData('custom-sounds.list', query); + const { reload, ...result } = useEndpointData('/v1/custom-sounds.list', query); const handleItemClick = useCallback( (_id) => (): void => { diff --git a/apps/meteor/client/views/admin/customSounds/EditCustomSound.tsx b/apps/meteor/client/views/admin/customSounds/EditCustomSound.tsx index 3fdb38b9b400..4fae6bcdcb7b 100644 --- a/apps/meteor/client/views/admin/customSounds/EditCustomSound.tsx +++ b/apps/meteor/client/views/admin/customSounds/EditCustomSound.tsx @@ -14,7 +14,7 @@ type EditCustomSoundProps = { function EditCustomSound({ _id, onChange, ...props }: EditCustomSoundProps): ReactElement { const query = useMemo(() => ({ query: JSON.stringify({ _id }) }), [_id]); - const { value: data, phase: state, error, reload } = useEndpointData('custom-sounds.list', query); + const { value: data, phase: state, error, reload } = useEndpointData('/v1/custom-sounds.list', query); if (state === AsyncStatePhase.LOADING) { return ( diff --git a/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusFormWithData.tsx b/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusFormWithData.tsx index ace27c478ec8..e3e6be9d3551 100644 --- a/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusFormWithData.tsx +++ b/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusFormWithData.tsx @@ -17,7 +17,7 @@ const CustomUserStatusFormWithData = ({ _id, onReload, onClose }: CustomUserStat const t = useTranslation(); const query = useMemo(() => ({ query: JSON.stringify({ _id }) }), [_id]); - const { value: data, phase: state, error, reload } = useEndpointData('custom-user-status.list', query); + const { value: data, phase: state, error, reload } = useEndpointData('/v1/custom-user-status.list', query); const handleReload = (): void => { onReload?.(); diff --git a/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusTable/CustomUserStatusTable.tsx b/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusTable/CustomUserStatusTable.tsx index 4407b664695a..ef0169704b8f 100644 --- a/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusTable/CustomUserStatusTable.tsx +++ b/apps/meteor/client/views/admin/customUserStatus/CustomUserStatusTable/CustomUserStatusTable.tsx @@ -41,7 +41,7 @@ const CustomUserStatus = ({ reload, onClick }: CustomUserStatusProps): ReactElem 500, ); - const { value, reload: reloadEndpoint, phase } = useEndpointData('custom-user-status.list', query); + const { value, reload: reloadEndpoint, phase } = useEndpointData('/v1/custom-user-status.list', query); useEffect(() => { reload.current = reloadEndpoint; diff --git a/apps/meteor/client/views/admin/emailInbox/EmailInboxEditWithData.tsx b/apps/meteor/client/views/admin/emailInbox/EmailInboxEditWithData.tsx index 3dd4484224ec..47f20f6a25ee 100644 --- a/apps/meteor/client/views/admin/emailInbox/EmailInboxEditWithData.tsx +++ b/apps/meteor/client/views/admin/emailInbox/EmailInboxEditWithData.tsx @@ -13,7 +13,7 @@ type EmailInboxEditWithDataProps = { const EmailInboxEditWithData = ({ id }: EmailInboxEditWithDataProps): ReactElement => { const t = useTranslation(); - const { value: data, error, phase: state } = useEndpointData(`email-inbox/${id}`); + const { value: data, error, phase: state } = useEndpointData(`/v1/email-inbox/${id}`); if ([state].includes(AsyncStatePhase.LOADING)) { return ; diff --git a/apps/meteor/client/views/admin/emailInbox/EmailInboxForm.js b/apps/meteor/client/views/admin/emailInbox/EmailInboxForm.js index 2597997e3859..f06b60f977f2 100644 --- a/apps/meteor/client/views/admin/emailInbox/EmailInboxForm.js +++ b/apps/meteor/client/views/admin/emailInbox/EmailInboxForm.js @@ -123,9 +123,9 @@ function EmailInboxForm({ id, data }) { const close = useCallback(() => router.push({}), [router]); - const saveEmailInbox = useEndpoint('POST', 'email-inbox'); - const deleteAction = useEndpoint('DELETE', `email-inbox/${id}`); - const emailAlreadyExistsAction = useEndpoint('GET', `email-inbox.search?email=${email}`); + const saveEmailInbox = useEndpoint('POST', '/v1/email-inbox'); + const deleteAction = useEndpoint('DELETE', `/v1/email-inbox/${id}`); + const emailAlreadyExistsAction = useEndpoint('GET', '/v1/email-inbox.search'); useComponentDidUpdate(() => { setEmailError(!validateEmail(email) ? t('Validate_email_address') : null); @@ -202,7 +202,7 @@ function EmailInboxForm({ id, data }) { if (!email && !validateEmail(email)) { return; } - const { emailInbox } = await emailAlreadyExistsAction(); + const { emailInbox } = await emailAlreadyExistsAction({ email }); if (!emailInbox || (id && emailInbox._id === id)) { return; diff --git a/apps/meteor/client/views/admin/emailInbox/EmailInboxTable.tsx b/apps/meteor/client/views/admin/emailInbox/EmailInboxTable.tsx index 499249a054c8..52718ccdd521 100644 --- a/apps/meteor/client/views/admin/emailInbox/EmailInboxTable.tsx +++ b/apps/meteor/client/views/admin/emailInbox/EmailInboxTable.tsx @@ -59,7 +59,7 @@ const EmailInboxTable = (): ReactElement => { [router], ); - const { phase, value: { emailInboxes = [], count = 0 } = {} } = useEndpointData('email-inbox.list', query); + const { phase, value: { emailInboxes = [], count = 0 } = {} } = useEndpointData('/v1/email-inbox.list', query); return ( <> diff --git a/apps/meteor/client/views/admin/emailInbox/SendTestButton.tsx b/apps/meteor/client/views/admin/emailInbox/SendTestButton.tsx index d1d2937459c0..fd1d6b627548 100644 --- a/apps/meteor/client/views/admin/emailInbox/SendTestButton.tsx +++ b/apps/meteor/client/views/admin/emailInbox/SendTestButton.tsx @@ -10,7 +10,7 @@ const SendTestButton = ({ id }: SendTestButtonProps): ReactElement => { const t = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); - const sendTest = useEndpoint('POST', `email-inbox.send-test/${id}`); + const sendTest = useEndpoint('POST', `/v1/email-inbox.send-test/${id}`); const handleOnClick = (e: React.MouseEvent): void => { e.preventDefault(); diff --git a/apps/meteor/client/views/admin/import/ImportHistoryPage.js b/apps/meteor/client/views/admin/import/ImportHistoryPage.js index e68f2e01a3d6..d24ea6793e6e 100644 --- a/apps/meteor/client/views/admin/import/ImportHistoryPage.js +++ b/apps/meteor/client/views/admin/import/ImportHistoryPage.js @@ -15,10 +15,10 @@ function ImportHistoryPage() { const [currentOperation, setCurrentOperation] = useSafely(useState()); const [latestOperations, setLatestOperations] = useSafely(useState([])); - const getCurrentImportOperation = useEndpoint('GET', 'getCurrentImportOperation'); - const getLatestImportOperations = useEndpoint('GET', 'getLatestImportOperations'); - const downloadPendingFiles = useEndpoint('POST', 'downloadPendingFiles'); - const downloadPendingAvatars = useEndpoint('POST', 'downloadPendingAvatars'); + const getCurrentImportOperation = useEndpoint('GET', '/v1/getCurrentImportOperation'); + const getLatestImportOperations = useEndpoint('GET', '/v1/getLatestImportOperations'); + const downloadPendingFiles = useEndpoint('POST', '/v1/downloadPendingFiles'); + const downloadPendingAvatars = useEndpoint('POST', '/v1/downloadPendingAvatars'); const newImportRoute = useRoute('admin-import-new'); const importProgressRoute = useRoute('admin-import-progress'); diff --git a/apps/meteor/client/views/admin/import/ImportProgressPage.js b/apps/meteor/client/views/admin/import/ImportProgressPage.js index 555def448057..911f27bbb8cf 100644 --- a/apps/meteor/client/views/admin/import/ImportProgressPage.js +++ b/apps/meteor/client/views/admin/import/ImportProgressPage.js @@ -19,8 +19,8 @@ function ImportProgressPage() { const [completed, setCompleted] = useSafely(useState(0)); const [total, setTotal] = useSafely(useState(0)); - const getCurrentImportOperation = useEndpoint('GET', 'getCurrentImportOperation'); - const getImportProgress = useEndpoint('GET', 'getImportProgress'); + const getCurrentImportOperation = useEndpoint('GET', '/v1/getCurrentImportOperation'); + const getImportProgress = useEndpoint('GET', '/v1/getImportProgress'); const importHistoryRoute = useRoute('admin-import'); const prepareImportRoute = useRoute('admin-import-prepare'); diff --git a/apps/meteor/client/views/admin/import/NewImportPage.js b/apps/meteor/client/views/admin/import/NewImportPage.js index 8e41803da6ce..6655d1468f81 100644 --- a/apps/meteor/client/views/admin/import/NewImportPage.js +++ b/apps/meteor/client/views/admin/import/NewImportPage.js @@ -38,8 +38,8 @@ function NewImportPage() { const newImportRoute = useRoute('admin-import-new'); const prepareImportRoute = useRoute('admin-import-prepare'); - const uploadImportFile = useEndpoint('POST', 'uploadImportFile'); - const downloadPublicImportFile = useEndpoint('POST', 'downloadPublicImportFile'); + const uploadImportFile = useEndpoint('POST', '/v1/uploadImportFile'); + const downloadPublicImportFile = useEndpoint('POST', '/v1/downloadPublicImportFile'); useEffect(() => { if (importerKey && !importer) { diff --git a/apps/meteor/client/views/admin/import/PrepareImportPage.js b/apps/meteor/client/views/admin/import/PrepareImportPage.js index b41004936da1..2765b3aa0aa1 100644 --- a/apps/meteor/client/views/admin/import/PrepareImportPage.js +++ b/apps/meteor/client/views/admin/import/PrepareImportPage.js @@ -53,9 +53,9 @@ function PrepareImportPage() { const newImportRoute = useRoute('admin-import-new'); const importProgressRoute = useRoute('admin-import-progress'); - const getImportFileData = useEndpoint('GET', 'getImportFileData'); - const getCurrentImportOperation = useEndpoint('GET', 'getCurrentImportOperation'); - const startImport = useEndpoint('POST', 'startImport'); + const getImportFileData = useEndpoint('GET', '/v1/getImportFileData'); + const getCurrentImportOperation = useEndpoint('GET', '/v1/getCurrentImportOperation'); + const startImport = useEndpoint('POST', '/v1/startImport'); useEffect(() => { const streamer = new Meteor.Streamer('importers'); diff --git a/apps/meteor/client/views/admin/info/InformationRoute.tsx b/apps/meteor/client/views/admin/info/InformationRoute.tsx index 207ce117d568..0f3e68e5a35a 100644 --- a/apps/meteor/client/views/admin/info/InformationRoute.tsx +++ b/apps/meteor/client/views/admin/info/InformationRoute.tsx @@ -20,7 +20,7 @@ const InformationRoute = (): ReactElement => { const [statistics, setStatistics] = useState(); const [instances, setInstances] = useState([]); const [fetchStatistics, setFetchStatistics] = useState(() => (): void => undefined); - const getStatistics = useEndpoint('GET', 'statistics'); + const getStatistics = useEndpoint('GET', '/v1/statistics'); const getInstances = useMethod('instances/get'); useEffect(() => { diff --git a/apps/meteor/client/views/admin/info/LicenseCard.tsx b/apps/meteor/client/views/admin/info/LicenseCard.tsx index 7490331b478c..7f0a3b4105f3 100644 --- a/apps/meteor/client/views/admin/info/LicenseCard.tsx +++ b/apps/meteor/client/views/admin/info/LicenseCard.tsx @@ -19,7 +19,7 @@ const LicenseCard = (): ReactElement => { const isAirGapped = true; - const { value, phase, error } = useEndpointData('licenses.get'); + const { value, phase, error } = useEndpointData('/v1/licenses.get'); const endpointLoading = phase === AsyncStatePhase.LOADING; const { modules = [] } = endpointLoading || error || !value?.licenses.length ? {} : value.licenses[0]; diff --git a/apps/meteor/client/views/admin/integrations/IntegrationsTable.js b/apps/meteor/client/views/admin/integrations/IntegrationsTable.js index 68d26b57cfb1..6f27e7aba61c 100644 --- a/apps/meteor/client/views/admin/integrations/IntegrationsTable.js +++ b/apps/meteor/client/views/admin/integrations/IntegrationsTable.js @@ -36,7 +36,7 @@ function IntegrationsTable({ type }) { const debouncedSort = useDebouncedValue(sort, 500); const query = useQuery({ ...params, text: debouncedText, type }, debouncedSort); - const { value: data } = useEndpointData('integrations.list', query); + const { value: data } = useEndpointData('/v1/integrations.list', query); const router = useRoute('admin-integrations'); diff --git a/apps/meteor/client/views/admin/integrations/edit/EditIncomingWebhookWithData.js b/apps/meteor/client/views/admin/integrations/edit/EditIncomingWebhookWithData.js index 4dcfca1bb1e7..1babc968c285 100644 --- a/apps/meteor/client/views/admin/integrations/edit/EditIncomingWebhookWithData.js +++ b/apps/meteor/client/views/admin/integrations/edit/EditIncomingWebhookWithData.js @@ -10,7 +10,7 @@ function EditIncomingWebhookWithData({ integrationId, ...props }) { const t = useTranslation(); const params = useMemo(() => ({ integrationId }), [integrationId]); - const { value: data, phase: state, error, reload } = useEndpointData('integrations.get', params); + const { value: data, phase: state, error, reload } = useEndpointData('/v1/integrations.get', params); const onChange = () => { reload(); diff --git a/apps/meteor/client/views/admin/integrations/edit/EditOutgoingWebhookWithData.js b/apps/meteor/client/views/admin/integrations/edit/EditOutgoingWebhookWithData.js index 30d61342ea56..18de6d51f192 100644 --- a/apps/meteor/client/views/admin/integrations/edit/EditOutgoingWebhookWithData.js +++ b/apps/meteor/client/views/admin/integrations/edit/EditOutgoingWebhookWithData.js @@ -10,7 +10,7 @@ function EditOutgoingWebhookWithData({ integrationId, ...props }) { const t = useTranslation(); const params = useMemo(() => ({ integrationId }), [integrationId]); - const { value: data, phase: state, error, reload } = useEndpointData('integrations.get', params); + const { value: data, phase: state, error, reload } = useEndpointData('/v1/integrations.get', params); const onChange = () => { reload(); diff --git a/apps/meteor/client/views/admin/integrations/edit/OutgoingWebhookHistoryPage.js b/apps/meteor/client/views/admin/integrations/edit/OutgoingWebhookHistoryPage.js index b917cc85149f..9eda4f9ee606 100644 --- a/apps/meteor/client/views/admin/integrations/edit/OutgoingWebhookHistoryPage.js +++ b/apps/meteor/client/views/admin/integrations/edit/OutgoingWebhookHistoryPage.js @@ -35,7 +35,7 @@ function OutgoingWebhookHistoryPage(props) { [id, itemsPerPage, current], ); - const { value: data, phase: state, reload } = useEndpointData('integrations.history', query); + const { value: data, phase: state, reload } = useEndpointData('/v1/integrations.history', query); const handleClearHistory = async () => { try { diff --git a/apps/meteor/client/views/admin/invites/InviteRow.tsx b/apps/meteor/client/views/admin/invites/InviteRow.tsx index 571f09f56293..2591df3a10e5 100644 --- a/apps/meteor/client/views/admin/invites/InviteRow.tsx +++ b/apps/meteor/client/views/admin/invites/InviteRow.tsx @@ -26,7 +26,7 @@ type InviteRowProps = Omit & { const InviteRow = ({ _id, createdAt, expires, uses, maxUses, onRemove }: InviteRowProps): ReactElement => { const t = useTranslation(); const formatDateAndTime = useFormatDateAndTime(); - const removeInvite = useEndpoint('DELETE', `removeInvite/${_id}`); + const removeInvite = useEndpoint('DELETE', `/v1/removeInvite/${_id}`); const getTimeFromNow = useTimeFromNow(false); diff --git a/apps/meteor/client/views/admin/invites/InvitesPage.tsx b/apps/meteor/client/views/admin/invites/InvitesPage.tsx index 293c326faace..3599e685b567 100644 --- a/apps/meteor/client/views/admin/invites/InvitesPage.tsx +++ b/apps/meteor/client/views/admin/invites/InvitesPage.tsx @@ -20,7 +20,7 @@ const InvitesPage = (): ReactElement => { const dispatchToastMessage = useToastMessageDispatch(); const setModal = useSetModal(); - const { phase, value, reload } = useEndpointData('listInvites'); + const { phase, value, reload } = useEndpointData('/v1/listInvites'); const onRemove = (removeInvite: () => void): void => { const confirmRemove = async (): Promise => { diff --git a/apps/meteor/client/views/admin/oauthApps/EditOauthAppWithData.tsx b/apps/meteor/client/views/admin/oauthApps/EditOauthAppWithData.tsx index e246de859be2..a72e02e2f011 100644 --- a/apps/meteor/client/views/admin/oauthApps/EditOauthAppWithData.tsx +++ b/apps/meteor/client/views/admin/oauthApps/EditOauthAppWithData.tsx @@ -11,7 +11,7 @@ const EditOauthAppWithData = ({ _id, ...props }: { _id: string }): ReactElement const params = useMemo(() => ({ appId: _id }), [_id]); - const { value: data, phase: state, error, reload } = useEndpointData('oauth-apps.get', params); + const { value: data, phase: state, error, reload } = useEndpointData('/v1/oauth-apps.get', params); const onChange = useCallback(() => { reload(); diff --git a/apps/meteor/client/views/admin/oauthApps/OAuthAppsTable.tsx b/apps/meteor/client/views/admin/oauthApps/OAuthAppsTable.tsx index 7cc9f6799ba4..e450483cbd07 100644 --- a/apps/meteor/client/views/admin/oauthApps/OAuthAppsTable.tsx +++ b/apps/meteor/client/views/admin/oauthApps/OAuthAppsTable.tsx @@ -10,7 +10,7 @@ const OAuthAppsTable = (): ReactElement => { const t = useTranslation(); const formatDateAndTime = useFormatDateAndTime(); - const { value: data } = useEndpointData('oauth-apps.list'); + const { value: data } = useEndpointData('/v1/oauth-apps.list'); const router = useRoute('admin-oauth-apps'); diff --git a/apps/meteor/client/views/admin/permissions/EditRolePage.tsx b/apps/meteor/client/views/admin/permissions/EditRolePage.tsx index 7ae9cc135eed..2d890c30e722 100644 --- a/apps/meteor/client/views/admin/permissions/EditRolePage.tsx +++ b/apps/meteor/client/views/admin/permissions/EditRolePage.tsx @@ -16,9 +16,9 @@ const EditRolePage = ({ role }: { role?: IRole }): ReactElement => { const usersInRoleRouter = useRoute('admin-permissions'); const router = useRoute('admin-permissions'); - const createRole = useEndpoint('POST', 'roles.create'); - const updateRole = useEndpoint('POST', 'roles.update'); - const deleteRole = useEndpoint('POST', 'roles.delete'); + const createRole = useEndpoint('POST', '/v1/roles.create'); + const updateRole = useEndpoint('POST', '/v1/roles.update'); + const deleteRole = useEndpoint('POST', '/v1/roles.delete'); const methods = useForm({ defaultValues: { diff --git a/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRolePage.tsx b/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRolePage.tsx index 33352a1147c7..837b412521ec 100644 --- a/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRolePage.tsx +++ b/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRolePage.tsx @@ -19,7 +19,7 @@ const UsersInRolePage = ({ role }: { role: IRole }): ReactElement => { const { _id, name, description } = role; const router = useRoute('admin-permissions'); - const addUser = useEndpoint('POST', 'roles.addUserToRole'); + const addUser = useEndpoint('POST', '/v1/roles.addUserToRole'); const handleReturn = useMutableCallback(() => { router.push({ diff --git a/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRoleTable/UsersInRoleTable.tsx b/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRoleTable/UsersInRoleTable.tsx index 3d8f78c6ff8d..3577fde2c89f 100644 --- a/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRoleTable/UsersInRoleTable.tsx +++ b/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRoleTable/UsersInRoleTable.tsx @@ -24,7 +24,7 @@ const UsersInRoleTable = ({ users, reload, roleName, roleId, description, total, const t = useTranslation(); const setModal = useSetModal(); const dispatchToastMessage = useToastMessageDispatch(); - const removeUser = useEndpoint('POST', 'roles.removeUserFromRole'); + const removeUser = useEndpoint('POST', '/v1/roles.removeUserFromRole'); const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = paginationData; const closeModal = (): void => setModal(); diff --git a/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRoleTable/UsersInRoleTableWithData.tsx b/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRoleTable/UsersInRoleTableWithData.tsx index 91dedabe1722..b6c94d1f6532 100644 --- a/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRoleTable/UsersInRoleTableWithData.tsx +++ b/apps/meteor/client/views/admin/permissions/UsersInRole/UsersInRoleTable/UsersInRoleTableWithData.tsx @@ -33,7 +33,7 @@ const UsersInRoleTableWithData = ({ [itemsPerPage, current, rid, roleId], ); - const { reload, ...result } = useEndpointData('roles.getUsersInRole', query); + const { reload, ...result } = useEndpointData('/v1/roles.getUsersInRole', query); useEffect(() => { reloadRef.current = reload; diff --git a/apps/meteor/client/views/admin/rooms/EditRoom.tsx b/apps/meteor/client/views/admin/rooms/EditRoom.tsx index 029c1fbb251b..b4551958e6b4 100644 --- a/apps/meteor/client/views/admin/rooms/EditRoom.tsx +++ b/apps/meteor/client/views/admin/rooms/EditRoom.tsx @@ -116,8 +116,8 @@ const EditRoom = ({ room, onChange, onDelete }: EditRoomProps): ReactElement => const archiveSelector = room.archived ? 'unarchive' : 'archive'; const archiveMessage = room.archived ? 'Room_has_been_unarchived' : 'Room_has_been_archived'; - const saveAction = useEndpointActionExperimental('POST', 'rooms.saveRoomSettings', t('Room_updated_successfully')); - const archiveAction = useEndpointActionExperimental('POST', 'rooms.changeArchivationState', t(archiveMessage)); + const saveAction = useEndpointActionExperimental('POST', '/v1/rooms.saveRoomSettings', t('Room_updated_successfully')); + const archiveAction = useEndpointActionExperimental('POST', '/v1/rooms.changeArchivationState', t(archiveMessage)); const handleSave = useMutableCallback(async () => { const save = (): Promise<{ success: boolean; rid: string }> => @@ -149,7 +149,7 @@ const EditRoom = ({ room, onChange, onDelete }: EditRoomProps): ReactElement => }); const eraseRoom = useMethod('eraseRoom'); - const deleteTeam = useEndpoint('POST', 'teams.delete'); + const deleteTeam = useEndpoint('POST', '/v1/teams.delete'); const handleDelete = useMutableCallback(() => { if (room.teamMain) { diff --git a/apps/meteor/client/views/admin/rooms/EditRoomWithData.tsx b/apps/meteor/client/views/admin/rooms/EditRoomWithData.tsx index 95f822faca9b..49fbc18fefcf 100644 --- a/apps/meteor/client/views/admin/rooms/EditRoomWithData.tsx +++ b/apps/meteor/client/views/admin/rooms/EditRoomWithData.tsx @@ -12,7 +12,7 @@ const EditRoomWithData: FC<{ rid?: string; onReload: () => void }> = ({ rid, onR error, reload, } = useEndpointData( - 'rooms.adminRooms.getRoom', + '/v1/rooms.adminRooms.getRoom', useMemo(() => ({ rid }), [rid]), ); diff --git a/apps/meteor/client/views/admin/rooms/RoomsTable.tsx b/apps/meteor/client/views/admin/rooms/RoomsTable.tsx index eebd3b2e1790..00fff51bae51 100644 --- a/apps/meteor/client/views/admin/rooms/RoomsTable.tsx +++ b/apps/meteor/client/views/admin/rooms/RoomsTable.tsx @@ -112,7 +112,7 @@ const RoomsTable = ({ reload }: { reload: MutableRefObject<() => void> }): React const query = useQuery(debouncedParams, debouncedSort); - const endpointData = useEndpointData('rooms.adminRooms', query); + const endpointData = useEndpointData('/v1/rooms.adminRooms', query); const { value: data, reload: reloadEndPoint } = endpointData; diff --git a/apps/meteor/client/views/admin/settings/groups/LDAPGroupPage.tsx b/apps/meteor/client/views/admin/settings/groups/LDAPGroupPage.tsx index ff5a041e3d81..7cc0c3e895c7 100644 --- a/apps/meteor/client/views/admin/settings/groups/LDAPGroupPage.tsx +++ b/apps/meteor/client/views/admin/settings/groups/LDAPGroupPage.tsx @@ -11,9 +11,9 @@ import TabbedGroupPage from './TabbedGroupPage'; function LDAPGroupPage({ _id, ...group }: ISetting): JSX.Element { const t = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); - const testConnection = useEndpoint('POST', 'ldap.testConnection'); - const syncNow = useEndpoint('POST', 'ldap.syncNow'); - const testSearch = useEndpoint('POST', 'ldap.testSearch'); + const testConnection = useEndpoint('POST', '/v1/ldap.testConnection'); + const syncNow = useEndpoint('POST', '/v1/ldap.syncNow'); + const testSearch = useEndpoint('POST', '/v1/ldap.testSearch'); const ldapEnabled = useSetting('LDAP_Enable'); const setModal = useSetModal(); const closeModal = useMutableCallback(() => setModal()); diff --git a/apps/meteor/client/views/admin/settings/groups/voip/AssignAgentModal.tsx b/apps/meteor/client/views/admin/settings/groups/voip/AssignAgentModal.tsx index 838518a65068..7c0180df4ec7 100644 --- a/apps/meteor/client/views/admin/settings/groups/voip/AssignAgentModal.tsx +++ b/apps/meteor/client/views/admin/settings/groups/voip/AssignAgentModal.tsx @@ -20,7 +20,7 @@ const AssignAgentModal: FC = ({ existingExtension, close const [extension, setExtension] = useState(existingExtension || ''); const query = useMemo(() => ({ type: 'available' as const, userId: agent }), [agent]); - const assignAgent = useEndpoint('POST', 'omnichannel/agent/extension'); + const assignAgent = useEndpoint('POST', '/v1/omnichannel/agent/extension'); const handleAssignment = useMutableCallback(async () => { try { @@ -33,7 +33,7 @@ const AssignAgentModal: FC = ({ existingExtension, close }); const handleAgentChange = useMutableCallback((e) => setAgent(e)); - const { value: availableExtensions, phase: state } = useEndpointData('omnichannel/extension', query); + const { value: availableExtensions, phase: state } = useEndpointData('/v1/omnichannel/extension', query); return ( diff --git a/apps/meteor/client/views/admin/settings/groups/voip/RemoveAgentButton.tsx b/apps/meteor/client/views/admin/settings/groups/voip/RemoveAgentButton.tsx index df3ec0fb57b6..a08c6da2f02d 100644 --- a/apps/meteor/client/views/admin/settings/groups/voip/RemoveAgentButton.tsx +++ b/apps/meteor/client/views/admin/settings/groups/voip/RemoveAgentButton.tsx @@ -6,7 +6,7 @@ import React, { FC } from 'react'; import GenericModal from '../../../../../components/GenericModal'; const RemoveAgentButton: FC<{ username: string; reload: () => void }> = ({ username, reload }) => { - const removeAgent = useEndpoint('DELETE', 'omnichannel/agent/extension'); + const removeAgent = useEndpoint('DELETE', '/v1/omnichannel/agent/extension'); const setModal = useSetModal(); const dispatchToastMessage = useToastMessageDispatch(); const t = useTranslation(); diff --git a/apps/meteor/client/views/admin/settings/groups/voip/VoipExtensionsPage.tsx b/apps/meteor/client/views/admin/settings/groups/voip/VoipExtensionsPage.tsx index a4030165ca18..e8a7a6ad482d 100644 --- a/apps/meteor/client/views/admin/settings/groups/voip/VoipExtensionsPage.tsx +++ b/apps/meteor/client/views/admin/settings/groups/voip/VoipExtensionsPage.tsx @@ -29,7 +29,7 @@ const VoipExtensionsPage: FC = () => { [itemsPerPage, current], ); - const { value: data, reload } = useEndpointData('omnichannel/extensions', query); + const { value: data, reload } = useEndpointData('/v1/omnichannel/extensions', query); const header = useMemo( () => diff --git a/apps/meteor/client/views/admin/users/AddUser.js b/apps/meteor/client/views/admin/users/AddUser.js index 010d20659b81..5aa3eb2cf9c9 100644 --- a/apps/meteor/client/views/admin/users/AddUser.js +++ b/apps/meteor/client/views/admin/users/AddUser.js @@ -13,7 +13,7 @@ export function AddUser({ roles, onReload, ...props }) { const router = useRoute('admin-users'); - const { value: roleData } = useEndpointData('roles.list', ''); + const { value: roleData } = useEndpointData('/v1/roles.list', ''); const [errors, setErrors] = useState({}); const validationKeys = { diff --git a/apps/meteor/client/views/admin/users/EditUser.js b/apps/meteor/client/views/admin/users/EditUser.js index a2e6ae02513e..a8130ca64484 100644 --- a/apps/meteor/client/views/admin/users/EditUser.js +++ b/apps/meteor/client/views/admin/users/EditUser.js @@ -89,10 +89,10 @@ function EditUser({ data, roles, onReload, ...props }) { [data._id], ); - const saveAction = useEndpointAction('POST', 'users.update', saveQuery, t('User_updated_successfully')); - const saveAvatarAction = useEndpointUpload('users.setAvatar', saveAvatarQuery, t('Avatar_changed_successfully')); - const saveAvatarUrlAction = useEndpointAction('POST', 'users.setAvatar', saveAvatarQuery, t('Avatar_changed_successfully')); - const resetAvatarAction = useEndpointAction('POST', 'users.resetAvatar', resetAvatarQuery, t('Avatar_changed_successfully')); + const saveAction = useEndpointAction('POST', '/v1/users.update', saveQuery, t('User_updated_successfully')); + const saveAvatarAction = useEndpointUpload('users.setAvatar', t('Avatar_changed_successfully')); + const saveAvatarUrlAction = useEndpointAction('POST', '/v1/users.setAvatar', saveAvatarQuery, t('Avatar_changed_successfully')); + const resetAvatarAction = useEndpointAction('POST', '/v1/users.resetAvatar', resetAvatarQuery, t('Avatar_changed_successfully')); const updateAvatar = useCallback(async () => { if (avatarObj === 'reset') { diff --git a/apps/meteor/client/views/admin/users/EditUserWithData.js b/apps/meteor/client/views/admin/users/EditUserWithData.js index aec9e89807c1..0e4ba0328ac4 100644 --- a/apps/meteor/client/views/admin/users/EditUserWithData.js +++ b/apps/meteor/client/views/admin/users/EditUserWithData.js @@ -9,13 +9,13 @@ import EditUser from './EditUser'; function EditUserWithData({ uid, ...props }) { const t = useTranslation(); - const { value: roleData, phase: roleState, error: roleError } = useEndpointData('roles.list', ''); + const { value: roleData, phase: roleState, error: roleError } = useEndpointData('/v1/roles.list', ''); const { value: data, phase: state, error, } = useEndpointData( - 'users.info', + '/v1/users.info', useMemo(() => ({ userId: uid }), [uid]), ); diff --git a/apps/meteor/client/views/admin/users/UserInfo.js b/apps/meteor/client/views/admin/users/UserInfo.js index 4a0be4ef9681..0be62c953161 100644 --- a/apps/meteor/client/views/admin/users/UserInfo.js +++ b/apps/meteor/client/views/admin/users/UserInfo.js @@ -25,7 +25,7 @@ export function UserInfoWithData({ uid, username, onReload, ...props }) { error, reload: reloadUserInfo, } = useEndpointData( - 'users.info', + '/v1/users.info', useMemo(() => ({ ...(uid && { userId: uid }), ...(username && { username }) }), [uid, username]), ); diff --git a/apps/meteor/client/views/admin/users/UserInfoActions.js b/apps/meteor/client/views/admin/users/UserInfoActions.js index c16388f7d0a0..da1d0b6847ac 100644 --- a/apps/meteor/client/views/admin/users/UserInfoActions.js +++ b/apps/meteor/client/views/admin/users/UserInfoActions.js @@ -75,7 +75,7 @@ export const UserInfoActions = ({ username, _id, isActive, isAdmin, onChange, on }; const deleteUserQuery = useMemo(() => ({ userId: _id }), [_id]); - const deleteUserEndpoint = useEndpoint('POST', 'users.delete'); + const deleteUserEndpoint = useEndpoint('POST', '/v1/users.delete'); const erasureType = useSetting('Message_ErasureType'); @@ -119,8 +119,8 @@ export const UserInfoActions = ({ username, _id, isActive, isAdmin, onChange, on } }, [_id, dispatchToastMessage, isAdmin, onChange, setAdminStatus, t]); - const resetE2EEKeyRequest = useEndpoint('POST', 'users.resetE2EKey'); - const resetTOTPRequest = useEndpoint('POST', 'users.resetTOTP'); + const resetE2EEKeyRequest = useEndpoint('POST', '/v1/users.resetE2EKey'); + const resetTOTPRequest = useEndpoint('POST', '/v1/users.resetTOTP'); const resetE2EEKey = useCallback(async () => { setModal(); const result = await resetE2EEKeyRequest({ userId: _id }); @@ -171,7 +171,7 @@ export const UserInfoActions = ({ username, _id, isActive, isAdmin, onChange, on [_id, isActive], ); const changeActiveStatusMessage = isActive ? 'User_has_been_deactivated' : 'User_has_been_activated'; - const changeActiveStatusRequest = useEndpoint('POST', 'users.setActiveStatus'); + const changeActiveStatusRequest = useEndpoint('POST', '/v1/users.setActiveStatus'); const changeActiveStatus = confirmOwnerChanges( async (confirm = false) => { diff --git a/apps/meteor/client/views/admin/users/UsersPage.js b/apps/meteor/client/views/admin/users/UsersPage.js index de5a6960468c..5dab712b1b53 100644 --- a/apps/meteor/client/views/admin/users/UsersPage.js +++ b/apps/meteor/client/views/admin/users/UsersPage.js @@ -77,7 +77,7 @@ function UsersPage() { const debouncedParams = useDebouncedValue(params, 500); const debouncedSort = useDebouncedValue(sort, 500); const query = useQuery(debouncedParams, debouncedSort); - const { value: data = {}, reload: reloadList } = useEndpointData('users.list', query); + const { value: data = {}, reload: reloadList } = useEndpointData('/v1/users.list', query); const reload = () => { seatsCap?.reload(); diff --git a/apps/meteor/client/views/admin/viewLogs/ServerLogs.tsx b/apps/meteor/client/views/admin/viewLogs/ServerLogs.tsx index 28f8bcb43b71..73f6f07c15b7 100644 --- a/apps/meteor/client/views/admin/viewLogs/ServerLogs.tsx +++ b/apps/meteor/client/views/admin/viewLogs/ServerLogs.tsx @@ -23,7 +23,7 @@ const ServerLogs = (): ReactElement => { const dispatchToastMessage = useToastMessageDispatch(); - const getStdoutQueue = useEndpoint('GET', 'stdout.queue'); + const getStdoutQueue = useEndpoint('GET', '/v1/stdout.queue'); const subscribeToStdout = useStream('stdout'); useEffect(() => { diff --git a/apps/meteor/client/views/directory/ChannelsTable.js b/apps/meteor/client/views/directory/ChannelsTable.js index d71bc76fde65..297f9e9869c0 100644 --- a/apps/meteor/client/views/directory/ChannelsTable.js +++ b/apps/meteor/client/views/directory/ChannelsTable.js @@ -93,7 +93,7 @@ function ChannelsTable() { const channelRoute = useRoute('channel'); const groupsRoute = useRoute('group'); - const { value: data = {} } = useEndpointData('directory', query); + const { value: data = {} } = useEndpointData('/v1/directory', query); const onClick = useMemo( () => (name, type) => (e) => { diff --git a/apps/meteor/client/views/directory/TeamsTable.js b/apps/meteor/client/views/directory/TeamsTable.js index da1d8e3a9071..614608ff7b47 100644 --- a/apps/meteor/client/views/directory/TeamsTable.js +++ b/apps/meteor/client/views/directory/TeamsTable.js @@ -69,7 +69,7 @@ function TeamsTable() { const query = useQuery(params, sort, 'teams'); - const { value: data = {} } = useEndpointData('directory', query); + const { value: data = {} } = useEndpointData('/v1/directory', query); const onClick = useMemo( () => (name, type) => (e) => { diff --git a/apps/meteor/client/views/directory/UserTable.js b/apps/meteor/client/views/directory/UserTable.js index 2ac612213c05..da868f2db689 100644 --- a/apps/meteor/client/views/directory/UserTable.js +++ b/apps/meteor/client/views/directory/UserTable.js @@ -84,7 +84,7 @@ function UserTable({ workspace = 'local' }) { const directRoute = useRoute('direct'); - const { value: data = {} } = useEndpointData('directory', query); + const { value: data = {} } = useEndpointData('/v1/directory', query); const onClick = useCallback( (username) => (e) => { diff --git a/apps/meteor/client/views/hooks/useDepartmentsByUnitsList.ts b/apps/meteor/client/views/hooks/useDepartmentsByUnitsList.ts index cec014d1cc18..8a5d99b9f83e 100644 --- a/apps/meteor/client/views/hooks/useDepartmentsByUnitsList.ts +++ b/apps/meteor/client/views/hooks/useDepartmentsByUnitsList.ts @@ -21,9 +21,8 @@ export const useDepartmentsByUnitsList = ( } => { const [itemsList, setItemsList] = useState(() => new RecordList()); const reload = useCallback(() => setItemsList(new RecordList()), []); - const endpoint = `livechat/departments.available-by-unit/${options.unitId || 'none'}` as const; - const getDepartments = useEndpoint('GET', endpoint); + const getDepartments = useEndpoint('GET', `/v1/livechat/departments.available-by-unit/${options.unitId || 'none'}`); useComponentDidUpdate(() => { options && reload(); diff --git a/apps/meteor/client/views/hooks/useMembersList.ts b/apps/meteor/client/views/hooks/useMembersList.ts index be18bd8db628..08d7616afce6 100644 --- a/apps/meteor/client/views/hooks/useMembersList.ts +++ b/apps/meteor/client/views/hooks/useMembersList.ts @@ -16,9 +16,9 @@ type MembersListOptions = { }; const endpointsByRoomType = { - d: 'im.members', - p: 'groups.members', - c: 'channels.members', + d: '/v1/im.members', + p: '/v1/groups.members', + c: '/v1/channels.members', } as const; export const useMembersList = ( diff --git a/apps/meteor/client/views/hooks/useMonitorsList.ts b/apps/meteor/client/views/hooks/useMonitorsList.ts index f1b884b915c3..decfa3ee3c70 100644 --- a/apps/meteor/client/views/hooks/useMonitorsList.ts +++ b/apps/meteor/client/views/hooks/useMonitorsList.ts @@ -21,9 +21,7 @@ export const useMonitorsList = ( const [itemsList, setItemsList] = useState(() => new RecordList()); const reload = useCallback(() => setItemsList(new RecordList()), []); - const endpoint = 'livechat/monitors.list'; - - const getMonitors = useEndpoint('GET', endpoint); + const getMonitors = useEndpoint('GET', '/v1/livechat/monitors.list'); useComponentDidUpdate(() => { options && reload(); diff --git a/apps/meteor/client/views/hooks/useUpgradeTabParams.ts b/apps/meteor/client/views/hooks/useUpgradeTabParams.ts index 6a91fd917776..234c54be5998 100644 --- a/apps/meteor/client/views/hooks/useUpgradeTabParams.ts +++ b/apps/meteor/client/views/hooks/useUpgradeTabParams.ts @@ -5,8 +5,8 @@ import { useQuery } from 'react-query'; import { UpgradeTabVariant, getUpgradeTabType } from '../../../lib/getUpgradeTabType'; export const useUpgradeTabParams = (): { tabType: UpgradeTabVariant | false; trialEndDate: string | undefined; isLoading: boolean } => { - const getRegistrationStatus = useEndpoint('GET', 'cloud.registrationStatus'); - const getLicenses = useEndpoint('GET', 'licenses.get'); + const getRegistrationStatus = useEndpoint('GET', '/v1/cloud.registrationStatus'); + const getLicenses = useEndpoint('GET', '/v1/licenses.get'); const cloudWorkspaceHadTrial = useSetting('Cloud_Workspace_Had_Trial') as boolean; const { data: registrationStatusData } = useQuery(['registrationStatus'], () => getRegistrationStatus()); diff --git a/apps/meteor/client/views/invite/InvitePage.tsx b/apps/meteor/client/views/invite/InvitePage.tsx index 319f997ba089..4e9f1e8cc57e 100644 --- a/apps/meteor/client/views/invite/InvitePage.tsx +++ b/apps/meteor/client/views/invite/InvitePage.tsx @@ -1,4 +1,3 @@ -import { OperationParams, OperationResult } from '@rocket.chat/rest-typings'; import { useToastMessageDispatch, useSessionDispatch, @@ -35,11 +34,7 @@ const InvitePage = (): ReactElement => { } try { - const { valid } = await APIClient.v1.post< - OperationParams<'POST', 'validateInviteToken'>, - never, - OperationResult<'POST', 'validateInviteToken'> - >('validateInviteToken', { token }); + const { valid } = await APIClient.post('/v1/validateInviteToken', { token }); return valid; } catch (error) { @@ -64,12 +59,9 @@ const InvitePage = (): ReactElement => { } try { - const result = await APIClient.v1.post< - OperationParams<'POST', 'useInviteToken'>, - never, - OperationResult<'POST', 'useInviteToken'> - >('useInviteToken', { token }); - if (!result?.room.name) { + const result = await APIClient.post('/v1/useInviteToken', { token }); + + if (!result.room.name) { dispatchToastMessage({ type: 'error', message: t('Failed_to_activate_invite_token') }); homeRoute.push(); return; diff --git a/apps/meteor/client/views/meet/MeetPage.tsx b/apps/meteor/client/views/meet/MeetPage.tsx index e0f673f6d9c2..8d9ea1235187 100644 --- a/apps/meteor/client/views/meet/MeetPage.tsx +++ b/apps/meteor/client/views/meet/MeetPage.tsx @@ -25,7 +25,14 @@ const MeetPage: FC = () => { const closeCallTab = (): void => window.close(); const setupCallForVisitor = useCallback(async () => { - const room = await APIClient.v1.get(`livechat/room?token=${visitorToken}&rid=${roomId}`); + if (!visitorToken || !roomId) { + throw new Error('Missing parameters'); + } + + const room = (await APIClient.get('/v1/livechat/room', { + token: visitorToken, + rid: roomId, + })) as any; if (room?.room?.v?.token === visitorToken) { setVisitorId(room.room.v._id); setVisitorName(room.room.fname); @@ -37,7 +44,11 @@ const MeetPage: FC = () => { }, [visitorToken, roomId]); const setupCallForAgent = useCallback(async () => { - const room = await APIClient.v1.get(`rooms.info?roomId=${roomId}`); + if (!roomId) { + throw new Error('Missing parameters'); + } + + const room = (await APIClient.get('/v1/rooms.info', { roomId })) as any; if (room?.room?.servedBy?._id === Meteor.userId()) { setVisitorName(room.room.fname); room?.room?.responseBy?.username ? setAgentName(room.room.responseBy.username) : setAgentName(room.room.servedBy.username); diff --git a/apps/meteor/client/views/omnichannel/DepartmentAutoComplete.js b/apps/meteor/client/views/omnichannel/DepartmentAutoComplete.js index d27c8d5adfbb..cfc62f03cfa3 100644 --- a/apps/meteor/client/views/omnichannel/DepartmentAutoComplete.js +++ b/apps/meteor/client/views/omnichannel/DepartmentAutoComplete.js @@ -16,7 +16,7 @@ const DepartmentAutoComplete = (props) => { const { enabled, onlyMyDepartments = false } = props; const [filter, setFilter] = useState(''); const { value: data } = useEndpointData( - 'livechat/department.autocomplete', + '/v1/livechat/department.autocomplete', useMemo(() => query(filter, enabled, onlyMyDepartments), [onlyMyDepartments, enabled, filter]), ); diff --git a/apps/meteor/client/views/omnichannel/agents/AddAgent.tsx b/apps/meteor/client/views/omnichannel/agents/AddAgent.tsx index dc1e7a9166d9..707d10876bc1 100644 --- a/apps/meteor/client/views/omnichannel/agents/AddAgent.tsx +++ b/apps/meteor/client/views/omnichannel/agents/AddAgent.tsx @@ -14,7 +14,7 @@ const AddAgent = ({ reload }: AddAgentProps): ReactElement => { const t = useTranslation(); const [username, setUsername] = useState(''); - const saveAction = useEndpointAction('POST', 'livechat/users/agent', { username }); + const saveAction = useEndpointAction('POST', '/v1/livechat/users/agent', { username }); const handleSave = useMutableCallback(async () => { if (!username) { diff --git a/apps/meteor/client/views/omnichannel/agents/AgentEditWithData.tsx b/apps/meteor/client/views/omnichannel/agents/AgentEditWithData.tsx index 3ff846618622..b2aed4a9af46 100644 --- a/apps/meteor/client/views/omnichannel/agents/AgentEditWithData.tsx +++ b/apps/meteor/client/views/omnichannel/agents/AgentEditWithData.tsx @@ -14,17 +14,17 @@ type AgentEditWithDataProps = { const AgentEditWithData = ({ uid, reload }: AgentEditWithDataProps): ReactElement => { const t = useTranslation(); - const { value: data, phase: state, error } = useEndpointData(`livechat/users/agent/${uid}`); + const { value: data, phase: state, error } = useEndpointData(`/v1/livechat/users/agent/${uid}`); const { value: userDepartments, phase: userDepartmentsState, error: userDepartmentsError, - } = useEndpointData(`livechat/agents/${uid}/departments`); + } = useEndpointData(`/v1/livechat/agents/${uid}/departments`); const { value: availableDepartments, phase: availableDepartmentsState, error: availableDepartmentsError, - } = useEndpointData('livechat/department'); + } = useEndpointData('/v1/livechat/department'); if ( [state, availableDepartmentsState, userDepartmentsState].includes(AsyncStatePhase.LOADING) || diff --git a/apps/meteor/client/views/omnichannel/agents/AgentInfo.tsx b/apps/meteor/client/views/omnichannel/agents/AgentInfo.tsx index 3287aca13583..f53192c345cf 100644 --- a/apps/meteor/client/views/omnichannel/agents/AgentInfo.tsx +++ b/apps/meteor/client/views/omnichannel/agents/AgentInfo.tsx @@ -17,7 +17,7 @@ type AgentInfoProps = { export const AgentInfo = memo(function AgentInfo({ uid, children, ...props }) { const t = useTranslation(); - const result = useEndpointData(`livechat/users/agent/${uid}`); + const result = useEndpointData(`/v1/livechat/users/agent/${uid}`); const { useMaxChatsPerAgentDisplay } = useFormsSubscription(); diff --git a/apps/meteor/client/views/omnichannel/agents/AgentInfoActions.tsx b/apps/meteor/client/views/omnichannel/agents/AgentInfoActions.tsx index 089bb43078ff..21fb735c4630 100644 --- a/apps/meteor/client/views/omnichannel/agents/AgentInfoActions.tsx +++ b/apps/meteor/client/views/omnichannel/agents/AgentInfoActions.tsx @@ -10,7 +10,7 @@ const AgentInfoActions = ({ reload }: { reload: () => void }): ReactElement => { const t = useTranslation(); const _id = useRouteParameter('id'); const agentsRoute = useRoute('omnichannel-agents'); - const deleteAction = useEndpointAction('DELETE', `livechat/users/agent/${_id}`); + const deleteAction = useEndpointAction('DELETE', `/v1/livechat/users/agent/${_id}`); const setModal = useSetModal(); const dispatchToastMessage = useToastMessageDispatch(); diff --git a/apps/meteor/client/views/omnichannel/agents/AgentsPage.tsx b/apps/meteor/client/views/omnichannel/agents/AgentsPage.tsx index 94045e8e982f..72b53541f8f6 100644 --- a/apps/meteor/client/views/omnichannel/agents/AgentsPage.tsx +++ b/apps/meteor/client/views/omnichannel/agents/AgentsPage.tsx @@ -36,7 +36,7 @@ const AgentsPage = (): ReactElement => { const { current, itemsPerPage, setItemsPerPage, setCurrent, ...paginationProps } = usePagination(); const query = useQuery({ text: debouncedFilter, current, itemsPerPage }, debouncedSort); - const { reload, ...result } = useEndpointData('livechat/users/agent', query); + const { reload, ...result } = useEndpointData('/v1/livechat/users/agent', query); const onHeaderClick = useMutableCallback((id) => { if (sortBy === id) { diff --git a/apps/meteor/client/views/omnichannel/agents/RemoveAgentButton.tsx b/apps/meteor/client/views/omnichannel/agents/RemoveAgentButton.tsx index a5f48da05a38..a94deecdc889 100644 --- a/apps/meteor/client/views/omnichannel/agents/RemoveAgentButton.tsx +++ b/apps/meteor/client/views/omnichannel/agents/RemoveAgentButton.tsx @@ -13,7 +13,7 @@ type RemoveAgentButtonProps = { }; const RemoveAgentButton = ({ _id, reload }: RemoveAgentButtonProps): ReactElement => { - const deleteAction = useEndpointAction('DELETE', `livechat/users/agent/${_id}`); + const deleteAction = useEndpointAction('DELETE', `/v1/livechat/users/agent/${_id}`); const setModal = useSetModal(); const dispatchToastMessage = useToastMessageDispatch(); const t = useTranslation(); diff --git a/apps/meteor/client/views/omnichannel/appearance/AppearancePageContainer.tsx b/apps/meteor/client/views/omnichannel/appearance/AppearancePageContainer.tsx index f77d997b6f74..cb5d5432fd14 100644 --- a/apps/meteor/client/views/omnichannel/appearance/AppearancePageContainer.tsx +++ b/apps/meteor/client/views/omnichannel/appearance/AppearancePageContainer.tsx @@ -12,7 +12,7 @@ import AppearancePage from './AppearancePage'; const AppearancePageContainer: FC = () => { const t = useTranslation(); - const { value: data, phase: state, error } = useEndpointData('livechat/appearance'); + const { value: data, phase: state, error } = useEndpointData('/v1/livechat/appearance'); const canViewAppearance = usePermission('view-livechat-appearance'); diff --git a/apps/meteor/client/views/omnichannel/businessHours/EditBusinessHoursPage.js b/apps/meteor/client/views/omnichannel/businessHours/EditBusinessHoursPage.js index 7bbc3c55a757..7482595dfcf4 100644 --- a/apps/meteor/client/views/omnichannel/businessHours/EditBusinessHoursPage.js +++ b/apps/meteor/client/views/omnichannel/businessHours/EditBusinessHoursPage.js @@ -17,7 +17,7 @@ const EditBusinessHoursPage = ({ id, type }) => { const isSingleBH = useIsSingleBusinessHours(); const { value: data, phase: state } = useEndpointData( - 'livechat/business-hour', + '/v1/livechat/business-hour', useMemo(() => ({ _id: id, type }), [id, type]), ); diff --git a/apps/meteor/client/views/omnichannel/components/CustomField.js b/apps/meteor/client/views/omnichannel/components/CustomField.js index 903bb010edb7..5960d5225467 100644 --- a/apps/meteor/client/views/omnichannel/components/CustomField.js +++ b/apps/meteor/client/views/omnichannel/components/CustomField.js @@ -11,7 +11,7 @@ import Label from './Label'; const CustomField = ({ id, value }) => { const t = useTranslation(); - const { value: data, phase: state, error } = useEndpointData(`livechat/custom-fields/${id}`); + const { value: data, phase: state, error } = useEndpointData(`/v1/livechat/custom-fields/${id}`); if (state === AsyncStatePhase.LOADING) { return ; } diff --git a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsRoute.tsx b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsRoute.tsx index f2e7ad337ec7..7abf9affeb96 100644 --- a/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsRoute.tsx +++ b/apps/meteor/client/views/omnichannel/currentChats/CurrentChatsRoute.tsx @@ -130,7 +130,7 @@ const CurrentChatsRoute = (): ReactElement => { directoryRoute.push({ id: _id }); }); - const { value: data, reload } = useEndpointData('livechat/rooms', query); + const { value: data, reload } = useEndpointData('/v1/livechat/rooms', query); const header = useMemo( () => diff --git a/apps/meteor/client/views/omnichannel/currentChats/FilterByText.tsx b/apps/meteor/client/views/omnichannel/currentChats/FilterByText.tsx index 8608a5f6754e..d7b92cb3463d 100644 --- a/apps/meteor/client/views/omnichannel/currentChats/FilterByText.tsx +++ b/apps/meteor/client/views/omnichannel/currentChats/FilterByText.tsx @@ -23,7 +23,7 @@ const FilterByText: FilterByTextType = ({ setFilter, reload, ...props }) => { const dispatchToastMessage = useToastMessageDispatch(); const t = useTranslation(); - const { value: allCustomFields } = useEndpointData('livechat/custom-fields'); + const { value: allCustomFields } = useEndpointData('/v1/livechat/custom-fields'); const statusOptions: [string, string][] = [ ['all', t('All')], ['closed', t('Closed')], diff --git a/apps/meteor/client/views/omnichannel/customFields/CustomFieldsRoute.js b/apps/meteor/client/views/omnichannel/customFields/CustomFieldsRoute.js index 96c50c623bca..208035d08979 100644 --- a/apps/meteor/client/views/omnichannel/customFields/CustomFieldsRoute.js +++ b/apps/meteor/client/views/omnichannel/customFields/CustomFieldsRoute.js @@ -57,7 +57,7 @@ const CustomFieldsRoute = () => { }), ); - const { value: data, reload } = useEndpointData('livechat/custom-fields', query); + const { value: data, reload } = useEndpointData('/v1/livechat/custom-fields', query); const header = useMemo( () => diff --git a/apps/meteor/client/views/omnichannel/customFields/EditCustomFieldsPageContainer.js b/apps/meteor/client/views/omnichannel/customFields/EditCustomFieldsPageContainer.js index 0f9a45bb2d55..8c8e6455755d 100644 --- a/apps/meteor/client/views/omnichannel/customFields/EditCustomFieldsPageContainer.js +++ b/apps/meteor/client/views/omnichannel/customFields/EditCustomFieldsPageContainer.js @@ -12,7 +12,7 @@ const EditCustomFieldsPageContainer = ({ reload }) => { const t = useTranslation(); const id = useRouteParameter('id'); - const { value: data, phase: state, error } = useEndpointData(`livechat/custom-fields/${id}`); + const { value: data, phase: state, error } = useEndpointData(`/v1/livechat/custom-fields/${id}`); if (state === AsyncStatePhase.LOADING) { return ; diff --git a/apps/meteor/client/views/omnichannel/departments/AddAgent.js b/apps/meteor/client/views/omnichannel/departments/AddAgent.js index ffc5175a13d7..a720bbee36e9 100644 --- a/apps/meteor/client/views/omnichannel/departments/AddAgent.js +++ b/apps/meteor/client/views/omnichannel/departments/AddAgent.js @@ -9,7 +9,7 @@ import { useEndpointAction } from '../../../hooks/useEndpointAction'; function AddAgent({ agentList, setAgentsAdded, setAgentList, ...props }) { const t = useTranslation(); const [userId, setUserId] = useState(); - const getAgent = useEndpointAction('GET', `livechat/users/agent/${userId}`); + const getAgent = useEndpointAction('GET', `/v1/livechat/users/agent/${userId}`); const dispatchToastMessage = useToastMessageDispatch(); const handleAgent = useMutableCallback((e) => setUserId(e)); diff --git a/apps/meteor/client/views/omnichannel/departments/DepartmentsRoute.js b/apps/meteor/client/views/omnichannel/departments/DepartmentsRoute.js index 1ec5bcec1682..b752413441ec 100644 --- a/apps/meteor/client/views/omnichannel/departments/DepartmentsRoute.js +++ b/apps/meteor/client/views/omnichannel/departments/DepartmentsRoute.js @@ -66,7 +66,7 @@ function DepartmentsRoute() { }), ); - const { value: data = {}, reload } = useEndpointData('livechat/department', query); + const { value: data = {}, reload } = useEndpointData('/v1/livechat/department', query); const header = useMemo( () => diff --git a/apps/meteor/client/views/omnichannel/departments/EditDepartment.js b/apps/meteor/client/views/omnichannel/departments/EditDepartment.js index f14f849ce062..ccde330932d0 100644 --- a/apps/meteor/client/views/omnichannel/departments/EditDepartment.js +++ b/apps/meteor/client/views/omnichannel/departments/EditDepartment.js @@ -138,7 +138,7 @@ function EditDepartment({ data, id, title, reload, allowedToForwardData }) { }; const saveDepartmentInfo = useMethod('livechat:saveDepartment'); - const saveDepartmentAgentsInfoOnEdit = useEndpoint('POST', `livechat/department/${id}/agents`); + const saveDepartmentAgentsInfoOnEdit = useEndpoint('POST', `/v1/livechat/department/${id}/agents`); const dispatchToastMessage = useToastMessageDispatch(); diff --git a/apps/meteor/client/views/omnichannel/departments/EditDepartmentWithAllowedForwardData.js b/apps/meteor/client/views/omnichannel/departments/EditDepartmentWithAllowedForwardData.js index d9bcade9284c..5df029fdab0a 100644 --- a/apps/meteor/client/views/omnichannel/departments/EditDepartmentWithAllowedForwardData.js +++ b/apps/meteor/client/views/omnichannel/departments/EditDepartmentWithAllowedForwardData.js @@ -15,7 +15,7 @@ function EditDepartmentWithAllowedForwardData({ data, ...props }) { phase: allowedToForwardState, error: allowedToForwardError, } = useEndpointData( - 'livechat/department.listByIds', + '/v1/livechat/department.listByIds', useMemo( () => ({ ids: data && data.department && data.department.departmentsAllowedToForward ? data.department.departmentsAllowedToForward : [], diff --git a/apps/meteor/client/views/omnichannel/departments/EditDepartmentWithData.js b/apps/meteor/client/views/omnichannel/departments/EditDepartmentWithData.js index 6c53ea6bfb8a..7fe2a08edc4a 100644 --- a/apps/meteor/client/views/omnichannel/departments/EditDepartmentWithData.js +++ b/apps/meteor/client/views/omnichannel/departments/EditDepartmentWithData.js @@ -11,7 +11,7 @@ import EditDepartmentWithAllowedForwardData from './EditDepartmentWithAllowedFor const param = { onlyMyDepartments: true }; function EditDepartmentWithData({ id, reload, title }) { const t = useTranslation(); - const { value: data, phase: state, error } = useEndpointData(`livechat/department/${id}`, param); + const { value: data, phase: state, error } = useEndpointData(`/v1/livechat/department/${id}`, param); if ([state].includes(AsyncStatePhase.LOADING)) { return ; diff --git a/apps/meteor/client/views/omnichannel/departments/RemoveDepartmentButton.js b/apps/meteor/client/views/omnichannel/departments/RemoveDepartmentButton.js index 16889b62e906..99599a49daca 100644 --- a/apps/meteor/client/views/omnichannel/departments/RemoveDepartmentButton.js +++ b/apps/meteor/client/views/omnichannel/departments/RemoveDepartmentButton.js @@ -7,7 +7,7 @@ import GenericModal from '../../../components/GenericModal'; import { useEndpointAction } from '../../../hooks/useEndpointAction'; function RemoveDepartmentButton({ _id, reload }) { - const deleteAction = useEndpointAction('DELETE', `livechat/department/${_id}`); + const deleteAction = useEndpointAction('DELETE', `/v1/livechat/department/${_id}`); const setModal = useSetModal(); const dispatchToastMessage = useToastMessageDispatch(); const t = useTranslation(); diff --git a/apps/meteor/client/views/omnichannel/directory/CallsContextualBarDirectory.tsx b/apps/meteor/client/views/omnichannel/directory/CallsContextualBarDirectory.tsx index a2df5446c180..e74e5ecfad3c 100644 --- a/apps/meteor/client/views/omnichannel/directory/CallsContextualBarDirectory.tsx +++ b/apps/meteor/client/views/omnichannel/directory/CallsContextualBarDirectory.tsx @@ -31,7 +31,7 @@ const CallsContextualBarDirectory: FC = () => { [id, token], ); - const { value: data, phase: state, error } = useEndpointData(`voip/room`, query); + const { value: data, phase: state, error } = useEndpointData(`/v1/voip/room`, query); if (bar === 'view' && id) { return ; diff --git a/apps/meteor/client/views/omnichannel/directory/ChatsContextualBar.tsx b/apps/meteor/client/views/omnichannel/directory/ChatsContextualBar.tsx index 01a39a2a981d..f13d2b17b293 100644 --- a/apps/meteor/client/views/omnichannel/directory/ChatsContextualBar.tsx +++ b/apps/meteor/client/views/omnichannel/directory/ChatsContextualBar.tsx @@ -37,7 +37,7 @@ const ChatsContextualBar: FC<{ chatReload?: () => void }> = ({ chatReload }) => [id], ); - const { value: data, phase: state, error, reload: reloadInfo } = useEndpointData(`rooms.info`, query); + const { value: data, phase: state, error, reload: reloadInfo } = useEndpointData(`/v1/rooms.info`, query); if (bar === 'view' && id) { return ; diff --git a/apps/meteor/client/views/omnichannel/directory/calls/CallTable.tsx b/apps/meteor/client/views/omnichannel/directory/calls/CallTable.tsx index 7d964af4c16a..db21eb25fe89 100644 --- a/apps/meteor/client/views/omnichannel/directory/calls/CallTable.tsx +++ b/apps/meteor/client/views/omnichannel/directory/calls/CallTable.tsx @@ -75,7 +75,7 @@ const CallTable: FC = () => { ); }); - const { value: data } = useEndpointData('voip/rooms', query); + const { value: data } = useEndpointData('/v1/voip/rooms', query); const header = useMemo( () => diff --git a/apps/meteor/client/views/omnichannel/directory/chats/ChatTable.tsx b/apps/meteor/client/views/omnichannel/directory/chats/ChatTable.tsx index 9258748ec959..3ad3b547b808 100644 --- a/apps/meteor/client/views/omnichannel/directory/chats/ChatTable.tsx +++ b/apps/meteor/client/views/omnichannel/directory/chats/ChatTable.tsx @@ -73,7 +73,7 @@ const ChatTable: FC<{ setChatReload: Dispatch> }> = ({ setCh }), ); - const { value: data, reload } = useEndpointData('livechat/rooms', query as any); // TODO: Check the typing for the livechat/rooms endpoint as it seems wrong + const { value: data, reload } = useEndpointData('/v1/livechat/rooms', query as any); // TODO: Check the typing for the livechat/rooms endpoint as it seems wrong useEffect(() => { setChatReload?.(() => reload); diff --git a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/AgentField.js b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/AgentField.js index 40c45afdd35c..56c34594eb6e 100644 --- a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/AgentField.js +++ b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/AgentField.js @@ -1,6 +1,6 @@ import { Box } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; -import React from 'react'; +import React, { useMemo } from 'react'; import UserCard from '../../../../../components/UserCard'; import { UserStatus } from '../../../../../components/UserStatus'; @@ -15,7 +15,10 @@ import { FormSkeleton } from '../../Skeleton'; const AgentField = ({ agent, isSmall = false }) => { const t = useTranslation(); const { username } = agent; - const { value, phase: state } = useEndpointData(`users.info?username=${username}`); + const { value, phase: state } = useEndpointData( + `/v1/users.info`, + useMemo(() => ({ username }), [username]), + ); if (state === AsyncStatePhase.LOADING) { return ; diff --git a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/ChatInfo.js b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/ChatInfo.js index ee49c4f464d3..4c38f7979878 100644 --- a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/ChatInfo.js +++ b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/ChatInfo.js @@ -27,7 +27,7 @@ function ChatInfo({ id, route }) { const t = useTranslation(); const formatDateAndTime = useFormatDateAndTime(); - const { value: allCustomFields, phase: stateCustomFields } = useEndpointData('livechat/custom-fields'); + const { value: allCustomFields, phase: stateCustomFields } = useEndpointData('/v1/livechat/custom-fields'); const [customFields, setCustomFields] = useState([]); const formatDuration = useFormatDuration(); diff --git a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/ChatInfoDirectory.js b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/ChatInfoDirectory.js index 1ac3accc8393..da0919789e44 100644 --- a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/ChatInfoDirectory.js +++ b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/ChatInfoDirectory.js @@ -24,7 +24,7 @@ function ChatInfoDirectory({ id, route = undefined, room }) { const t = useTranslation(); const formatDateAndTime = useFormatDateAndTime(); - const { value: allCustomFields, phase: stateCustomFields } = useEndpointData('livechat/custom-fields'); + const { value: allCustomFields, phase: stateCustomFields } = useEndpointData('/v1/livechat/custom-fields'); const [customFields, setCustomFields] = useState([]); const formatDuration = useFormatDuration(); diff --git a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/ContactField.js b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/ContactField.js index 9a0820019e1d..15ae3d4cf75e 100644 --- a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/ContactField.js +++ b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/ContactField.js @@ -1,6 +1,6 @@ import { Avatar, Box } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; -import React from 'react'; +import React, { useMemo } from 'react'; import UserCard from '../../../../../components/UserCard'; import { UserStatus } from '../../../../../components/UserStatus'; @@ -18,7 +18,14 @@ const ContactField = ({ contact, room }) => { const { fname, t: type } = room; const avatarUrl = roomCoordinator.getRoomDirectives(type)?.getAvatarPath(room); - const { value: data, phase: state, error } = useEndpointData(`livechat/visitors.info?visitorId=${contact._id}`); + const { + value: data, + phase: state, + error, + } = useEndpointData( + '/v1/livechat/visitors.info', + useMemo(() => ({ visitorId: contact._id }), [contact._id]), + ); if (state === AsyncStatePhase.LOADING) { return ; diff --git a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/DepartmentField.js b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/DepartmentField.js index 8425894bfd44..5e68b7abb586 100644 --- a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/DepartmentField.js +++ b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/DepartmentField.js @@ -10,7 +10,7 @@ import { FormSkeleton } from '../../Skeleton'; const DepartmentField = ({ departmentId }) => { const t = useTranslation(); - const { value: data, phase: state } = useEndpointData(`livechat/department/${departmentId}`); + const { value: data, phase: state } = useEndpointData(`/v1/livechat/department/${departmentId}`); if (state === AsyncStatePhase.LOADING) { return ; } diff --git a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/PriorityField.js b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/PriorityField.js index 04a389787cc3..66626c80715b 100644 --- a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/PriorityField.js +++ b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/PriorityField.js @@ -1,6 +1,6 @@ import { Box } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; -import React from 'react'; +import React, { useMemo } from 'react'; import { useEndpointData } from '../../../../../hooks/useEndpointData'; import { AsyncStatePhase } from '../../../../../lib/asyncState'; @@ -11,7 +11,14 @@ import { FormSkeleton } from '../../Skeleton'; const PriorityField = ({ id }) => { const t = useTranslation(); - const { value: data, phase: state, error } = useEndpointData(`livechat/priorities.getOne?priorityId=${id}`); + const { + value: data, + phase: state, + error, + } = useEndpointData( + '/v1/livechat/priorities.getOne', + useMemo(() => ({ priorityId: id }), [id]), + ); if (state === AsyncStatePhase.LOADING) { return ; } diff --git a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/RoomEdit.js b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/RoomEdit.js index 81530fda9a53..0f4a5578c4b3 100644 --- a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/RoomEdit.js +++ b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/RoomEdit.js @@ -64,8 +64,8 @@ function RoomEdit({ room, visitor, reload, reloadInfo, close }) { const [customFieldsError, setCustomFieldsError] = useState([]); - const { value: allCustomFields, phase: stateCustomFields } = useEndpointData('livechat/custom-fields'); - const { value: prioritiesResult = {}, phase: statePriorities } = useEndpointData('livechat/priorities.list'); + const { value: allCustomFields, phase: stateCustomFields } = useEndpointData('/v1/livechat/custom-fields'); + const { value: prioritiesResult = {}, phase: statePriorities } = useEndpointData('/v1/livechat/priorities.list'); const jsonConverterToValidFormat = (customFields) => { const jsonObj = {}; diff --git a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/RoomEditWithData.js b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/RoomEditWithData.js index 9142e9f1ea2e..b4c566db5181 100644 --- a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/RoomEditWithData.js +++ b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/RoomEditWithData.js @@ -1,6 +1,6 @@ import { Box } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; -import React from 'react'; +import React, { useMemo } from 'react'; import { AsyncStatePhase } from '../../../../../hooks/useAsyncState'; import { useEndpointData } from '../../../../../hooks/useEndpointData'; @@ -10,7 +10,14 @@ import VisitorData from './VisitorData'; function RoomEditWithData({ id, reload, reloadInfo, close }) { const t = useTranslation(); - const { value: roomData, phase: state, error } = useEndpointData(`rooms.info?roomId=${id}`); + const { + value: roomData, + phase: state, + error, + } = useEndpointData( + '/v1/rooms.info', + useMemo(() => ({ roomId: id }), [id]), + ); if ([state].includes(AsyncStatePhase.LOADING)) { return ; diff --git a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/VisitorClientInfo.js b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/VisitorClientInfo.js index c68d9dce8216..3dce914bb3b7 100644 --- a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/VisitorClientInfo.js +++ b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/VisitorClientInfo.js @@ -1,5 +1,5 @@ import { useTranslation } from '@rocket.chat/ui-contexts'; -import React from 'react'; +import React, { useMemo } from 'react'; import UAParser from 'ua-parser-js'; import { useEndpointData } from '../../../../../hooks/useEndpointData'; @@ -11,7 +11,14 @@ import { FormSkeleton } from '../../Skeleton'; const VisitorClientInfo = ({ uid }) => { const t = useTranslation(); - const { value: userData, phase: state, error } = useEndpointData(`livechat/visitors.info?visitorId=${uid}`); + const { + value: userData, + phase: state, + error, + } = useEndpointData( + '/v1/livechat/visitors.info', + useMemo(() => ({ visitorId: uid }), [uid]), + ); if (state === AsyncStatePhase.LOADING) { return ; } diff --git a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/VisitorData.js b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/VisitorData.js index af665d5b227f..7d2b78a4bb26 100644 --- a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/VisitorData.js +++ b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/VisitorData.js @@ -1,6 +1,6 @@ import { Box } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; -import React from 'react'; +import React, { useMemo } from 'react'; import { AsyncStatePhase } from '../../../../../hooks/useAsyncState'; import { useEndpointData } from '../../../../../hooks/useEndpointData'; @@ -16,7 +16,14 @@ function VisitorData({ room, reload, reloadInfo, close }) { }, } = room; - const { value: visitor, phase: stateVisitor, error: errorVisitor } = useEndpointData(`livechat/visitors.info?visitorId=${_id}`); + const { + value: visitor, + phase: stateVisitor, + error: errorVisitor, + } = useEndpointData( + '/v1/livechat/visitors.info', + useMemo(() => ({ visitorId: _id }), [_id]), + ); if ([stateVisitor].includes(AsyncStatePhase.LOADING)) { return ; diff --git a/apps/meteor/client/views/omnichannel/directory/contacts/ContactTable.js b/apps/meteor/client/views/omnichannel/directory/contacts/ContactTable.js index 1a400255e61f..b824f3a67185 100644 --- a/apps/meteor/client/views/omnichannel/directory/contacts/ContactTable.js +++ b/apps/meteor/client/views/omnichannel/directory/contacts/ContactTable.js @@ -55,7 +55,7 @@ function ContactTable({ setContactReload }) { }), ); - const { value: data, reload } = useEndpointData('livechat/visitors.search', query); + const { value: data, reload } = useEndpointData('/v1/livechat/visitors.search', query); useEffect(() => { setContactReload(() => reload); diff --git a/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactEditWithData.js b/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactEditWithData.js index 7f6fa1dd52bd..24c95fa64b35 100644 --- a/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactEditWithData.js +++ b/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactEditWithData.js @@ -1,6 +1,6 @@ import { Box } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; -import React from 'react'; +import React, { useMemo } from 'react'; import { AsyncStatePhase } from '../../../../../hooks/useAsyncState'; import { useEndpointData } from '../../../../../hooks/useEndpointData'; @@ -9,7 +9,14 @@ import ContactNewEdit from './ContactNewEdit'; function ContactEditWithData({ id, close }) { const t = useTranslation(); - const { value: data, phase: state, error } = useEndpointData(`omnichannel/contact?contactId=${id}`); // TODO OMNICHANNEL + const { + value: data, + phase: state, + error, + } = useEndpointData( + '/v1/omnichannel/contact', + useMemo(() => ({ contactId: id }), [id]), + ); if ([state].includes(AsyncStatePhase.LOADING)) { return ; diff --git a/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactInfo.js b/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactInfo.js index 21f6bf43bac8..6e815bdf63d9 100644 --- a/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactInfo.js +++ b/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactInfo.js @@ -1,7 +1,7 @@ import { Box, Margins, ButtonGroup, Button, Icon } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useToastMessageDispatch, useCurrentRoute, useRoute, useTranslation } from '@rocket.chat/ui-contexts'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { hasPermission } from '../../../../../../app/authorization/client'; import ContactManagerInfo from '../../../../../../ee/client/omnichannel/ContactManagerInfo'; @@ -22,7 +22,7 @@ const ContactInfo = ({ id, rid, route }) => { const t = useTranslation(); const routePath = useRoute(route || 'omnichannel-directory'); - const { value: allCustomFields, phase: stateCustomFields } = useEndpointData('livechat/custom-fields'); + const { value: allCustomFields, phase: stateCustomFields } = useEndpointData('/v1/livechat/custom-fields'); const [customFields, setCustomFields] = useState([]); @@ -59,7 +59,14 @@ const ContactInfo = ({ id, rid, route }) => { } }, [allCustomFields, stateCustomFields]); - const { value: data, phase: state, error } = useEndpointData(`omnichannel/contact?contactId=${id}`); + const { + value: data, + phase: state, + error, + } = useEndpointData( + '/v1/omnichannel/contact', + useMemo(() => ({ contactId: id }), [id]), + ); const [currentRouteName] = useCurrentRoute(); const liveRoute = useRoute('live'); diff --git a/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactNewEdit.js b/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactNewEdit.js index be8159d46ce6..6380f16ab565 100644 --- a/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactNewEdit.js +++ b/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactNewEdit.js @@ -75,7 +75,7 @@ function ContactNewEdit({ id, data, close }) { const [phoneError, setPhoneError] = useState(); const [customFieldsError, setCustomFieldsError] = useState([]); - const { value: allCustomFields, phase: state } = useEndpointData('livechat/custom-fields'); + const { value: allCustomFields, phase: state } = useEndpointData('/v1/livechat/custom-fields'); const jsonConverterToValidFormat = (customFields) => { const jsonObj = {}; @@ -97,9 +97,17 @@ function ContactNewEdit({ id, data, close }) { [allCustomFields], ); - const saveContact = useEndpoint('POST', 'omnichannel/contact'); - const emailAlreadyExistsAction = useEndpoint('GET', `omnichannel/contact.search?email=${email}`); - const phoneAlreadyExistsAction = useEndpoint('GET', `omnichannel/contact.search?phone=${phone}`); + const saveContact = useEndpoint('POST', '/v1/omnichannel/contact'); + const emailAlreadyExistsAction = useEndpoint( + 'GET', + '/v1/omnichannel/contact.search', + useMemo(() => ({ email }), [email]), + ); + const phoneAlreadyExistsAction = useEndpoint( + 'GET', + '/v1/omnichannel/contact.search', + useMemo(() => ({ phone }), [phone]), + ); const checkEmailExists = useMutableCallback(async () => { if (!validateEmail(email)) { diff --git a/apps/meteor/client/views/omnichannel/managers/AddManager.tsx b/apps/meteor/client/views/omnichannel/managers/AddManager.tsx index 58e6d956afce..c904b02d5291 100644 --- a/apps/meteor/client/views/omnichannel/managers/AddManager.tsx +++ b/apps/meteor/client/views/omnichannel/managers/AddManager.tsx @@ -10,7 +10,7 @@ const AddManager = ({ reload }: { reload: () => void }): ReactElement => { const t = useTranslation(); const [username, setUsername] = useState(''); - const saveAction = useEndpointAction('POST', 'livechat/users/manager', { username }); + const saveAction = useEndpointAction('POST', '/v1/livechat/users/manager', { username }); const handleSave = useMutableCallback(async () => { if (!username) { diff --git a/apps/meteor/client/views/omnichannel/managers/ManagersRoute.tsx b/apps/meteor/client/views/omnichannel/managers/ManagersRoute.tsx index 864e40debf77..c409f06c089e 100644 --- a/apps/meteor/client/views/omnichannel/managers/ManagersRoute.tsx +++ b/apps/meteor/client/views/omnichannel/managers/ManagersRoute.tsx @@ -42,7 +42,7 @@ const ManagersRoute = (): ReactElement => { 500, ); - const { reload, ...result } = useEndpointData('livechat/users/manager', query); + const { reload, ...result } = useEndpointData('/v1/livechat/users/manager', query); const canViewManagers = usePermission('manage-livechat-managers'); if (!canViewManagers) { diff --git a/apps/meteor/client/views/omnichannel/managers/RemoveManagerButton.tsx b/apps/meteor/client/views/omnichannel/managers/RemoveManagerButton.tsx index e1d428cb5e33..dd3083f2a467 100644 --- a/apps/meteor/client/views/omnichannel/managers/RemoveManagerButton.tsx +++ b/apps/meteor/client/views/omnichannel/managers/RemoveManagerButton.tsx @@ -8,7 +8,7 @@ import { useEndpointAction } from '../../../hooks/useEndpointAction'; const RemoveManagerButton = ({ _id, reload }: { _id: string; reload: () => void }): ReactElement => { const t = useTranslation(); - const deleteAction = useEndpointAction('DELETE', `livechat/users/manager/${_id}`); + const deleteAction = useEndpointAction('DELETE', `/v1/livechat/users/manager/${_id}`); const setModal = useSetModal(); const dispatchToastMessage = useToastMessageDispatch(); diff --git a/apps/meteor/client/views/omnichannel/queueList/index.tsx b/apps/meteor/client/views/omnichannel/queueList/index.tsx index e199e54cbbbb..9603d855cb6b 100644 --- a/apps/meteor/client/views/omnichannel/queueList/index.tsx +++ b/apps/meteor/client/views/omnichannel/queueList/index.tsx @@ -97,7 +97,7 @@ const QueueList = (): ReactElement => { const debouncedParams = useDebouncedValue(params, 500); const debouncedSort = useDebouncedValue(sort, 500); const query = useQuery(debouncedParams, debouncedSort); - const { value: data } = useEndpointData('livechat/queue', query); + const { value: data } = useEndpointData('/v1/livechat/queue', query); return ( diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.js b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.js index 6012edcfc95a..71c2edee9c54 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.js +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/AgentStatusChart.js @@ -38,7 +38,7 @@ const AgentStatusChart = ({ params, reloadRef, ...props }) => { init, }); - const { value: data, phase: state, reload } = useEndpointData('livechat/analytics/dashboards/charts/agents-status', params); + const { value: data, phase: state, reload } = useEndpointData('/v1/livechat/analytics/dashboards/charts/agents-status', params); reloadRef.current.agentStatusChart = reload; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatDurationChart.js b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatDurationChart.js index c7a727b6d319..f1dc8f1072d9 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatDurationChart.js +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatDurationChart.js @@ -45,7 +45,7 @@ const ChatDurationChart = ({ params, reloadRef, ...props }) => { init, }); - const { value: data, phase: state, reload } = useEndpointData('livechat/analytics/dashboards/charts/timings', params); + const { value: data, phase: state, reload } = useEndpointData('/v1/livechat/analytics/dashboards/charts/timings', params); reloadRef.current.chatDurationChart = reload; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.js b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.js index 239b1bb2815a..5cb49cafb013 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.js +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsChart.js @@ -38,7 +38,7 @@ const ChatsChart = ({ params, reloadRef, ...props }) => { init, }); - const { value: data, phase: state, reload } = useEndpointData('livechat/analytics/dashboards/charts/chats', params); + const { value: data, phase: state, reload } = useEndpointData('/v1/livechat/analytics/dashboards/charts/chats', params); reloadRef.current.chatsChart = reload; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerAgentChart.js b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerAgentChart.js index ea8156f2ed2c..4a2bbccab60e 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerAgentChart.js +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerAgentChart.js @@ -31,7 +31,7 @@ const ChatsPerAgentChart = ({ params, reloadRef, ...props }) => { init, }); - const { value: data, phase: state, reload } = useEndpointData('livechat/analytics/dashboards/charts/chats-per-agent', params); + const { value: data, phase: state, reload } = useEndpointData('/v1/livechat/analytics/dashboards/charts/chats-per-agent', params); reloadRef.current.chatsPerAgentChart = reload; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerDepartmentChart.js b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerDepartmentChart.js index 09658ebe870f..55ba495d412a 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerDepartmentChart.js +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ChatsPerDepartmentChart.js @@ -31,7 +31,7 @@ const ChatsPerDepartmentChart = ({ params, reloadRef, ...props }) => { init, }); - const { value: data, phase: state, reload } = useEndpointData('livechat/analytics/dashboards/charts/chats-per-department', params); + const { value: data, phase: state, reload } = useEndpointData('/v1/livechat/analytics/dashboards/charts/chats-per-department', params); reloadRef.current.chatsPerDepartmentChart = reload; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ResponseTimesChart.js b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ResponseTimesChart.js index 903a37969cc1..04a2cf1af0b7 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ResponseTimesChart.js +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/charts/ResponseTimesChart.js @@ -46,7 +46,7 @@ const ResponseTimesChart = ({ params, reloadRef, ...props }) => { init, }); - const { value: data, phase: state, reload } = useEndpointData('livechat/analytics/dashboards/charts/timings', params); + const { value: data, phase: state, reload } = useEndpointData('/v1/livechat/analytics/dashboards/charts/timings', params); reloadRef.current.responseTimesChart = reload; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/overviews/AgentsOverview.js b/apps/meteor/client/views/omnichannel/realTimeMonitoring/overviews/AgentsOverview.js index dd52d328b687..f0c4fdc353bb 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/overviews/AgentsOverview.js +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/overviews/AgentsOverview.js @@ -11,7 +11,7 @@ const overviewInitalValue = { const initialData = [overviewInitalValue, overviewInitalValue, overviewInitalValue]; const AgentsOverview = ({ params, reloadRef, ...props }) => { - const { value: data, phase: state, reload } = useEndpointData('livechat/analytics/dashboards/agents-productivity-totalizers', params); + const { value: data, phase: state, reload } = useEndpointData('/v1/livechat/analytics/dashboards/agents-productivity-totalizers', params); reloadRef.current.agentsOverview = reload; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/overviews/ChatsOverview.js b/apps/meteor/client/views/omnichannel/realTimeMonitoring/overviews/ChatsOverview.js index 123c1924fc91..21912c1666cc 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/overviews/ChatsOverview.js +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/overviews/ChatsOverview.js @@ -10,7 +10,7 @@ const initialData = [ ]; const ChatsOverview = ({ params, reloadRef, ...props }) => { - const { value: data, phase: state, reload } = useEndpointData('livechat/analytics/dashboards/chats-totalizers', params); + const { value: data, phase: state, reload } = useEndpointData('/v1/livechat/analytics/dashboards/chats-totalizers', params); reloadRef.current.chatsOverview = reload; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/overviews/ConversationOverview.js b/apps/meteor/client/views/omnichannel/realTimeMonitoring/overviews/ConversationOverview.js index 4007fde085ec..141f50968d77 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/overviews/ConversationOverview.js +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/overviews/ConversationOverview.js @@ -11,7 +11,7 @@ const overviewInitalValue = { const initialData = [overviewInitalValue, overviewInitalValue, overviewInitalValue, overviewInitalValue]; const ConversationOverview = ({ params, reloadRef, ...props }) => { - const { value: data, phase: state, reload } = useEndpointData('livechat/analytics/dashboards/conversation-totalizers', params); + const { value: data, phase: state, reload } = useEndpointData('/v1/livechat/analytics/dashboards/conversation-totalizers', params); reloadRef.current.conversationOverview = reload; diff --git a/apps/meteor/client/views/omnichannel/realTimeMonitoring/overviews/ProductivityOverview.js b/apps/meteor/client/views/omnichannel/realTimeMonitoring/overviews/ProductivityOverview.js index d244cb1c233b..9e76b9d3e337 100644 --- a/apps/meteor/client/views/omnichannel/realTimeMonitoring/overviews/ProductivityOverview.js +++ b/apps/meteor/client/views/omnichannel/realTimeMonitoring/overviews/ProductivityOverview.js @@ -8,7 +8,7 @@ const defaultValue = { title: '', value: '00:00:00' }; const initialData = [defaultValue, defaultValue, defaultValue, defaultValue]; const ProductivityOverview = ({ params, reloadRef, ...props }) => { - const { value: data, phase: state, reload } = useEndpointData('livechat/analytics/dashboards/productivity-totalizers', params); + const { value: data, phase: state, reload } = useEndpointData('/v1/livechat/analytics/dashboards/productivity-totalizers', params); reloadRef.current.productivityOverview = reload; diff --git a/apps/meteor/client/views/omnichannel/triggers/EditTriggerPageContainer.js b/apps/meteor/client/views/omnichannel/triggers/EditTriggerPageContainer.js index 74d2b54f4f20..96f76541d2db 100644 --- a/apps/meteor/client/views/omnichannel/triggers/EditTriggerPageContainer.js +++ b/apps/meteor/client/views/omnichannel/triggers/EditTriggerPageContainer.js @@ -9,7 +9,7 @@ import EditTriggerPage from './EditTriggerPage'; const EditTriggerPageContainer = ({ id, onSave }) => { const t = useTranslation(); - const { value: data, phase: state } = useEndpointData(`livechat/triggers/${id}`); + const { value: data, phase: state } = useEndpointData(`/v1/livechat/triggers/${id}`); if (state === AsyncStatePhase.LOADING) { return ; diff --git a/apps/meteor/client/views/omnichannel/triggers/TriggersTableContainer.js b/apps/meteor/client/views/omnichannel/triggers/TriggersTableContainer.js index c4f68a47213c..270f702bd8ef 100644 --- a/apps/meteor/client/views/omnichannel/triggers/TriggersTableContainer.js +++ b/apps/meteor/client/views/omnichannel/triggers/TriggersTableContainer.js @@ -17,7 +17,7 @@ const TriggersTableContainer = ({ reloadRef }) => { phase: state, reload, } = useEndpointData( - 'livechat/triggers', + '/v1/livechat/triggers', useMemo(() => ({ offset: current, count: itemsPerPage }), [current, itemsPerPage]), ); diff --git a/apps/meteor/client/views/omnichannel/webhooks/WebhooksPageContainer.js b/apps/meteor/client/views/omnichannel/webhooks/WebhooksPageContainer.js index 970a636a726e..d1e7379e1c3c 100644 --- a/apps/meteor/client/views/omnichannel/webhooks/WebhooksPageContainer.js +++ b/apps/meteor/client/views/omnichannel/webhooks/WebhooksPageContainer.js @@ -18,7 +18,7 @@ const reduceSettings = (settings) => const WebhooksPageContainer = () => { const t = useTranslation(); - const { value: data, phase: state, error } = useEndpointData('livechat/integrations.settings'); + const { value: data, phase: state, error } = useEndpointData('/v1/livechat/integrations.settings'); const canViewLivechatWebhooks = usePermission('view-livechat-webhooks'); diff --git a/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx b/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx index 5d354e35da0a..12a5dc517e19 100644 --- a/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx +++ b/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx @@ -48,7 +48,7 @@ export const useQuickActions = ( const uid = useUserId(); const roomLastMessage = room.lastMessage; - const getVisitorInfo = useEndpoint('GET', 'livechat/visitors.info'); + const getVisitorInfo = useEndpoint('GET', '/v1/livechat/visitors.info'); const getVisitorEmail = useMutableCallback(async () => { if (!visitorRoomId) { @@ -194,7 +194,7 @@ export const useQuickActions = ( [closeChat, closeModal, dispatchToastMessage, rid, t], ); - const onHoldChat = useEndpoint('POST', 'livechat/room.onHold'); + const onHoldChat = useEndpoint('POST', '/v1/livechat/room.onHold'); const handleOnHoldChat = useCallback(async () => { try { diff --git a/apps/meteor/client/views/room/Header/ParentRoomWithEndpointData.tsx b/apps/meteor/client/views/room/Header/ParentRoomWithEndpointData.tsx index 242dc6018940..fdcb48a4c2dc 100644 --- a/apps/meteor/client/views/room/Header/ParentRoomWithEndpointData.tsx +++ b/apps/meteor/client/views/room/Header/ParentRoomWithEndpointData.tsx @@ -12,7 +12,7 @@ type ParentRoomWithEndpointDataProps = { const ParentRoomWithEndpointData = ({ rid }: ParentRoomWithEndpointDataProps): ReactElement | null => { const { phase, value } = useEndpointData( - 'rooms.info', + '/v1/rooms.info', useMemo(() => ({ roomId: rid }), [rid]), ); diff --git a/apps/meteor/client/views/room/Header/ParentTeam.tsx b/apps/meteor/client/views/room/Header/ParentTeam.tsx index 4286c99695c3..1eb63ae2c214 100644 --- a/apps/meteor/client/views/room/Header/ParentTeam.tsx +++ b/apps/meteor/client/views/room/Header/ParentTeam.tsx @@ -25,12 +25,12 @@ const ParentTeam = ({ room }: ParentTeamProps): ReactElement | null => { } const { value, phase } = useEndpointData( - 'teams.info', + '/v1/teams.info', useMemo(() => ({ teamId }), [teamId]), ); const { value: userTeams, phase: userTeamsPhase } = useEndpointData( - 'users.listTeams', + '/v1/users.listTeams', useMemo(() => ({ userId }), [userId]), ); diff --git a/apps/meteor/client/views/room/MessageList/providers/MessageListProvider.tsx b/apps/meteor/client/views/room/MessageList/providers/MessageListProvider.tsx index b711e492b969..999e295ab1cc 100644 --- a/apps/meteor/client/views/room/MessageList/providers/MessageListProvider.tsx +++ b/apps/meteor/client/views/room/MessageList/providers/MessageListProvider.tsx @@ -13,7 +13,7 @@ const fields = {}; export const MessageListProvider: FC<{ rid: IRoom['_id']; }> = memo(function MessageListProvider({ rid, ...props }) { - const reactToMessage = useEndpoint('POST', 'chat.react'); + const reactToMessage = useEndpoint('POST', '/v1/chat.react'); const user = useUser(); const uid = user?._id; const username = user?.username; diff --git a/apps/meteor/client/views/room/UserCard/index.js b/apps/meteor/client/views/room/UserCard/index.js index 34701b45af9b..839326f8fefd 100644 --- a/apps/meteor/client/views/room/UserCard/index.js +++ b/apps/meteor/client/views/room/UserCard/index.js @@ -23,7 +23,7 @@ const UserCardWithData = ({ username, onClose, target, open, rid }) => { const query = useMemo(() => ({ username }), [username]); - const { value: data, phase: state } = useEndpointData('users.info', query); + const { value: data, phase: state } = useEndpointData('/v1/users.info', query); ref.current = target; diff --git a/apps/meteor/client/views/room/contextualBar/AutoTranslate/AutoTranslateWithData.tsx b/apps/meteor/client/views/room/contextualBar/AutoTranslate/AutoTranslateWithData.tsx index f34171b39422..d159bc219e91 100644 --- a/apps/meteor/client/views/room/contextualBar/AutoTranslate/AutoTranslateWithData.tsx +++ b/apps/meteor/client/views/room/contextualBar/AutoTranslate/AutoTranslateWithData.tsx @@ -13,10 +13,10 @@ const AutoTranslateWithData = ({ rid }: { rid: IRoom['_id'] }): ReactElement => const userLanguage = useLanguage(); const subscription = useUserSubscription(rid); const [currentLanguage, setCurrentLanguage] = useState(subscription?.autoTranslateLanguage ?? ''); - const saveSettings = useEndpointActionExperimental('POST', 'autotranslate.saveSettings'); + const saveSettings = useEndpointActionExperimental('POST', '/v1/autotranslate.saveSettings'); const { value: translateData } = useEndpointData( - 'autotranslate.getSupportedLanguages', + '/v1/autotranslate.getSupportedLanguages', useMemo(() => ({ targetLanguage: userLanguage }), [userLanguage]), ); diff --git a/apps/meteor/client/views/room/contextualBar/Discussions/useDiscussionsList.ts b/apps/meteor/client/views/room/contextualBar/Discussions/useDiscussionsList.ts index fadc1684ecf5..5e5837da0960 100644 --- a/apps/meteor/client/views/room/contextualBar/Discussions/useDiscussionsList.ts +++ b/apps/meteor/client/views/room/contextualBar/Discussions/useDiscussionsList.ts @@ -17,7 +17,7 @@ export const useDiscussionsList = ( } => { const discussionsList = useMemo(() => new DiscussionsList(options), [options]); - const getDiscussions = useEndpoint('GET', 'chat.getDiscussions'); + const getDiscussions = useEndpoint('GET', '/v1/chat.getDiscussions'); const fetchMessages = useCallback( async (start, end) => { diff --git a/apps/meteor/client/views/room/contextualBar/ExportMessages/FileExport.tsx b/apps/meteor/client/views/room/contextualBar/ExportMessages/FileExport.tsx index a15f7a8bec29..ba29b585698b 100644 --- a/apps/meteor/client/views/room/contextualBar/ExportMessages/FileExport.tsx +++ b/apps/meteor/client/views/room/contextualBar/ExportMessages/FileExport.tsx @@ -34,7 +34,7 @@ const FileExport: FC = ({ onCancel, rid }) => { [t], ); - const roomsExport = useEndpoint('POST', 'rooms.export'); + const roomsExport = useEndpoint('POST', '/v1/rooms.export'); const dispatchToastMessage = useToastMessageDispatch(); diff --git a/apps/meteor/client/views/room/contextualBar/ExportMessages/MailExportForm.tsx b/apps/meteor/client/views/room/contextualBar/ExportMessages/MailExportForm.tsx index d76b10d04cbc..ae7e164d5998 100644 --- a/apps/meteor/client/views/room/contextualBar/ExportMessages/MailExportForm.tsx +++ b/apps/meteor/client/views/room/contextualBar/ExportMessages/MailExportForm.tsx @@ -94,7 +94,7 @@ const MailExportForm: FC = ({ onCancel, rid }) => { handleToUsers(toUsers.filter((current) => current !== value)); }); - const roomsExport = useEndpoint('POST', 'rooms.export'); + const roomsExport = useEndpoint('POST', '/v1/rooms.export'); const handleSubmit = async (): Promise => { if (toUsers.length === 0 && additionalEmails === '') { diff --git a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditChannel.js b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditChannel.js index 7486de4e0839..354fb74d9978 100644 --- a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditChannel.js +++ b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditChannel.js @@ -224,8 +224,8 @@ function EditChannel({ room, onClickClose, onClickBack }) { const changeArchivation = archived !== !!room.archived; const archiveSelector = room.archived ? 'unarchive' : 'archive'; const archiveMessage = room.archived ? 'Room_has_been_unarchived' : 'Room_has_been_archived'; - const saveAction = useEndpointActionExperimental('POST', 'rooms.saveRoomSettings', t('Room_updated_successfully')); - const archiveAction = useEndpointActionExperimental('POST', 'rooms.changeArchivationState', t(archiveMessage)); + const saveAction = useEndpointActionExperimental('POST', '/v1/rooms.saveRoomSettings', t('Room_updated_successfully')); + const archiveAction = useEndpointActionExperimental('POST', '/v1/rooms.changeArchivationState', t(archiveMessage)); const handleSave = useMutableCallback(async () => { const { joinCodeRequired, hideSysMes, ...data } = saveData.current; diff --git a/apps/meteor/client/views/room/contextualBar/PruneMessages/PruneMessagesWithData.tsx b/apps/meteor/client/views/room/contextualBar/PruneMessages/PruneMessagesWithData.tsx index dbf3f00b4cd3..cacbb05d349d 100644 --- a/apps/meteor/client/views/room/contextualBar/PruneMessages/PruneMessagesWithData.tsx +++ b/apps/meteor/client/views/room/contextualBar/PruneMessages/PruneMessagesWithData.tsx @@ -37,7 +37,7 @@ const PruneMessagesWithData = ({ rid, tabBar }: { rid: IRoom['_id']; tabBar: Too const onClickClose = useMutableCallback(() => tabBar?.close()); const closeModal = useCallback(() => setModal(null), [setModal]); const dispatchToastMessage = useToastMessageDispatch(); - const pruneMessagesAction = useEndpoint('POST', 'rooms.cleanHistory'); + const pruneMessagesAction = useEndpoint('POST', '/v1/rooms.cleanHistory'); const [fromDate, setFromDate] = useState(new Date('0001-01-01T00:00:00Z')); const [toDate, setToDate] = useState(new Date('9999-12-31T23:59:59Z')); diff --git a/apps/meteor/client/views/room/contextualBar/RoomFiles/hooks/useFilesList.ts b/apps/meteor/client/views/room/contextualBar/RoomFiles/hooks/useFilesList.ts index 23102f11c601..fe43a86d5e19 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomFiles/hooks/useFilesList.ts +++ b/apps/meteor/client/views/room/contextualBar/RoomFiles/hooks/useFilesList.ts @@ -32,14 +32,14 @@ export const useFilesList = ( }, [filesList, options]); const roomTypes = { - c: 'channels.files', - l: 'channels.files', - v: 'channels.files', - d: 'im.files', - p: 'groups.files', + c: '/v1/channels.files', + l: '/v1/channels.files', + v: '/v1/channels.files', + d: '/v1/im.files', + p: '/v1/groups.files', } as const; - const apiEndPoint = room ? roomTypes[room.t] : 'channels.files'; + const apiEndPoint = room ? roomTypes[room.t] : '/v1/channels.files'; const getFiles = useEndpoint('GET', apiEndPoint); diff --git a/apps/meteor/client/views/room/contextualBar/RoomMembers/InviteUsers/WrappedInviteUsers.js b/apps/meteor/client/views/room/contextualBar/RoomMembers/InviteUsers/WrappedInviteUsers.js index 66b5ddf2c3cc..9f821423e64d 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomMembers/InviteUsers/WrappedInviteUsers.js +++ b/apps/meteor/client/views/room/contextualBar/RoomMembers/InviteUsers/WrappedInviteUsers.js @@ -17,7 +17,7 @@ const WrappedInviteUsers = ({ rid, tabBar, onClickBack }) => { const handleEdit = useMutableCallback(() => setEditing(true)); const onClickBackEditing = useMutableCallback(() => setEditing(false)); - const findOrCreateInvite = useEndpoint('POST', 'findOrCreateInvite'); + const findOrCreateInvite = useEndpoint('POST', '/v1/findOrCreateInvite'); const [{ days = 1, maxUses = 0 }, setDayAndMaxUses] = useState({}); diff --git a/apps/meteor/client/views/room/contextualBar/Threads/useThreadsList.ts b/apps/meteor/client/views/room/contextualBar/Threads/useThreadsList.ts index 71454275a0f2..5477e009187f 100644 --- a/apps/meteor/client/views/room/contextualBar/Threads/useThreadsList.ts +++ b/apps/meteor/client/views/room/contextualBar/Threads/useThreadsList.ts @@ -17,7 +17,7 @@ export const useThreadsList = ( } => { const threadsList = useMemo(() => new ThreadsList(options), [options]); - const getThreadsList = useEndpoint('GET', 'chat.getThreadsList'); + const getThreadsList = useEndpoint('GET', '/v1/chat.getThreadsList'); const fetchMessages = useCallback( async (start, end) => { diff --git a/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoWithData.js b/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoWithData.js index a327dab0ea4c..b902bf13213c 100644 --- a/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoWithData.js +++ b/apps/meteor/client/views/room/contextualBar/UserInfo/UserInfoWithData.js @@ -27,7 +27,7 @@ function UserInfoWithData({ uid, username, tabBar, rid, onClickClose, onClose = phase: state, error, } = useEndpointData( - 'users.info', + '/v1/users.info', useMemo(() => ({ ...(uid && { userId: uid }), ...(username && { username }) }), [uid, username]), ); diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions.js b/apps/meteor/client/views/room/hooks/useUserInfoActions.js index 9f5f61b91b63..fc6fdabd4a1f 100644 --- a/apps/meteor/client/views/room/hooks/useUserInfoActions.js +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions.js @@ -73,7 +73,7 @@ const WarningModal = ({ text, confirmText, close, confirm, ...props }) => { ); }; - +// TODO: Remove endpoint concatenation export const useUserInfoActions = (user = {}, rid, reload) => { const t = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); @@ -99,7 +99,7 @@ export const useUserInfoActions = (user = {}, rid, reload) => { const isIgnored = currentSubscription && currentSubscription.ignored && currentSubscription.ignored.indexOf(uid) > -1; const isMuted = getUserIsMuted(room, user, otherUserCanPostReadonly); - const endpointPrefix = room.t === 'p' ? 'groups' : 'channels'; + const endpointPrefix = room.t === 'p' ? '/v1/groups' : '/v1/channels'; const roomDirectives = room && room.t && roomCoordinator.getRoomDirectives(room.t); diff --git a/apps/meteor/client/views/room/threads/ThreadComponent.tsx b/apps/meteor/client/views/room/threads/ThreadComponent.tsx index e8f7a28c5862..ed123346152a 100644 --- a/apps/meteor/client/views/room/threads/ThreadComponent.tsx +++ b/apps/meteor/client/views/room/threads/ThreadComponent.tsx @@ -18,7 +18,7 @@ const subscriptionFields = {}; const useThreadMessage = (tmid: string): IMessage => { const [message, setMessage] = useState(() => Tracker.nonreactive(() => ChatMessage.findOne({ _id: tmid }))); - const getMessage = useEndpoint('GET', 'chat.getMessage'); + const getMessage = useEndpoint('GET', '/v1/chat.getMessage'); const getMessageParsed = useCallback<(params: { msgId: IMessage['_id'] }) => Promise>( async (params) => { const { message } = await getMessage(params); diff --git a/apps/meteor/client/views/setupWizard/providers/SetupWizardProvider.tsx b/apps/meteor/client/views/setupWizard/providers/SetupWizardProvider.tsx index ac5b00e47771..692854ba5192 100644 --- a/apps/meteor/client/views/setupWizard/providers/SetupWizardProvider.tsx +++ b/apps/meteor/client/views/setupWizard/providers/SetupWizardProvider.tsx @@ -54,7 +54,7 @@ const SetupWizardProvider = ({ children }: { children: ReactElement }): ReactEle const defineUsername = useMethod('setUsername'); const loginWithPassword = useLoginWithPassword(); const setForceLogin = useSessionDispatch('forceLogin'); - const createRegistrationIntent = useEndpoint('POST', 'cloud.createRegistrationIntent'); + const createRegistrationIntent = useEndpoint('POST', '/v1/cloud.createRegistrationIntent'); const goToPreviousStep = useCallback(() => setCurrentStep((currentStep) => currentStep - 1), [setCurrentStep]); const goToNextStep = useCallback(() => setCurrentStep((currentStep) => currentStep + 1), [setCurrentStep]); diff --git a/apps/meteor/client/views/setupWizard/steps/CloudAccountConfirmation.tsx b/apps/meteor/client/views/setupWizard/steps/CloudAccountConfirmation.tsx index f54b441dafa8..4d5b73c00888 100644 --- a/apps/meteor/client/views/setupWizard/steps/CloudAccountConfirmation.tsx +++ b/apps/meteor/client/views/setupWizard/steps/CloudAccountConfirmation.tsx @@ -14,7 +14,7 @@ const CloudAccountConfirmation = (): ReactElement => { saveWorkspaceData, } = useSetupWizardContext(); const setShowSetupWizard = useSettingSetValue('Show_Setup_Wizard'); - const cloudConfirmationPoll = useEndpoint('GET', 'cloud.confirmationPoll'); + const cloudConfirmationPoll = useEndpoint('GET', '/v1/cloud.confirmationPoll'); const dispatchToastMessage = useToastMessageDispatch(); const t = useTranslation(); diff --git a/apps/meteor/client/views/teams/ConvertToChannelModal/ConvertToChannelModal.tsx b/apps/meteor/client/views/teams/ConvertToChannelModal/ConvertToChannelModal.tsx index 219fab067cb5..422060b888a6 100644 --- a/apps/meteor/client/views/teams/ConvertToChannelModal/ConvertToChannelModal.tsx +++ b/apps/meteor/client/views/teams/ConvertToChannelModal/ConvertToChannelModal.tsx @@ -21,7 +21,7 @@ const ConvertToChannelModal: FC = ({ onClose, onCanc const t = useTranslation(); const { value, phase } = useEndpointData( - 'teams.listRoomsOfUser', + '/v1/teams.listRoomsOfUser', useMemo(() => ({ teamId, userId, canUserDelete: 'true' }), [teamId, userId]), ); diff --git a/apps/meteor/client/views/teams/CreateTeamModal/CreateTeamModal.tsx b/apps/meteor/client/views/teams/CreateTeamModal/CreateTeamModal.tsx index 00a579640286..c035fa4a7134 100644 --- a/apps/meteor/client/views/teams/CreateTeamModal/CreateTeamModal.tsx +++ b/apps/meteor/client/views/teams/CreateTeamModal/CreateTeamModal.tsx @@ -149,7 +149,7 @@ const useCreateTeamModalState = (onClose: () => void): CreateTeamModalState => { const canCreateTeam = usePermission('create-team'); const isCreateButtonEnabled = canSave && canCreateTeam; - const createTeam = useEndpointActionExperimental('POST', 'teams.create'); + const createTeam = useEndpointActionExperimental('POST', '/v1/teams.create'); const onCreate = useCallback(async () => { const params = { diff --git a/apps/meteor/client/views/teams/CreateTeamModal/UsersInput.tsx b/apps/meteor/client/views/teams/CreateTeamModal/UsersInput.tsx index 339b6565d6ed..13126e2c6838 100644 --- a/apps/meteor/client/views/teams/CreateTeamModal/UsersInput.tsx +++ b/apps/meteor/client/views/teams/CreateTeamModal/UsersInput.tsx @@ -19,7 +19,7 @@ const useUsersAutoComplete = (term: string): AutocompleteData => { }), [term], ); - const { value: data } = useEndpointData('users.autocomplete', params); + const { value: data } = useEndpointData('/v1/users.autocomplete', params); return useMemo(() => { if (!data) { diff --git a/apps/meteor/client/views/teams/contextualBar/TeamAutocomplete/TeamAutocomplete.js b/apps/meteor/client/views/teams/contextualBar/TeamAutocomplete/TeamAutocomplete.js index 5fa51fb5658a..0f06cb132b2c 100644 --- a/apps/meteor/client/views/teams/contextualBar/TeamAutocomplete/TeamAutocomplete.js +++ b/apps/meteor/client/views/teams/contextualBar/TeamAutocomplete/TeamAutocomplete.js @@ -8,7 +8,7 @@ const TeamAutocomplete = (props) => { const [filter, setFilter] = useState(''); const { value: data } = useEndpointData( - 'teams.autocomplete', + '/v1/teams.autocomplete', useMemo(() => ({ name: filter }), [filter]), ); diff --git a/apps/meteor/client/views/teams/contextualBar/channels/AddExistingModal/AddExistingModal.tsx b/apps/meteor/client/views/teams/contextualBar/channels/AddExistingModal/AddExistingModal.tsx index 7a0de057239c..156a85e0b93d 100644 --- a/apps/meteor/client/views/teams/contextualBar/channels/AddExistingModal/AddExistingModal.tsx +++ b/apps/meteor/client/views/teams/contextualBar/channels/AddExistingModal/AddExistingModal.tsx @@ -22,7 +22,7 @@ type AddExistingModalProps = { const useAddExistingModalState = (onClose: () => void, teamId: string, reload: () => void): AddExistingModalState => { const t = useTranslation(); - const addRoomEndpoint = useEndpoint('POST', 'teams.addRooms'); + const addRoomEndpoint = useEndpoint('POST', '/v1/teams.addRooms'); const dispatchToastMessage = useToastMessageDispatch(); const { values, handlers, hasUnsavedChanges } = useForm({ diff --git a/apps/meteor/client/views/teams/contextualBar/channels/AddExistingModal/RoomsInput.tsx b/apps/meteor/client/views/teams/contextualBar/channels/AddExistingModal/RoomsInput.tsx index 097a9adac959..3d2f63399760 100644 --- a/apps/meteor/client/views/teams/contextualBar/channels/AddExistingModal/RoomsInput.tsx +++ b/apps/meteor/client/views/teams/contextualBar/channels/AddExistingModal/RoomsInput.tsx @@ -26,7 +26,7 @@ const useRoomsAutoComplete = ( }), [name], ); - const { value: data } = useEndpointData('rooms.autocomplete.availableForTeams', params); + const { value: data } = useEndpointData('/v1/rooms.autocomplete.availableForTeams', params); const options = useMemo(() => { if (!data) { diff --git a/apps/meteor/client/views/teams/contextualBar/channels/hooks/useTeamsChannelList.ts b/apps/meteor/client/views/teams/contextualBar/channels/hooks/useTeamsChannelList.ts index 328d2007d628..a141f905b6d2 100644 --- a/apps/meteor/client/views/teams/contextualBar/channels/hooks/useTeamsChannelList.ts +++ b/apps/meteor/client/views/teams/contextualBar/channels/hooks/useTeamsChannelList.ts @@ -22,7 +22,7 @@ export const useTeamsChannelList = ( reload: () => void; loadMoreItems: (start: number, end: number) => void; } => { - const apiEndPoint = useEndpoint('GET', 'teams.listRooms'); + const apiEndPoint = useEndpoint('GET', '/v1/teams.listRooms'); const [teamsChannelList, setTeamsChannelList] = useState(() => new RecordList()); const reload = useCallback(() => setTeamsChannelList(new RecordList()), []); diff --git a/apps/meteor/client/views/teams/contextualBar/info/Delete/DeleteTeamModalWithRooms.tsx b/apps/meteor/client/views/teams/contextualBar/info/Delete/DeleteTeamModalWithRooms.tsx index 3625da876daa..48e9b34a1415 100644 --- a/apps/meteor/client/views/teams/contextualBar/info/Delete/DeleteTeamModalWithRooms.tsx +++ b/apps/meteor/client/views/teams/contextualBar/info/Delete/DeleteTeamModalWithRooms.tsx @@ -16,7 +16,7 @@ type DeleteTeamModalWithRoomsProps = { const DeleteTeamModalWithRooms = ({ teamId, onConfirm, onCancel }: DeleteTeamModalWithRoomsProps): ReactElement => { const { value, phase } = useEndpointData( - 'teams.listRooms', + '/v1/teams.listRooms', useMemo(() => ({ teamId }), [teamId]), ); diff --git a/apps/meteor/client/views/teams/contextualBar/info/Leave/index.js b/apps/meteor/client/views/teams/contextualBar/info/Leave/index.js index 90df97b6aa91..c559b2560310 100644 --- a/apps/meteor/client/views/teams/contextualBar/info/Leave/index.js +++ b/apps/meteor/client/views/teams/contextualBar/info/Leave/index.js @@ -14,7 +14,7 @@ const LeaveTeamModalWithRooms = ({ teamId, onCancel, onConfirm }) => { const userId = useUserId(); - const listRooms = useEndpoint('GET', 'teams.listRoomsOfUser'); + const listRooms = useEndpoint('GET', '/v1/teams.listRoomsOfUser'); const { resolve, reject, reset, phase, value } = useAsyncState([]); const fetchData = useCallback(() => { diff --git a/apps/meteor/client/views/teams/contextualBar/info/index.js b/apps/meteor/client/views/teams/contextualBar/info/index.js index 124ecadaf36c..f903923050e8 100644 --- a/apps/meteor/client/views/teams/contextualBar/info/index.js +++ b/apps/meteor/client/views/teams/contextualBar/info/index.js @@ -15,7 +15,7 @@ export default function TeamsInfoWithRooms({ rid }) { const t = useTranslation(); const params = useMemo(() => ({ roomId: rid }), [rid]); - const { phase, value, error } = useEndpointData('rooms.info', params); + const { phase, value, error } = useEndpointData('/v1/rooms.info', params); if (phase === AsyncStatePhase.LOADING) { return ; diff --git a/apps/meteor/client/views/teams/contextualBar/members/RemoveUsersModal/RemoveUsersModal.js b/apps/meteor/client/views/teams/contextualBar/members/RemoveUsersModal/RemoveUsersModal.js index 51e1085a1ce8..25bc54b72baa 100644 --- a/apps/meteor/client/views/teams/contextualBar/members/RemoveUsersModal/RemoveUsersModal.js +++ b/apps/meteor/client/views/teams/contextualBar/members/RemoveUsersModal/RemoveUsersModal.js @@ -12,11 +12,11 @@ const initialData = { user: { username: '' } }; const RemoveUsersModal = ({ teamId, userId, onClose, onCancel, onConfirm }) => { const t = useTranslation(); const { value, phase } = useEndpointData( - 'teams.listRoomsOfUser', + '/v1/teams.listRoomsOfUser', useMemo(() => ({ teamId, userId }), [teamId, userId]), ); const userDataFetch = useEndpointData( - 'users.info', + '/v1/users.info', useMemo(() => ({ userId }), [userId]), initialData, ); diff --git a/apps/meteor/definition/externals/meteor/accounts-base.d.ts b/apps/meteor/definition/externals/meteor/accounts-base.d.ts index 8f625c007331..a3f6e3151bba 100644 --- a/apps/meteor/definition/externals/meteor/accounts-base.d.ts +++ b/apps/meteor/definition/externals/meteor/accounts-base.d.ts @@ -17,5 +17,9 @@ declare module 'meteor/accounts-base' { export class LoginCancelledError extends Error { public static readonly numericError: number; } + + export const USER_ID_KEY: string; + + export const LOGIN_TOKEN_KEY: string; } } diff --git a/apps/meteor/definition/externals/meteor/session.d.ts b/apps/meteor/definition/externals/meteor/session.d.ts new file mode 100644 index 000000000000..9b65df618f04 --- /dev/null +++ b/apps/meteor/definition/externals/meteor/session.d.ts @@ -0,0 +1,7 @@ +declare module 'meteor/session' { + // eslint-disable-next-line @typescript-eslint/interface-name-prefix + namespace Session { + function _delete(key: string): void; + export { _delete as delete }; + } +} diff --git a/apps/meteor/ee/app/canned-responses/client/startup/responses.js b/apps/meteor/ee/app/canned-responses/client/startup/responses.js index a703390f3be4..3862de281836 100644 --- a/apps/meteor/ee/app/canned-responses/client/startup/responses.js +++ b/apps/meteor/ee/app/canned-responses/client/startup/responses.js @@ -34,7 +34,7 @@ Meteor.startup(() => { } events[response.type](response); }); - const { responses } = await APIClient.v1.get('canned-responses.get'); + const { responses } = await APIClient.get('/v1/canned-responses.get'); responses.forEach((response) => CannedResponse.insert(response)); c.stop(); } catch (error) { diff --git a/apps/meteor/ee/app/livechat-enterprise/client/views/app/customTemplates/visitorEditCustomFieldsForm.js b/apps/meteor/ee/app/livechat-enterprise/client/views/app/customTemplates/visitorEditCustomFieldsForm.js index 68a3deebf2e3..65b844dea979 100644 --- a/apps/meteor/ee/app/livechat-enterprise/client/views/app/customTemplates/visitorEditCustomFieldsForm.js +++ b/apps/meteor/ee/app/livechat-enterprise/client/views/app/customTemplates/visitorEditCustomFieldsForm.js @@ -24,6 +24,6 @@ Template.visitorEditCustomFieldsForm.onCreated(async function () { if (priorityId) { this.roomPriority.set(priorityId); } - const { priorities } = await APIClient.v1.get('livechat/priorities.list'); + const { priorities } = await APIClient.get('/v1/livechat/priorities.list'); this.priorities.set(priorities); }); diff --git a/apps/meteor/ee/app/livechat-enterprise/client/views/app/customTemplates/visitorInfoCustomForm.js b/apps/meteor/ee/app/livechat-enterprise/client/views/app/customTemplates/visitorInfoCustomForm.js index 0248764d9127..20e02feaebbc 100644 --- a/apps/meteor/ee/app/livechat-enterprise/client/views/app/customTemplates/visitorInfoCustomForm.js +++ b/apps/meteor/ee/app/livechat-enterprise/client/views/app/customTemplates/visitorInfoCustomForm.js @@ -20,7 +20,7 @@ Template.visitorInfoCustomForm.onCreated(function () { let priority; if (priorityId) { - priority = await APIClient.v1.get(`livechat/priorities.getOne?priorityId=${priorityId}`); + priority = await APIClient.get('/v1/livechat/priorities.getOne', { priorityId }); } this.priority.set(priority); diff --git a/apps/meteor/ee/client/audit/AuditPageBase.js b/apps/meteor/ee/client/audit/AuditPageBase.js index 4edb2d6ef96a..ca2540b98488 100644 --- a/apps/meteor/ee/client/audit/AuditPageBase.js +++ b/apps/meteor/ee/client/audit/AuditPageBase.js @@ -43,7 +43,7 @@ export const AuditPageBase = ({ handleType(type); }); - const eventStats = useEndpointAction('POST', 'statistics.telemetry', { + const eventStats = useEndpointAction('POST', '/v1/statistics.telemetry', { params: [{ eventName: 'updateCounter', settingsId: 'Message_Auditing_Apply_Count', timestamp: Date.now() }], }); diff --git a/apps/meteor/ee/client/audit/RoomAutoComplete/RoomAutoComplete.js b/apps/meteor/ee/client/audit/RoomAutoComplete/RoomAutoComplete.js index 47f361a64a30..f690814fed74 100644 --- a/apps/meteor/ee/client/audit/RoomAutoComplete/RoomAutoComplete.js +++ b/apps/meteor/ee/client/audit/RoomAutoComplete/RoomAutoComplete.js @@ -9,7 +9,7 @@ const query = (name = '') => ({ selector: JSON.stringify({ name }) }); const RoomAutoComplete = (props) => { const [filter, setFilter] = useState(''); const { value: data } = useEndpointData( - 'rooms.autocomplete.adminRooms', + '/v1/rooms.autocomplete.adminRooms', useMemo(() => query(filter), [filter]), ); const options = useMemo( diff --git a/apps/meteor/ee/client/audit/VisitorAutoComplete.js b/apps/meteor/ee/client/audit/VisitorAutoComplete.js index 71e0d5f23a2f..1bc9b22de288 100644 --- a/apps/meteor/ee/client/audit/VisitorAutoComplete.js +++ b/apps/meteor/ee/client/audit/VisitorAutoComplete.js @@ -8,7 +8,7 @@ const query = (term = '') => ({ selector: JSON.stringify({ term }) }); const VisitorAutoComplete = (props) => { const [filter, setFilter] = useState(''); const { value: data } = useEndpointData( - 'livechat/visitors.autocomplete', + '/v1/livechat/visitors.autocomplete', useMemo(() => query(filter), [filter]), ); const options = useMemo(() => (data && data.items.map((user) => ({ value: user._id, label: user.name }))) || [], [data]); diff --git a/apps/meteor/ee/client/ecdh.ts b/apps/meteor/ee/client/ecdh.ts index 32861f794e34..1aef97117ec1 100644 --- a/apps/meteor/ee/client/ecdh.ts +++ b/apps/meteor/ee/client/ecdh.ts @@ -65,18 +65,14 @@ async function initEncryptedSession(): Promise { } initEncryptedSession(); - -const _jqueryCall = APIClient._jqueryCall.bind(APIClient); - -APIClient._jqueryCall = async (method, endpoint, params, body, headers = {}): Promise => { +APIClient.use(async (request, next) => { const session = await sessionPromise; if (!session) { - return _jqueryCall(method, endpoint, params, body, headers); + return next(...request); } - - const result = await _jqueryCall(method, endpoint, params, body, headers, 'text'); + const result = await (await next(...request)).text(); const decrypted = await session.decrypt(result); const parsed = JSON.parse(decrypted); return parsed; -}; +}); diff --git a/apps/meteor/ee/client/hooks/useAgentsList.ts b/apps/meteor/ee/client/hooks/useAgentsList.ts index 63e6bdf4fb0a..e7acb5cca3c4 100644 --- a/apps/meteor/ee/client/hooks/useAgentsList.ts +++ b/apps/meteor/ee/client/hooks/useAgentsList.ts @@ -21,7 +21,7 @@ export const useAgentsList = ( const [itemsList, setItemsList] = useState(() => new RecordList()); const reload = useCallback(() => setItemsList(new RecordList()), []); - const getAgents = useEndpoint('GET', 'livechat/users/agent'); + const getAgents = useEndpoint('GET', '/v1/livechat/users/agent'); useComponentDidUpdate(() => { options && reload(); diff --git a/apps/meteor/ee/client/hooks/useTagsList.ts b/apps/meteor/ee/client/hooks/useTagsList.ts index d2f606b4dc22..2e3452cb46da 100644 --- a/apps/meteor/ee/client/hooks/useTagsList.ts +++ b/apps/meteor/ee/client/hooks/useTagsList.ts @@ -21,7 +21,7 @@ export const useTagsList = ( const [itemsList, setItemsList] = useState(() => new RecordList()); const reload = useCallback(() => setItemsList(new RecordList()), []); - const getTags = useEndpoint('GET', 'livechat/tags.list'); + const getTags = useEndpoint('GET', '/v1/livechat/tags.list'); useComponentDidUpdate(() => { options && reload(); diff --git a/apps/meteor/ee/client/lib/getFromRestApi.ts b/apps/meteor/ee/client/lib/getFromRestApi.ts index 6fa6800f217f..328264b530e0 100644 --- a/apps/meteor/ee/client/lib/getFromRestApi.ts +++ b/apps/meteor/ee/client/lib/getFromRestApi.ts @@ -1,4 +1,4 @@ -import { Serialized } from '@rocket.chat/core-typings'; +import type { Serialized } from '@rocket.chat/core-typings'; import type { MatchPathPattern, OperationParams, OperationResult, PathFor } from '@rocket.chat/rest-typings'; import { APIClient } from '../../../app/utils/client/lib/RestApiClient'; @@ -10,7 +10,7 @@ export const getFromRestApi = ? void : Serialized>>, ): Promise>>> => { - const response = await APIClient.get(endpoint.replace(/^\/+/, ''), params); + const response = await APIClient.get(endpoint.replace(/^\/+/, '') as TPath, params as any); if (typeof response === 'string') { throw new Error('invalid response data type'); diff --git a/apps/meteor/ee/client/omnichannel/BusinessHoursTableContainer.js b/apps/meteor/ee/client/omnichannel/BusinessHoursTableContainer.js index de454c697a6f..601ebdd0ecd2 100644 --- a/apps/meteor/ee/client/omnichannel/BusinessHoursTableContainer.js +++ b/apps/meteor/ee/client/omnichannel/BusinessHoursTableContainer.js @@ -1,6 +1,6 @@ import { Callout } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { AsyncStatePhase } from '../../../client/hooks/useAsyncState'; import { useEndpointData } from '../../../client/hooks/useEndpointData'; @@ -14,7 +14,17 @@ const BusinessHoursTableContainer = () => { value: data, phase: state, reload, - } = useEndpointData(`livechat/business-hours.list?count=${params.itemsPerPage}&offset=${params.current}&name=${params.text}`); + } = useEndpointData( + '/v1/livechat/business-hours.list', + useMemo( + () => ({ + count: params.itemsPerPage, + offset: params.current, + name: params.text, + }), + [params], + ), + ); if (state === AsyncStatePhase.REJECTED) { return {t('Error')}: error; diff --git a/apps/meteor/ee/client/omnichannel/ContactManagerInfo.js b/apps/meteor/ee/client/omnichannel/ContactManagerInfo.js index 4324016577e9..ecb563bd021a 100644 --- a/apps/meteor/ee/client/omnichannel/ContactManagerInfo.js +++ b/apps/meteor/ee/client/omnichannel/ContactManagerInfo.js @@ -1,6 +1,6 @@ import { css } from '@rocket.chat/css-in-js'; import { Box } from '@rocket.chat/fuselage'; -import React from 'react'; +import React, { useMemo } from 'react'; import UserCard from '../../../client/components/UserCard'; import { UserStatus } from '../../../client/components/UserStatus'; @@ -13,7 +13,10 @@ const wordBreak = css` `; function ContactManagerInfo({ username }) { - const { value: data, phase: state } = useEndpointData(`users.info?username=${username}`); + const { value: data, phase: state } = useEndpointData( + `/v1/users.info`, + useMemo(() => ({ username }), [username]), + ); if (!data && state === AsyncStatePhase.LOADING) { return null; } diff --git a/apps/meteor/ee/client/omnichannel/additionalForms/DepartmentBusinessHours.js b/apps/meteor/ee/client/omnichannel/additionalForms/DepartmentBusinessHours.js index a259cae39916..12fb93f551df 100644 --- a/apps/meteor/ee/client/omnichannel/additionalForms/DepartmentBusinessHours.js +++ b/apps/meteor/ee/client/omnichannel/additionalForms/DepartmentBusinessHours.js @@ -7,7 +7,7 @@ import { useEndpointData } from '../../../../client/hooks/useEndpointData'; export const DepartmentBusinessHours = ({ bhId }) => { const t = useTranslation(); const { value: data } = useEndpointData( - 'livechat/business-hour', + '/v1/livechat/business-hour', useMemo(() => ({ _id: bhId, type: 'custom' }), [bhId]), ); diff --git a/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponseEdit.tsx b/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponseEdit.tsx index a8cf541624f2..ef1716dd4213 100644 --- a/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponseEdit.tsx +++ b/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponseEdit.tsx @@ -30,7 +30,7 @@ const CannedResponseEdit: FC<{ }), ); - const saveCannedResponse = useEndpoint('POST', 'canned-responses'); + const saveCannedResponse = useEndpoint('POST', '/v1/canned-responses'); const hasManagerPermission = usePermission('view-all-canned-responses'); const hasMonitorPermission = usePermission('save-department-canned-responses'); diff --git a/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponseEditWithData.tsx b/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponseEditWithData.tsx index f2cbbca85145..4625845fea3e 100644 --- a/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponseEditWithData.tsx +++ b/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponseEditWithData.tsx @@ -14,7 +14,7 @@ const CannedResponseEditWithData: FC<{ reload: () => void; totalDataReload: () => void; }> = ({ cannedResponseId, reload, totalDataReload }) => { - const { value: data, phase: state, error } = useEndpointData(`canned-responses/${cannedResponseId}`); + const { value: data, phase: state, error } = useEndpointData(`/v1/canned-responses/${cannedResponseId}`); const t = useTranslation(); diff --git a/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponseEditWithDepartmentData.tsx b/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponseEditWithDepartmentData.tsx index 8d70c9db7a52..6ae941d18c5d 100644 --- a/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponseEditWithDepartmentData.tsx +++ b/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponseEditWithDepartmentData.tsx @@ -19,7 +19,7 @@ const CannedResponseEditWithData: FC<{ totalDataReload: () => void; }> = ({ data, reload, totalDataReload }) => { const departmentId = useMemo(() => data?.cannedResponse?.departmentId, [data]) as string; - const { value: departmentData, phase: state, error } = useEndpointData(`livechat/department/${departmentId}`); + const { value: departmentData, phase: state, error } = useEndpointData(`/v1/livechat/department/${departmentId}`); const t = useTranslation(); diff --git a/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponsesRoute.tsx b/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponsesRoute.tsx index a3e0a8351bed..b0176783716f 100644 --- a/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponsesRoute.tsx +++ b/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponsesRoute.tsx @@ -103,8 +103,8 @@ const CannedResponsesRoute: FC = () => { [t], ); - const { value: data, reload } = useEndpointData('canned-responses', query); - const { value: totalData, phase: totalDataPhase, reload: totalDataReload } = useEndpointData('canned-responses'); + const { value: data, reload } = useEndpointData('/v1/canned-responses', query); + const { value: totalData, phase: totalDataPhase, reload: totalDataReload } = useEndpointData('/v1/canned-responses'); const getTime = useFormatDateAndTime(); diff --git a/apps/meteor/ee/client/omnichannel/components/CannedResponse/modals/CreateCannedResponse/index.tsx b/apps/meteor/ee/client/omnichannel/components/CannedResponse/modals/CreateCannedResponse/index.tsx index 926ce92f8954..10a494dd2aa4 100644 --- a/apps/meteor/ee/client/omnichannel/components/CannedResponse/modals/CreateCannedResponse/index.tsx +++ b/apps/meteor/ee/client/omnichannel/components/CannedResponse/modals/CreateCannedResponse/index.tsx @@ -9,7 +9,7 @@ const WrapCreateCannedResponseModal: FC<{ data?: any; reloadCannedList?: any }> const closeModal = useSetModal(); const dispatchToastMessage = useToastMessageDispatch(); - const saveCannedResponse = useEndpoint('POST', 'canned-responses'); + const saveCannedResponse = useEndpoint('POST', '/v1/canned-responses'); const hasManagerPermission = usePermission('view-all-canned-responses'); const hasMonitorPermission = usePermission('save-department-canned-responses'); diff --git a/apps/meteor/ee/client/omnichannel/hooks/useCannedResponseFilterOptions.ts b/apps/meteor/ee/client/omnichannel/hooks/useCannedResponseFilterOptions.ts index f8f99abb8c98..f3ca3b4159cf 100644 --- a/apps/meteor/ee/client/omnichannel/hooks/useCannedResponseFilterOptions.ts +++ b/apps/meteor/ee/client/omnichannel/hooks/useCannedResponseFilterOptions.ts @@ -3,7 +3,7 @@ import { useEffect, useMemo, useState } from 'react'; export const useCannedResponseFilterOptions = (): string[][] => { const t = useTranslation(); - const getDepartments = useEndpoint('GET', 'livechat/department'); + const getDepartments = useEndpoint('GET', '/v1/livechat/department'); const defaultOptions = useMemo( () => [ diff --git a/apps/meteor/ee/client/omnichannel/hooks/useCannedResponseList.ts b/apps/meteor/ee/client/omnichannel/hooks/useCannedResponseList.ts index 8d74261be905..4f4e60e0f30a 100644 --- a/apps/meteor/ee/client/omnichannel/hooks/useCannedResponseList.ts +++ b/apps/meteor/ee/client/omnichannel/hooks/useCannedResponseList.ts @@ -26,8 +26,8 @@ export const useCannedResponseList = ( } }, [cannedList, options]); - const getCannedResponses = useEndpoint('GET', 'canned-responses'); - const getDepartments = useEndpoint('GET', 'livechat/department'); + const getCannedResponses = useEndpoint('GET', '/v1/canned-responses'); + const getDepartments = useEndpoint('GET', '/v1/livechat/department'); const fetchData = useCallback( async (start, end) => { diff --git a/apps/meteor/ee/client/omnichannel/monitors/MonitorsPage.js b/apps/meteor/ee/client/omnichannel/monitors/MonitorsPage.js index 093f72d54fd1..d0bf1593fc1c 100644 --- a/apps/meteor/ee/client/omnichannel/monitors/MonitorsPage.js +++ b/apps/meteor/ee/client/omnichannel/monitors/MonitorsPage.js @@ -30,7 +30,7 @@ const MonitorsPage = () => { const [sort, setSort] = useState(['name', 'asc']); const [username, setUsername] = useState(''); - const { value: data, phase: state, reload } = useEndpointData('livechat/monitors.list', useQuery(params, sort)); + const { value: data, phase: state, reload } = useEndpointData('/v1/livechat/monitors.list', useQuery(params, sort)); const addMonitor = useMethod('livechat:addMonitor'); diff --git a/apps/meteor/ee/client/omnichannel/priorities/PrioritiesRoute.js b/apps/meteor/ee/client/omnichannel/priorities/PrioritiesRoute.js index c92edc2bef1b..2d143cdfd3e6 100644 --- a/apps/meteor/ee/client/omnichannel/priorities/PrioritiesRoute.js +++ b/apps/meteor/ee/client/omnichannel/priorities/PrioritiesRoute.js @@ -61,7 +61,7 @@ function PrioritiesRoute() { }), ); - const { value: data = {}, reload } = useEndpointData('livechat/priorities.list', query); + const { value: data = {}, reload } = useEndpointData('/v1/livechat/priorities.list', query); const header = useMemo( () => diff --git a/apps/meteor/ee/client/omnichannel/priorities/PriorityEditWithData.js b/apps/meteor/ee/client/omnichannel/priorities/PriorityEditWithData.js index 3159c164064d..c1303c4d5caa 100644 --- a/apps/meteor/ee/client/omnichannel/priorities/PriorityEditWithData.js +++ b/apps/meteor/ee/client/omnichannel/priorities/PriorityEditWithData.js @@ -9,7 +9,7 @@ import PriorityEdit from './PriorityEdit'; function PriorityEditWithData({ priorityId, reload }) { const query = useMemo(() => ({ priorityId }), [priorityId]); - const { value: data, phase: state, error } = useEndpointData('livechat/priorities.getOne', query); + const { value: data, phase: state, error } = useEndpointData('/v1/livechat/priorities.getOne', query); const t = useTranslation(); diff --git a/apps/meteor/ee/client/omnichannel/tags/TagEditWithData.js b/apps/meteor/ee/client/omnichannel/tags/TagEditWithData.js index 2b76ca3fc93d..5b53f7f20ed8 100644 --- a/apps/meteor/ee/client/omnichannel/tags/TagEditWithData.js +++ b/apps/meteor/ee/client/omnichannel/tags/TagEditWithData.js @@ -10,7 +10,7 @@ import TagEditWithDepartmentData from './TagEditWithDepartmentData'; function TagEditWithData({ tagId, reload, title }) { const query = useMemo(() => ({ tagId }), [tagId]); - const { value: data, phase: state, error } = useEndpointData('livechat/tags.getOne', query); + const { value: data, phase: state, error } = useEndpointData('/v1/livechat/tags.getOne', query); const t = useTranslation(); diff --git a/apps/meteor/ee/client/omnichannel/tags/TagEditWithDepartmentData.tsx b/apps/meteor/ee/client/omnichannel/tags/TagEditWithDepartmentData.tsx index f8d231f95245..526c64cf0ce9 100644 --- a/apps/meteor/ee/client/omnichannel/tags/TagEditWithDepartmentData.tsx +++ b/apps/meteor/ee/client/omnichannel/tags/TagEditWithDepartmentData.tsx @@ -23,7 +23,7 @@ function TagEditWithDepartmentData({ data, title, ...props }: TagEditWithDepartm phase: currentDepartmentsState, error: currentDepartmentsError, } = useEndpointData( - 'livechat/department.listByIds', + '/v1/livechat/department.listByIds', useMemo(() => ({ ids: data?.departments ? data.departments : [] }), [data]), ); diff --git a/apps/meteor/ee/client/omnichannel/tags/TagNew.js b/apps/meteor/ee/client/omnichannel/tags/TagNew.js index 3ba4e7a9fb8b..d19f355bfad1 100644 --- a/apps/meteor/ee/client/omnichannel/tags/TagNew.js +++ b/apps/meteor/ee/client/omnichannel/tags/TagNew.js @@ -14,7 +14,7 @@ function TagNew({ reload }) { value: availableDepartments, phase: availableDepartmentsState, error: availableDepartmentsError, - } = useEndpointData('livechat/department'); + } = useEndpointData('/v1/livechat/department'); if (availableDepartmentsState === AsyncStatePhase.LOADING) { return ; diff --git a/apps/meteor/ee/client/omnichannel/tags/TagsRoute.js b/apps/meteor/ee/client/omnichannel/tags/TagsRoute.js index 1626fddc471a..80b8e8e06204 100644 --- a/apps/meteor/ee/client/omnichannel/tags/TagsRoute.js +++ b/apps/meteor/ee/client/omnichannel/tags/TagsRoute.js @@ -59,7 +59,7 @@ function TagsRoute() { }), ); - const { value: data = {}, reload } = useEndpointData('livechat/tags.list', query); + const { value: data = {}, reload } = useEndpointData('/v1/livechat/tags.list', query); const header = useMemo( () => diff --git a/apps/meteor/ee/client/omnichannel/units/UnitEditWithData.tsx b/apps/meteor/ee/client/omnichannel/units/UnitEditWithData.tsx index 9a374ddb811d..af33ccc139d1 100644 --- a/apps/meteor/ee/client/omnichannel/units/UnitEditWithData.tsx +++ b/apps/meteor/ee/client/omnichannel/units/UnitEditWithData.tsx @@ -14,15 +14,19 @@ const UnitEditWithData: FC<{ }> = function UnitEditWithData({ unitId, reload, title }) { const query = useMemo(() => ({ unitId }), [unitId]); - const { value: data, phase: state, error } = useEndpointData('livechat/units.getOne', query); + const { value: data, phase: state, error } = useEndpointData('/v1/livechat/units.getOne', query); - const { value: unitMonitors, phase: unitMonitorsState, error: unitMonitorsError } = useEndpointData('livechat/unitMonitors.list', query); + const { + value: unitMonitors, + phase: unitMonitorsState, + error: unitMonitorsError, + } = useEndpointData('/v1/livechat/unitMonitors.list', query); const { value: unitDepartments, phase: unitDepartmentsState, error: unitDepartmentsError, - } = useEndpointData(`livechat/departments.by-unit/${unitId}`); + } = useEndpointData(`/v1/livechat/departments.by-unit/${unitId}`); const t = useTranslation(); diff --git a/apps/meteor/ee/client/omnichannel/units/UnitNew.js b/apps/meteor/ee/client/omnichannel/units/UnitNew.js index bb58fa036af2..1f93c587bb68 100644 --- a/apps/meteor/ee/client/omnichannel/units/UnitNew.js +++ b/apps/meteor/ee/client/omnichannel/units/UnitNew.js @@ -14,12 +14,12 @@ function UnitNew({ reload, allUnits }) { value: availableDepartments, phase: availableDepartmentsState, error: availableDepartmentsError, - } = useEndpointData('livechat/department'); + } = useEndpointData('/v1/livechat/department'); const { value: availableMonitors, phase: availableMonitorsState, error: availableMonitorsError, - } = useEndpointData('livechat/monitors.list'); + } = useEndpointData('/v1/livechat/monitors.list'); if ([availableDepartmentsState, availableMonitorsState].includes(AsyncStatePhase.LOADING)) { return ; diff --git a/apps/meteor/ee/client/omnichannel/units/UnitsRoute.js b/apps/meteor/ee/client/omnichannel/units/UnitsRoute.js index 816e0cd50327..5362af00f8f0 100644 --- a/apps/meteor/ee/client/omnichannel/units/UnitsRoute.js +++ b/apps/meteor/ee/client/omnichannel/units/UnitsRoute.js @@ -59,7 +59,7 @@ function UnitsRoute() { }), ); - const { value: data = {}, reload } = useEndpointData('livechat/units.list', query); + const { value: data = {}, reload } = useEndpointData('/v1/livechat/units.list', query); const header = useMemo( () => diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardRoute.tsx b/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardRoute.tsx index e6bca507779a..ee53c8c364d2 100644 --- a/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardRoute.tsx +++ b/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardRoute.tsx @@ -24,7 +24,7 @@ const EngagementDashboardRoute = (): ReactElement | null => { } }, [routeName, engagementDashboardRoute, tab]); - const eventStats = useEndpointAction('POST', 'statistics.telemetry', { + const eventStats = useEndpointAction('POST', '/v1/statistics.telemetry', { params: [{ eventName: 'updateCounter', settingsId: 'Engagement_Dashboard_Load_Count' }], }); diff --git a/apps/meteor/ee/client/views/admin/users/useSeatsCap.ts b/apps/meteor/ee/client/views/admin/users/useSeatsCap.ts index 338208ce95b8..99290f70d034 100644 --- a/apps/meteor/ee/client/views/admin/users/useSeatsCap.ts +++ b/apps/meteor/ee/client/views/admin/users/useSeatsCap.ts @@ -7,7 +7,7 @@ export const useSeatsCap = (): reload: () => void; } | undefined => { - const { value, reload } = useEndpointData('licenses.maxActiveUsers'); + const { value, reload } = useEndpointData('/v1/licenses.maxActiveUsers'); if (!value) { return undefined; diff --git a/apps/meteor/ee/definition/rest/v1/omnichannel/businessHours.ts b/apps/meteor/ee/definition/rest/v1/omnichannel/businessHours.ts index baac71fb1c14..3eff58121555 100644 --- a/apps/meteor/ee/definition/rest/v1/omnichannel/businessHours.ts +++ b/apps/meteor/ee/definition/rest/v1/omnichannel/businessHours.ts @@ -3,7 +3,7 @@ import type { ILivechatBusinessHour } from '@rocket.chat/core-typings'; declare module '@rocket.chat/rest-typings' { // eslint-disable-next-line @typescript-eslint/interface-name-prefix interface Endpoints { - 'livechat/business-hours.list': { + '/v1/livechat/business-hours.list': { GET: (params: { name?: string; offset: number; count: number; sort: Record }) => { businessHours: ILivechatBusinessHour[]; count: number; diff --git a/apps/meteor/ee/definition/rest/v1/omnichannel/businessUnits.ts b/apps/meteor/ee/definition/rest/v1/omnichannel/businessUnits.ts index 616d404b18b4..5f01ca96b3e0 100644 --- a/apps/meteor/ee/definition/rest/v1/omnichannel/businessUnits.ts +++ b/apps/meteor/ee/definition/rest/v1/omnichannel/businessUnits.ts @@ -4,22 +4,22 @@ import type { PaginatedResult } from '@rocket.chat/rest-typings'; declare module '@rocket.chat/rest-typings' { // eslint-disable-next-line @typescript-eslint/interface-name-prefix interface Endpoints { - 'livechat/units.list': { + '/v1/livechat/units.list': { GET: (params: { text: string }) => PaginatedResult & { units: IOmnichannelBusinessUnit[]; }; }; - 'livechat/units.getOne': { + '/v1/livechat/units.getOne': { GET: (params: { unitId: string }) => IOmnichannelBusinessUnit; }; - 'livechat/unitMonitors.list': { + '/v1/livechat/unitMonitors.list': { GET: (params: { unitId: string }) => { monitors: ILivechatMonitor[] }; }; - 'livechat/units': { + '/v1/livechat/units': { GET: (params: { text: string }) => PaginatedResult & { units: IOmnichannelBusinessUnit[] }; POST: (params: { unitData: string; unitMonitors: string; unitDepartments: string }) => IOmnichannelBusinessUnit; }; - 'livechat/units/:id': { + '/v1/livechat/units/:id': { GET: () => IOmnichannelBusinessUnit; POST: (params: { unitData: string; unitMonitors: string; unitDepartments: string }) => IOmnichannelBusinessUnit; DELETE: () => number; diff --git a/apps/meteor/ee/definition/rest/v1/omnichannel/cannedResponses.ts b/apps/meteor/ee/definition/rest/v1/omnichannel/cannedResponses.ts index 13a6ec1d2224..e909aac72c95 100644 --- a/apps/meteor/ee/definition/rest/v1/omnichannel/cannedResponses.ts +++ b/apps/meteor/ee/definition/rest/v1/omnichannel/cannedResponses.ts @@ -4,7 +4,7 @@ import type { PaginatedResult, PaginatedRequest } from '@rocket.chat/rest-typing declare module '@rocket.chat/rest-typings' { // eslint-disable-next-line @typescript-eslint/interface-name-prefix interface Endpoints { - 'canned-responses': { + '/v1/canned-responses': { GET: ( params: PaginatedRequest<{ shortcut?: string; @@ -27,7 +27,7 @@ declare module '@rocket.chat/rest-typings' { }) => void; DELETE: (params: { _id: IOmnichannelCannedResponse['_id'] }) => void; }; - 'canned-responses/:_id': { + '/v1/canned-responses/:_id': { GET: () => { cannedResponse: IOmnichannelCannedResponse; }; diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 993111666eba..e6df68426608 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -123,6 +123,7 @@ "@types/semver": "^7.3.9", "@types/sharp": "^0.30.2", "@types/sinon": "^10.0.11", + "@types/strict-uri-encode": "^2", "@types/string-strip-html": "^5.0.0", "@types/supertest": "^2.0.11", "@types/ua-parser-js": "^0.7.36", @@ -185,6 +186,7 @@ "@nivo/heatmap": "0.73.0", "@nivo/line": "0.62.0", "@nivo/pie": "0.73.0", + "@rocket.chat/api-client": "workspace:^", "@rocket.chat/apps-engine": "1.32.0", "@rocket.chat/core-typings": "workspace:^", "@rocket.chat/css-in-js": "~0.31.12", @@ -333,6 +335,7 @@ "sodium-plus": "^0.9.0", "speakeasy": "^2.0.0", "stream-buffers": "^3.0.2", + "strict-uri-encode": "^2.0.0", "string-strip-html": "^7.0.3", "tar-stream": "^1.6.2", "tinykeys": "^1.4.0", diff --git a/packages/api-client/.eslintrc b/packages/api-client/.eslintrc new file mode 100644 index 000000000000..a83aeda48e66 --- /dev/null +++ b/packages/api-client/.eslintrc @@ -0,0 +1,4 @@ +{ + "extends": ["@rocket.chat/eslint-config"], + "ignorePatterns": ["**/dist"] +} diff --git a/packages/api-client/package.json b/packages/api-client/package.json new file mode 100644 index 000000000000..2100aaa8b0e1 --- /dev/null +++ b/packages/api-client/package.json @@ -0,0 +1,32 @@ +{ + "name": "@rocket.chat/api-client", + "version": "0.0.1", + "private": true, + "devDependencies": { + "@types/jest": "^27.4.1", + "@types/strict-uri-encode": "^2", + "eslint": "^8.12.0", + "jest": "^27.5.1", + "ts-jest": "^27.1.4", + "typescript": "~4.3.5" + }, + "scripts": { + "lint": "eslint --ext .js,.jsx,.ts,.tsx .", + "lint:fix": "eslint --ext .js,.jsx,.ts,.tsx . --fix", + "jest": "jest", + "build": "tsc -p tsconfig.json" + }, + "main": "./dist/index.js", + "typings": "./dist/index.d.ts", + "files": [ + "/dist" + ], + "dependencies": { + "@rocket.chat/core-typings": "workspace:^", + "@rocket.chat/rest-typings": "workspace:^", + "filter-obj": "^3.0.0", + "query-string": "^7.1.1", + "split-on-first": "^3.0.0", + "strict-uri-encode": "^2.0.0" + } +} diff --git a/packages/api-client/src/RestClientInterface.ts b/packages/api-client/src/RestClientInterface.ts new file mode 100644 index 000000000000..d9e3dd9ca373 --- /dev/null +++ b/packages/api-client/src/RestClientInterface.ts @@ -0,0 +1,61 @@ +import type { Serialized } from '@rocket.chat/core-typings/dist'; +import type { MatchPathPattern, OperationParams, OperationResult, PathFor } from '@rocket.chat/rest-typings'; + +type Next any> = (...args: Parameters) => ReturnType; + +export type Middleware any> = (context: Parameters, next: Next) => ReturnType; + +// eslint-disable-next-line @typescript-eslint/interface-name-prefix +export interface RestClientInterface { + get>( + endpoint: TPath, + params: void extends OperationParams<'GET', MatchPathPattern> ? never : OperationParams<'GET', MatchPathPattern>, + options?: Omit, + ): Promise>>>; + + get>( + endpoint: TPath, + params?: void extends OperationParams<'GET', MatchPathPattern> ? undefined : never, + options?: Omit, + ): Promise>>>; + + post>( + endpoint: TPath, + params: void extends OperationParams<'POST', MatchPathPattern> ? void : OperationParams<'POST', MatchPathPattern>, + options?: Omit, + ): Promise>>>; + + upload>( + endpoint: TPath, + params: void extends OperationParams<'POST', MatchPathPattern> ? void : OperationParams<'POST', MatchPathPattern>, + events?: { + load?: (event: ProgressEvent) => void; + progress?: (event: ProgressEvent) => void; + abort?: (event: ProgressEvent) => void; + error?: (event: ProgressEvent) => void; + }, + ): XMLHttpRequest; + + put>( + endpoint: TPath, + params: void extends OperationParams<'PUT', MatchPathPattern> ? void : OperationParams<'PUT', MatchPathPattern>, + options?: Omit, + ): Promise>>>; + + delete>( + endpoint: TPath, + params: void extends OperationParams<'DELETE', MatchPathPattern> ? void : OperationParams<'DELETE', MatchPathPattern>, + options?: Omit, + ): Promise>>>; + getCredentials(): + | { + 'X-User-Id': string; + 'X-Auth-Token': string; + } + | undefined; + setCredentials(credentials: undefined | { 'X-User-Id': string; 'X-Auth-Token': string }): void; + + use(middleware: Middleware): void; + + send(endpoint: string, method: string, options: Omit): Promise; +} diff --git a/packages/api-client/src/index.ts b/packages/api-client/src/index.ts new file mode 100644 index 000000000000..90905a7812b5 --- /dev/null +++ b/packages/api-client/src/index.ts @@ -0,0 +1,215 @@ +import { stringify } from 'query-string'; + +import type { Serialized } from '../../core-typings/dist'; +import type { MatchPathPattern, OperationParams, OperationResult, PathFor } from '../../rest-typings/dist'; +import type { Middleware, RestClientInterface } from './RestClientInterface'; + +export { RestClientInterface }; + +const pipe = + any>(fn: T) => + (...args: Parameters): ReturnType => + fn(...args); + +function buildFormData(data?: Record | void, formData = new FormData(), parentKey?: string): FormData { + if (data instanceof FormData) { + return data; + } + if (!data) { + return formData; + } + + if (typeof data === 'object' && !(data instanceof File)) { + Object.keys(data).forEach((key) => { + buildFormData(formData, data[key], parentKey ? `${parentKey}[${key}]` : key); + }); + } else { + data && parentKey && formData.append(parentKey, data); + } + return formData; +} + +const checkIfIsFormData = (data: any = {}): boolean => { + if (data instanceof FormData) { + return true; + } + return Object.values(data).some((value) => { + if (typeof value === 'object' && !(value instanceof File)) { + return checkIfIsFormData(value); + } + return value instanceof File; + }); +}; + +export class RestClient implements RestClientInterface { + private readonly baseUrl: string; + + private headers: Record = {}; + + private credentials: + | { + 'X-User-Id': string; + 'X-Auth-Token': string; + } + | undefined; + + constructor({ + baseUrl, + credentials, + headers = {}, + }: { + baseUrl: string; + credentials?: { + 'X-User-Id': string; + 'X-Auth-Token': string; + }; + headers?: Record; + }) { + this.baseUrl = `${baseUrl}/api`; + this.setCredentials(credentials); + this.headers = headers; + } + + getCredentials(): ReturnType { + return this.credentials; + } + + setCredentials: RestClientInterface['setCredentials'] = (credentials) => { + this.credentials = credentials; + }; + + get>( + endpoint: TPath, + params: void extends OperationParams<'GET', MatchPathPattern> ? never : OperationParams<'GET', MatchPathPattern>, + options?: Omit, + ): Promise>>>; + + get>( + endpoint: TPath, + params?: void extends OperationParams<'GET', MatchPathPattern> ? undefined : never, + options?: Omit, + ): Promise>>>; + + get>( + endpoint: TPath, + params?: OperationParams<'GET', MatchPathPattern>, + options?: Omit, + ): Promise>>> { + if (/\?/.test(endpoint)) { + // throw new Error('Endpoint cannot contain query string'); + console.warn('Endpoint cannot contain query string', endpoint); + } + const queryParams = this.getParams(params); + return this.send(`${endpoint}${queryParams ? `?${queryParams}` : ''}`, 'GET', options).then(function (response) { + return response.json(); + }); + } + + post: RestClientInterface['post'] = (endpoint, params, { headers, ...options } = {}) => { + const isFormData = checkIfIsFormData(params); + + return this.send(endpoint, 'POST', { + body: isFormData ? buildFormData(params) : JSON.stringify(params), + + headers: { + Accept: 'application/json', + ...(!isFormData && { 'Content-Type': 'application/json' }), + ...headers, + }, + + ...options, + }).then(function (response) { + return response.json(); + }); + }; + + put: RestClientInterface['put'] = (endpoint, params, { headers, ...options } = {}) => { + const isFormData = checkIfIsFormData(params); + return this.send(endpoint, 'PUT', { + body: isFormData ? buildFormData(params) : JSON.stringify(params), + + headers: { + Accept: 'application/json', + ...(!isFormData && { 'Content-Type': 'application/json' }), + ...headers, + }, + + ...options, + }).then(function (response) { + return response.json(); + }); + }; + + delete: RestClientInterface['delete'] = (endpoint, params, options) => { + return this.send(endpoint, 'DELETE', options).then(function (response) { + return response.json(); + }); + }; + + protected getCredentialsAsHeaders(): Record { + const credentials = this.getCredentials(); + return credentials + ? { + 'X-User-Id': credentials['X-User-Id'], + 'X-Auth-Token': credentials['X-Auth-Token'], + } + : {}; + } + + send(endpoint: string, method: string, { headers, ...options }: Omit = {}): Promise { + return fetch(`${this.baseUrl}${`/${endpoint}`.replace(/\/+/, '/')}`, { + ...options, + headers: { ...this.getCredentialsAsHeaders(), ...this.headers, ...headers }, + method, + }); + } + + protected getParams(data: Record | void): string { + return data ? stringify(data, { arrayFormat: 'bracket' }) : ''; + } + + upload: RestClientInterface['upload'] = (endpoint, params, events) => { + if (!params) { + throw new Error('Missing params'); + } + const xhr = new XMLHttpRequest(); + const data = new FormData(); + + Object.entries(params as any).forEach(([key, value]) => { + if (value instanceof File) { + data.append(key, value, value.name); + return; + } + value && data.append(key, value as any); + }); + + xhr.open('POST', `${this.baseUrl}${`/${endpoint}`.replace(/\/+/, '/')}`, true); + Object.entries(this.getCredentialsAsHeaders()).forEach(([key, value]) => { + xhr.setRequestHeader(key, value); + }); + + if (events?.load) { + xhr.upload.addEventListener('load', events.load); + } + if (events?.progress) { + xhr.upload.addEventListener('progress', events.progress); + } + if (events?.error) { + xhr.addEventListener('error', events.error); + } + if (events?.abort) { + xhr.addEventListener('abort', events.abort); + } + + xhr.send(data); + + return xhr; + }; + + use(middleware: Middleware): void { + const fn = this.send.bind(this); + this.send = function (this: RestClient, ...context: Parameters): ReturnType { + return middleware(context, pipe(fn)); + } as RestClientInterface['send']; + } +} diff --git a/packages/api-client/tsconfig.json b/packages/api-client/tsconfig.json new file mode 100644 index 000000000000..455edb8149c4 --- /dev/null +++ b/packages/api-client/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["./src/**/*"] +} diff --git a/packages/core-typings/src/ICustomEmojiDescriptor.ts b/packages/core-typings/src/ICustomEmojiDescriptor.ts index 1fa3a97cee62..f04a19d42eab 100644 --- a/packages/core-typings/src/ICustomEmojiDescriptor.ts +++ b/packages/core-typings/src/ICustomEmojiDescriptor.ts @@ -2,6 +2,6 @@ import type { IRocketChatRecord } from './IRocketChatRecord'; export interface ICustomEmojiDescriptor extends IRocketChatRecord { name: string; - aliases: string[]; + aliases: string; extension: string; } diff --git a/packages/core-typings/src/IEmojiCustom.ts b/packages/core-typings/src/IEmojiCustom.ts index 64c2706fbb2e..98bbc9121d47 100644 --- a/packages/core-typings/src/IEmojiCustom.ts +++ b/packages/core-typings/src/IEmojiCustom.ts @@ -2,6 +2,6 @@ import type { IRocketChatRecord } from './IRocketChatRecord'; export interface IEmojiCustom extends IRocketChatRecord { name: string; - aliases: string; + aliases: string[]; extension: string; } diff --git a/packages/rest-typings/src/apps/index.ts b/packages/rest-typings/src/apps/index.ts index 26de92caaae4..50b9160d23e2 100644 --- a/packages/rest-typings/src/apps/index.ts +++ b/packages/rest-typings/src/apps/index.ts @@ -1,11 +1,21 @@ import type { IApiEndpointMetadata } from '@rocket.chat/apps-engine/definition/api'; import type { IExternalComponent } from '@rocket.chat/apps-engine/definition/externalComponent'; +import type { IUIActionButton } from '@rocket.chat/apps-engine/definition/ui'; import type { ISetting, AppScreenshot, App } from '@rocket.chat/core-typings'; export type AppsEndpoints = { '/apps/externalComponents': { GET: () => { externalComponents: IExternalComponent[] }; }; + + '/apps/actionButtons': { + GET: () => IUIActionButton[]; + }; + + '/apps/public/:appId/get-sidebar-icon': { + GET: (params: { icon: string }) => unknown; + }; + '/apps/:id/settings': { GET: () => { [key: string]: ISetting; @@ -23,6 +33,7 @@ export type AppsEndpoints = { apis: IApiEndpointMetadata[]; }; }; + '/apps/:id': { GET: (params: { marketplace?: 'true' | 'false'; update?: 'true' | 'false'; appVersion: string }) => { app: App; diff --git a/packages/rest-typings/src/index.ts b/packages/rest-typings/src/index.ts index 5c2b9fa56e4d..e50250dbb060 100644 --- a/packages/rest-typings/src/index.ts +++ b/packages/rest-typings/src/index.ts @@ -35,14 +35,17 @@ import type { VoipEndpoints } from './v1/voip'; import type { EmailInboxEndpoints } from './v1/email-inbox'; import type { WebdavEndpoints } from './v1/webdav'; import type { OAuthAppsEndpoint } from './v1/oauthapps'; -import type { SubscriptionsEndpoints } from './v1/subscriptionsEndpoints'; import type { CommandsEndpoints } from './v1/commands'; +import type { MeEndpoints } from './v1/me'; +import type { SubscriptionsEndpoints } from './v1/subscriptionsEndpoints'; // eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/interface-name-prefix export interface Endpoints extends ChannelsEndpoints, + MeEndpoints, BannersEndpoints, ChatEndpoints, + CommandsEndpoints, CloudEndpoints, CommandsEndpoints, CustomUserStatusEndpoints, @@ -135,7 +138,7 @@ export type UrlParams = string extends T ? { [k in Param | keyof UrlParams]: string } : T extends `${infer _Start}:${infer Param}` ? { [k in Param]: string } - : {}; + : undefined | {}; export type MethodOf = TPathPattern extends any ? keyof Endpoints[TPathPattern] : never; @@ -172,3 +175,4 @@ export * from './v1/oauthapps'; export * from './helpers/PaginatedRequest'; export * from './helpers/PaginatedResult'; export * from './helpers/ReplacePlaceholders'; +export * from './v1/emojiCustom'; diff --git a/packages/rest-typings/src/v1/autoTranslate.ts b/packages/rest-typings/src/v1/autoTranslate.ts index 9f265c9aa6d4..b9710609501f 100644 --- a/packages/rest-typings/src/v1/autoTranslate.ts +++ b/packages/rest-typings/src/v1/autoTranslate.ts @@ -1,13 +1,13 @@ import type { ISupportedLanguage } from '@rocket.chat/core-typings'; export type AutoTranslateEndpoints = { - 'autotranslate.getSupportedLanguages': { + '/v1/autotranslate.getSupportedLanguages': { GET: (params: { targetLanguage: string }) => { languages: ISupportedLanguage[] }; }; - 'autotranslate.saveSettings': { + '/v1/autotranslate.saveSettings': { POST: (params: { roomId: string; field: string; value: boolean; defaultLanguage?: string }) => void; }; - 'autotranslate.translateMessage': { + '/v1/autotranslate.translateMessage': { POST: (params: { messageId: string; targetLanguage?: string }) => void; }; }; diff --git a/packages/rest-typings/src/v1/banners.ts b/packages/rest-typings/src/v1/banners.ts index 7045e2383a14..03ee39961a9a 100644 --- a/packages/rest-typings/src/v1/banners.ts +++ b/packages/rest-typings/src/v1/banners.ts @@ -80,25 +80,25 @@ export const isBannersDismissProps = ajv.compile(BannersDismissS export type BannersEndpoints = { /* @deprecated */ - 'banners.getNew': { + '/v1/banners.getNew': { GET: (params: BannersGetNew) => { banners: IBanner[]; }; }; - 'banners/:id': { + '/v1/banners/:id': { GET: (params: BannersId) => { banners: IBanner[]; }; }; - 'banners': { + '/v1/banners': { GET: (params: Banners) => { banners: IBanner[]; }; }; - 'banners.dismiss': { + '/v1/banners.dismiss': { POST: (params: BannersDismiss) => void; }; }; diff --git a/packages/rest-typings/src/v1/channels/channels.ts b/packages/rest-typings/src/v1/channels/channels.ts index 171f48703c10..dd3361172244 100644 --- a/packages/rest-typings/src/v1/channels/channels.ts +++ b/packages/rest-typings/src/v1/channels/channels.ts @@ -12,17 +12,17 @@ import type { ChannelsSetAnnouncementProps } from './ChannelsSetAnnouncementProp import type { ChannelsUnarchiveProps } from './ChannelsUnarchiveProps'; export type ChannelsEndpoints = { - 'channels.files': { + '/v1/channels.files': { GET: (params: PaginatedRequest<{ roomId: IRoom['_id'] }>) => PaginatedResult<{ files: IUpload[]; }>; }; - 'channels.members': { + '/v1/channels.members': { GET: (params: PaginatedRequest<{ roomId: IRoom['_id']; filter?: string; status?: string[] }>) => PaginatedResult<{ members: IUser[]; }>; }; - 'channels.history': { + '/v1/channels.history': { GET: ( params: PaginatedRequest<{ roomId: string; @@ -35,13 +35,13 @@ export type ChannelsEndpoints = { messages: IMessage[]; }>; }; - 'channels.archive': { + '/v1/channels.archive': { POST: (params: ChannelsArchiveProps) => void; }; - 'channels.unarchive': { + '/v1/channels.unarchive': { POST: (params: ChannelsUnarchiveProps) => void; }; - 'channels.create': { + '/v1/channels.create': { POST: (params: { name: string; members: string[]; @@ -55,15 +55,15 @@ export type ChannelsEndpoints = { group: Partial; }; }; - 'channels.convertToTeam': { + '/v1/channels.convertToTeam': { POST: (params: { channelId: string; channelName: string }) => { team: ITeam; }; }; - 'channels.info': { + '/v1/channels.info': { GET: (params: { roomId: string }) => { channel: IRoom }; }; - 'channels.counters': { + '/v1/channels.counters': { GET: (params: { roomId: string }) => { joined: boolean; members: number; @@ -74,76 +74,76 @@ export type ChannelsEndpoints = { userMentions: number; }; }; - 'channels.join': { + '/v1/channels.join': { POST: (params: { roomId: string; joinCode?: string }) => { channel: IRoom; }; }; - 'channels.close': { + '/v1/channels.close': { POST: (params: { roomId: string }) => {}; }; - 'channels.kick': { + '/v1/channels.kick': { POST: (params: { roomId: string; userId: string }) => {}; }; - 'channels.delete': { + '/v1/channels.delete': { POST: (params: ChannelsDeleteProps) => void; }; - 'channels.leave': { + '/v1/channels.leave': { POST: (params: { roomId: string }) => {}; }; - 'channels.addModerator': { + '/v1/channels.addModerator': { POST: (params: { roomId: string; userId: string }) => {}; }; - 'channels.removeModerator': { + '/v1/channels.removeModerator': { POST: (params: { roomId: string; userId: string }) => {}; }; - 'channels.addOwner': { + '/v1/channels.addOwner': { POST: (params: { roomId: string; userId: string }) => {}; }; - 'channels.removeOwner': { + '/v1/channels.removeOwner': { POST: (params: { roomId: string; userId: string }) => {}; }; - 'channels.addLeader': { + '/v1/channels.addLeader': { POST: (params: { roomId: string; userId: string }) => {}; }; - 'channels.removeLeader': { + '/v1/channels.removeLeader': { POST: (params: { roomId: string; userId: string }) => {}; }; - 'channels.roles': { + '/v1/channels.roles': { GET: (params: { roomId: string }) => { roles: IGetRoomRoles[] }; }; - 'channels.messages': { + '/v1/channels.messages': { GET: (params: ChannelsMessagesProps) => PaginatedResult<{ messages: IMessage[]; }>; }; - 'channels.open': { + '/v1/channels.open': { POST: (params: ChannelsOpenProps) => void; }; - 'channels.setReadOnly': { + '/v1/channels.setReadOnly': { POST: (params: { roomId: string; readOnly: boolean }) => { channel: IRoom; }; }; - 'channels.addAll': { + '/v1/channels.addAll': { POST: (params: ChannelsAddAllProps) => { channel: IRoom; }; }; - 'channels.anonymousread': { + '/v1/channels.anonymousread': { GET: (params: PaginatedRequest<{ roomId: string } | { roomName: string }>) => PaginatedResult<{ messages: IMessage[]; }>; }; - 'channels.setAnnouncement': { + '/v1/channels.setAnnouncement': { POST: (params: ChannelsSetAnnouncementProps) => {}; }; - 'channels.getAllUserMentionsByChannel': { + '/v1/channels.getAllUserMentionsByChannel': { GET: (params: ChannelsGetAllUserMentionsByChannelProps) => PaginatedResult<{ mentions: IUser[]; }>; }; - 'channels.moderators': { + '/v1/channels.moderators': { GET: (params: { roomId: string }) => { moderators: Pick[] }; }; }; diff --git a/packages/rest-typings/src/v1/chat.ts b/packages/rest-typings/src/v1/chat.ts index 1754c302f5a9..07024e9a4138 100644 --- a/packages/rest-typings/src/v1/chat.ts +++ b/packages/rest-typings/src/v1/chat.ts @@ -387,45 +387,45 @@ const ChatGetMessageReadReceiptsSchema = { export const isChatGetMessageReadReceiptsProps = ajv.compile(ChatGetMessageReadReceiptsSchema); export type ChatEndpoints = { - 'chat.getMessage': { + '/v1/chat.getMessage': { GET: (params: ChatGetMessage) => { message: IMessage; }; }; - 'chat.followMessage': { + '/v1/chat.followMessage': { POST: (params: ChatFollowMessage) => void; }; - 'chat.unfollowMessage': { + '/v1/chat.unfollowMessage': { POST: (params: ChatUnfollowMessage) => void; }; - 'chat.starMessage': { + '/v1/chat.starMessage': { POST: (params: ChatStarMessage) => void; }; - 'chat.unStarMessage': { + '/v1/chat.unStarMessage': { POST: (params: ChatUnstarMessage) => void; }; - 'chat.pinMessage': { + '/v1/chat.pinMessage': { POST: (params: ChatPinMessage) => void; }; - 'chat.unPinMessage': { + '/v1/chat.unPinMessage': { POST: (params: ChatUnpinMessage) => void; }; - 'chat.reportMessage': { + '/v1/chat.reportMessage': { POST: (params: ChatReportMessage) => void; }; - 'chat.getDiscussions': { + '/v1/chat.getDiscussions': { GET: (params: ChatGetDiscussions) => { messages: IMessage[]; total: number; }; }; - 'chat.getThreadsList': { + '/v1/chat.getThreadsList': { GET: (params: ChatGetThreadsList) => { threads: IMessage[]; total: number; }; }; - 'chat.syncThreadsList': { + '/v1/chat.syncThreadsList': { GET: (params: ChatSyncThreadsList) => { threads: { update: IMessage[]; @@ -433,30 +433,30 @@ export type ChatEndpoints = { }; }; }; - 'chat.delete': { + '/v1/chat.delete': { POST: (params: ChatDelete) => { _id: string; ts: string; message: Pick; }; }; - 'chat.react': { + '/v1/chat.react': { POST: (params: ChatReact) => void; }; - 'chat.ignoreUser': { + '/v1/chat.ignoreUser': { GET: (params: ChatIgnoreUser) => {}; }; - 'chat.search': { + '/v1/chat.search': { GET: (params: ChatSearch) => { messages: IMessage[]; }; }; - 'chat.update': { + '/v1/chat.update': { POST: (params: ChatUpdate) => { messages: IMessage; }; }; - 'chat.getMessageReadReceipts': { + '/v1/chat.getMessageReadReceipts': { GET: (params: ChatGetMessageReadReceipts) => { receipts: ReadReceipt[] }; }; }; diff --git a/packages/rest-typings/src/v1/cloud.ts b/packages/rest-typings/src/v1/cloud.ts index e27289566711..1e265f0025d9 100644 --- a/packages/rest-typings/src/v1/cloud.ts +++ b/packages/rest-typings/src/v1/cloud.ts @@ -67,20 +67,20 @@ const CloudConfirmationPollSchema = { export const isCloudConfirmationPollProps = ajv.compile(CloudConfirmationPollSchema); export type CloudEndpoints = { - 'cloud.manualRegister': { + '/v1/cloud.manualRegister': { POST: (params: CloudManualRegister) => void; }; - 'cloud.createRegistrationIntent': { + '/v1/cloud.createRegistrationIntent': { POST: (params: CloudCreateRegistrationIntent) => { intentData: CloudRegistrationIntentData; }; }; - 'cloud.confirmationPoll': { + '/v1/cloud.confirmationPoll': { GET: (params: CloudConfirmationPoll) => { pollData: CloudConfirmationPollData; }; }; - 'cloud.registrationStatus': { + '/v1/cloud.registrationStatus': { GET: (params: void) => { registrationStatus: CloudRegistrationStatus }; }; }; diff --git a/packages/rest-typings/src/v1/commands.ts b/packages/rest-typings/src/v1/commands.ts index aec14e57c669..97d93f161baa 100644 --- a/packages/rest-typings/src/v1/commands.ts +++ b/packages/rest-typings/src/v1/commands.ts @@ -3,12 +3,12 @@ import type { PaginatedRequest } from '../helpers/PaginatedRequest'; import type { PaginatedResult } from '../helpers/PaginatedResult'; export type CommandsEndpoints = { - 'commands.get': { + '/v1/commands.get': { GET: (params: { command: string }) => { command: Pick; }; }; - 'commands.list': { + '/v1/commands.list': { GET: ( params: PaginatedRequest<{ fields?: string; @@ -17,12 +17,12 @@ export type CommandsEndpoints = { commands: Pick[]; }>; }; - 'commands.run': { + '/v1/commands.run': { POST: (params: { command: string; params?: string; roomId: string; tmid?: string; triggerId: string }) => { result: unknown; }; }; - 'commands.preview': { + '/v1/commands.preview': { GET: (params: { command: string; params?: string; roomId: string }) => { preview: SlashCommandPreviews; }; diff --git a/packages/rest-typings/src/v1/customSounds.ts b/packages/rest-typings/src/v1/customSounds.ts index 84efd66ecb60..0380d954348f 100644 --- a/packages/rest-typings/src/v1/customSounds.ts +++ b/packages/rest-typings/src/v1/customSounds.ts @@ -36,7 +36,7 @@ const CustomSoundsListSchema = { export const isCustomSoundsListProps = ajv.compile(CustomSoundsListSchema); export type CustomSoundEndpoint = { - 'custom-sounds.list': { + '/v1/custom-sounds.list': { GET: (params: CustomSoundsList) => PaginatedResult<{ sounds: ICustomSound[]; }>; diff --git a/packages/rest-typings/src/v1/customUserStatus.ts b/packages/rest-typings/src/v1/customUserStatus.ts index dfd30aa9a117..a6131abfca8d 100644 --- a/packages/rest-typings/src/v1/customUserStatus.ts +++ b/packages/rest-typings/src/v1/customUserStatus.ts @@ -36,20 +36,20 @@ const CustomUserStatusListSchema = { export const isCustomUserStatusListProps = ajv.compile(CustomUserStatusListSchema); export type CustomUserStatusEndpoints = { - 'custom-user-status.list': { + '/v1/custom-user-status.list': { GET: (params: CustomUserStatusListProps) => PaginatedResult<{ statuses: IUserStatus[]; }>; }; - 'custom-user-status.create': { + '/v1/custom-user-status.create': { POST: (params: { name: string; statusType?: string }) => { customUserStatus: ICustomUserStatus; }; }; - 'custom-user-status.delete': { + '/v1/custom-user-status.delete': { POST: (params: { customUserStatusId: string }) => void; }; - 'custom-user-status.update': { + '/v1/custom-user-status.update': { POST: (params: { id: string; name?: string; statusType?: string }) => { customUserStatus: ICustomUserStatus; }; diff --git a/packages/rest-typings/src/v1/directory.ts b/packages/rest-typings/src/v1/directory.ts index af0ad5be6d8d..4a44f6539bf9 100644 --- a/packages/rest-typings/src/v1/directory.ts +++ b/packages/rest-typings/src/v1/directory.ts @@ -36,7 +36,7 @@ const DirectorySchema = { export const isDirectoryProps = ajv.compile(DirectorySchema); export type DirectoryEndpoint = { - directory: { + '/v1/directory': { GET: (params: DirectoryProps) => PaginatedResult<{ result: IRoom[] }>; }; }; diff --git a/packages/rest-typings/src/v1/dm/dm.ts b/packages/rest-typings/src/v1/dm/dm.ts index c23bae0b51b7..80955beeef04 100644 --- a/packages/rest-typings/src/v1/dm/dm.ts +++ b/packages/rest-typings/src/v1/dm/dm.ts @@ -1,17 +1,17 @@ import type { ImEndpoints } from './im'; export type DmEndpoints = { - 'dm.create': ImEndpoints['im.create']; - 'dm.delete': ImEndpoints['im.delete']; - 'dm.close': ImEndpoints['im.close']; - 'dm.counters': ImEndpoints['im.counters']; - 'dm.files': ImEndpoints['im.files']; - 'dm.history': ImEndpoints['im.history']; - 'dm.members': ImEndpoints['im.members']; - 'dm.messages': ImEndpoints['im.messages']; - 'dm.messages.others': ImEndpoints['im.messages.others']; - 'dm.list': ImEndpoints['im.list']; - 'dm.list.everyone': ImEndpoints['im.list.everyone']; - 'dm.open': ImEndpoints['im.open']; - 'dm.setTopic': ImEndpoints['im.setTopic']; + '/v1/dm.create': ImEndpoints['/v1/im.create']; + '/v1/dm.delete': ImEndpoints['/v1/im.delete']; + '/v1/dm.close': ImEndpoints['/v1/im.close']; + '/v1/dm.counters': ImEndpoints['/v1/im.counters']; + '/v1/dm.files': ImEndpoints['/v1/im.files']; + '/v1/dm.history': ImEndpoints['/v1/im.history']; + '/v1/dm.members': ImEndpoints['/v1/im.members']; + '/v1/dm.messages': ImEndpoints['/v1/im.messages']; + '/v1/dm.messages.others': ImEndpoints['/v1/im.messages.others']; + '/v1/dm.list': ImEndpoints['/v1/im.list']; + '/v1/dm.list.everyone': ImEndpoints['/v1/im.list.everyone']; + '/v1/dm.open': ImEndpoints['/v1/im.open']; + '/v1/dm.setTopic': ImEndpoints['/v1/im.setTopic']; }; diff --git a/packages/rest-typings/src/v1/dm/im.ts b/packages/rest-typings/src/v1/dm/im.ts index 87c4adfe4fa7..bd48bd6fd3b1 100644 --- a/packages/rest-typings/src/v1/dm/im.ts +++ b/packages/rest-typings/src/v1/dm/im.ts @@ -12,24 +12,24 @@ import type { DmMemberProps } from './DmMembersProps'; import type { DmMessagesProps } from './DmMessagesProps'; export type ImEndpoints = { - 'im.create': { + '/v1/im.create': { POST: (params: DmCreateProps) => { room: IRoom & { rid: IRoom['_id'] }; }; }; - 'im.delete': { + '/v1/im.delete': { POST: (params: DmDeleteProps) => void; }; - 'im.close': { + '/v1/im.close': { POST: (params: DmCloseProps) => void; }; - 'im.kick': { + '/v1/im.kick': { POST: (params: DmCloseProps) => void; }; - 'im.leave': { + '/v1/im.leave': { POST: (params: DmLeaveProps) => void; }; - 'im.counters': { + '/v1/im.counters': { GET: (params: { roomId: string; userId?: string }) => { joined: boolean; unreads: number | null; @@ -40,40 +40,40 @@ export type ImEndpoints = { userMentions: number | null; }; }; - 'im.files': { + '/v1/im.files': { GET: (params: DmFileProps) => PaginatedResult<{ files: IUpload[]; }>; }; - 'im.history': { + '/v1/im.history': { GET: (params: DmHistoryProps) => { messages: Pick[]; }; }; - 'im.members': { + '/v1/im.members': { GET: (params: DmMemberProps) => PaginatedResult<{ members: Pick[]; }>; }; - 'im.messages': { + '/v1/im.messages': { GET: (params: DmMessagesProps) => PaginatedResult<{ messages: IMessage[]; }>; }; - 'im.messages.others': { + '/v1/im.messages.others': { GET: (params: PaginatedRequest<{ roomId: IRoom['_id']; query?: string; fields?: string }>) => PaginatedResult<{ messages: IMessage[] }>; }; - 'im.list': { + '/v1/im.list': { GET: (params: PaginatedRequest<{ fields?: string }>) => PaginatedResult<{ ims: IRoom[] }>; }; - 'im.list.everyone': { + '/v1/im.list.everyone': { GET: (params: PaginatedRequest<{ query: string; fields?: string }>) => PaginatedResult<{ ims: IRoom[] }>; }; - 'im.open': { + '/v1/im.open': { POST: (params: { roomId: string }) => void; }; - 'im.setTopic': { + '/v1/im.setTopic': { POST: (params: { roomId: string; topic?: string }) => { topic?: string; }; diff --git a/packages/rest-typings/src/v1/dns.ts b/packages/rest-typings/src/v1/dns.ts index 28a630ea3352..b3cdb4e1ff68 100644 --- a/packages/rest-typings/src/v1/dns.ts +++ b/packages/rest-typings/src/v1/dns.ts @@ -39,12 +39,12 @@ const DnsResolveSrvSchema = { export const isDnsResolveSrvProps = ajv.compile(DnsResolveSrvSchema); export type DnsEndpoints = { - 'dns.resolve.srv': { + '/v1/dns.resolve.srv': { GET: (params: DnsResolveSrvProps) => { resolved: Record; }; }; - 'dns.resolve.txt': { + '/v1/dns.resolve.txt': { POST: (params: DnsResolveTxtProps) => { resolved: string; // resolved: Record; diff --git a/packages/rest-typings/src/v1/e2e.ts b/packages/rest-typings/src/v1/e2e.ts index 62ce4110ddbb..10827062e8b9 100644 --- a/packages/rest-typings/src/v1/e2e.ts +++ b/packages/rest-typings/src/v1/e2e.ts @@ -89,21 +89,21 @@ const E2eSetRoomKeyIdSchema = { export const isE2eSetRoomKeyIdProps = ajv.compile(E2eSetRoomKeyIdSchema); export type E2eEndpoints = { - 'e2e.setUserPublicAndPrivateKeys': { + '/v1/e2e.setUserPublicAndPrivateKeys': { POST: (params: E2eSetUserPublicAndPrivateKeysProps) => void; }; - 'e2e.getUsersOfRoomWithoutKey': { + '/v1/e2e.getUsersOfRoomWithoutKey': { GET: (params: E2eGetUsersOfRoomWithoutKeyProps) => { users: Pick[]; }; }; - 'e2e.updateGroupKey': { + '/v1/e2e.updateGroupKey': { POST: (params: E2eUpdateGroupKeyProps) => {}; }; - 'e2e.setRoomKeyID': { + '/v1/e2e.setRoomKeyID': { POST: (params: E2eSetRoomKeyIdProps) => {}; }; - 'e2e.fetchMyKeys': { + '/v1/e2e.fetchMyKeys': { GET: () => { public_key: string; private_key: string }; }; }; diff --git a/packages/rest-typings/src/v1/email-inbox.ts b/packages/rest-typings/src/v1/email-inbox.ts index d0347fe7e829..d62b5845b454 100644 --- a/packages/rest-typings/src/v1/email-inbox.ts +++ b/packages/rest-typings/src/v1/email-inbox.ts @@ -157,20 +157,20 @@ const EmailInboxSearchPropsSchema = { export const isEmailInboxSearch = ajv.compile(EmailInboxSearchPropsSchema); export type EmailInboxEndpoints = { - 'email-inbox.list': { + '/v1/email-inbox.list': { GET: (params: EmailInboxListProps) => PaginatedResult<{ emailInboxes: IEmailInbox[] }>; }; - 'email-inbox': { + '/v1/email-inbox': { POST: (params: EmailInboxProps) => { _id: string }; }; - 'email-inbox/:_id': { + '/v1/email-inbox/:_id': { GET: (params: void) => IEmailInbox | null; DELETE: (params: void) => { _id: string }; }; - 'email-inbox.search': { + '/v1/email-inbox.search': { GET: (params: EmailInboxSearchProps) => { emailInbox: IEmailInbox | null }; }; - 'email-inbox.send-test/:_id': { + '/v1/email-inbox.send-test/:_id': { POST: (params: void) => { _id: string }; }; }; diff --git a/packages/rest-typings/src/v1/emojiCustom.ts b/packages/rest-typings/src/v1/emojiCustom.ts index 2532879dd9a9..e0cd87906c36 100644 --- a/packages/rest-typings/src/v1/emojiCustom.ts +++ b/packages/rest-typings/src/v1/emojiCustom.ts @@ -1,4 +1,4 @@ -import type { ICustomEmojiDescriptor } from '@rocket.chat/core-typings'; +import type { ICustomEmojiDescriptor, IEmojiCustom } from '@rocket.chat/core-typings'; import Ajv from 'ajv'; import type { PaginatedRequest } from '../helpers/PaginatedRequest'; @@ -27,6 +27,7 @@ export const isEmojiCustomDelete = ajv.compile(emojiCust type emojiCustomList = { query: string; + updatedSince?: string; }; const emojiCustomListSchema = { @@ -35,27 +36,38 @@ const emojiCustomListSchema = { query: { type: 'string', }, + updatedSince: { + type: 'string', + nullable: true, + }, }, required: ['query'], additionalProperties: false, }; -export const isemojiCustomList = ajv.compile(emojiCustomListSchema); +export const isEmojiCustomList = ajv.compile(emojiCustomListSchema); export type EmojiCustomEndpoints = { - 'emoji-custom.all': { - GET: (params: PaginatedRequest<{ query: string }, 'name'>) => { - emojis: ICustomEmojiDescriptor[]; - } & PaginatedResult; + '/v1/emoji-custom.all': { + GET: (params: PaginatedRequest<{ query: string }, 'name'>) => PaginatedResult<{ + emojis: IEmojiCustom[]; + }>; }; - 'emoji-custom.list': { + '/v1/emoji-custom.list': { GET: (params: emojiCustomList) => { - emojis?: { - update: ICustomEmojiDescriptor[]; + emojis: { + update: IEmojiCustom[]; + remove: IEmojiCustom[]; }; }; }; - 'emoji-custom.delete': { + '/v1/emoji-custom.delete': { POST: (params: emojiCustomDeleteProps) => void; }; + '/v1/emoji-custom.create': { + POST: (params: { emoji: ICustomEmojiDescriptor }) => void; + }; + '/v1/emoji-custom.update': { + POST: (params: { emoji: ICustomEmojiDescriptor }) => void; + }; }; diff --git a/packages/rest-typings/src/v1/groups.ts b/packages/rest-typings/src/v1/groups.ts index 10540a3ecd62..7fc73cbe7440 100644 --- a/packages/rest-typings/src/v1/groups.ts +++ b/packages/rest-typings/src/v1/groups.ts @@ -318,12 +318,12 @@ const GroupsMessagePropsSchema = { export const isGroupsMessageProps = ajv.compile(GroupsMessagePropsSchema); export type GroupsEndpoints = { - 'groups.files': { + '/v1/groups.files': { GET: (params: GroupsFilesProps) => PaginatedResult<{ files: IUpload[]; }>; }; - 'groups.members': { + '/v1/groups.members': { GET: (params: GroupsMembersProps) => { count: number; offset: number; @@ -331,26 +331,26 @@ export type GroupsEndpoints = { total: number; }; }; - 'groups.history': { + '/v1/groups.history': { GET: (params: PaginatedRequest<{ roomId: string; latest?: string }>) => PaginatedResult<{ messages: IMessage[]; }>; }; - 'groups.archive': { + '/v1/groups.archive': { POST: (params: GroupsArchiveProps) => void; }; - 'groups.unarchive': { + '/v1/groups.unarchive': { POST: (params: GroupsUnarchiveProps) => void; }; - 'groups.create': { + '/v1/groups.create': { POST: (params: GroupsCreateProps) => { group: Partial; }; }; - 'groups.convertToTeam': { + '/v1/groups.convertToTeam': { POST: (params: GroupsConvertToTeamProps) => { team: ITeam }; }; - 'groups.counters': { + '/v1/groups.counters': { GET: (params: GroupsCountersProps) => { joined: boolean; members: number; @@ -361,22 +361,22 @@ export type GroupsEndpoints = { userMentions: number; }; }; - 'groups.close': { + '/v1/groups.close': { POST: (params: GroupsCloseProps) => {}; }; - 'groups.kick': { + '/v1/groups.kick': { POST: (params: GroupsKickProps) => {}; }; - 'groups.delete': { + '/v1/groups.delete': { POST: (params: GroupsDeleteProps) => {}; }; - 'groups.leave': { + '/v1/groups.leave': { POST: (params: GroupsLeaveProps) => {}; }; - 'groups.roles': { + '/v1/groups.roles': { GET: (params: GroupsRolesProps) => { roles: IGetRoomRoles[] }; }; - 'groups.messages': { + '/v1/groups.messages': { GET: (params: GroupsMessageProps) => PaginatedResult<{ messages: IMessage[]; }>; diff --git a/packages/rest-typings/src/v1/instances.ts b/packages/rest-typings/src/v1/instances.ts index ab37f127ad23..faf085e5ae92 100644 --- a/packages/rest-typings/src/v1/instances.ts +++ b/packages/rest-typings/src/v1/instances.ts @@ -1,7 +1,7 @@ import type { IInstanceStatus } from '@rocket.chat/core-typings'; export type InstancesEndpoints = { - 'instances.get': { + '/v1/instances.get': { GET: () => { instances: ( | IInstanceStatus diff --git a/packages/rest-typings/src/v1/invites.ts b/packages/rest-typings/src/v1/invites.ts index d123c3d18df7..f6e6bd7f70b3 100644 --- a/packages/rest-typings/src/v1/invites.ts +++ b/packages/rest-typings/src/v1/invites.ts @@ -61,13 +61,13 @@ const FindOrCreateInviteParamsSchema = { export const isFindOrCreateInviteParams = ajv.compile(FindOrCreateInviteParamsSchema); export type InvitesEndpoints = { - 'listInvites': { + '/v1/listInvites': { GET: () => Array; }; - 'removeInvite/:_id': { + '/v1/removeInvite/:_id': { DELETE: () => boolean; }; - 'useInviteToken': { + '/v1/useInviteToken': { POST: (params: UseInviteTokenProps) => { room: { rid: IRoom['_id']; @@ -78,10 +78,10 @@ export type InvitesEndpoints = { }; }; }; - 'validateInviteToken': { + '/v1/validateInviteToken': { POST: (params: ValidateInviteTokenProps) => { valid: boolean }; }; - 'findOrCreateInvite': { + '/v1/findOrCreateInvite': { POST: (params: FindOrCreateInviteParams) => IInvite; }; }; diff --git a/packages/rest-typings/src/v1/ldap.ts b/packages/rest-typings/src/v1/ldap.ts index 3fd343cf4c03..1591ff033393 100644 --- a/packages/rest-typings/src/v1/ldap.ts +++ b/packages/rest-typings/src/v1/ldap.ts @@ -22,17 +22,17 @@ const ldapTestSearchPropsSchema = { export const isLdapTestSearch = ajv.compile(ldapTestSearchPropsSchema); export type LDAPEndpoints = { - 'ldap.testConnection': { + '/v1/ldap.testConnection': { POST: () => { message: string; }; }; - 'ldap.testSearch': { + '/v1/ldap.testSearch': { POST: (params: ldapTestSearchProps) => { message: string; }; }; - 'ldap.syncNow': { + '/v1/ldap.syncNow': { POST: () => { message: string; }; diff --git a/packages/rest-typings/src/v1/licenses.ts b/packages/rest-typings/src/v1/licenses.ts index de477d37a813..88b1d34dc23f 100644 --- a/packages/rest-typings/src/v1/licenses.ts +++ b/packages/rest-typings/src/v1/licenses.ts @@ -23,16 +23,16 @@ const licensesAddPropsSchema = { export const isLicensesAddProps = ajv.compile(licensesAddPropsSchema); export type LicensesEndpoints = { - 'licenses.get': { + '/v1/licenses.get': { GET: () => { licenses: Array }; }; - 'licenses.add': { + '/v1/licenses.add': { POST: (params: licensesAddProps) => void; }; - 'licenses.maxActiveUsers': { + '/v1/licenses.maxActiveUsers': { GET: () => { maxActiveUsers: number | null; activeUsers: number }; }; - 'licenses.requestSeatsLink': { + '/v1/licenses.requestSeatsLink': { GET: () => { url: string }; }; }; diff --git a/packages/rest-typings/src/v1/me.ts b/packages/rest-typings/src/v1/me.ts new file mode 100644 index 000000000000..af406ac1d4d4 --- /dev/null +++ b/packages/rest-typings/src/v1/me.ts @@ -0,0 +1,35 @@ +import type { IUser, Serialized } from '@rocket.chat/core-typings'; + +type RawUserData = Serialized< + Pick< + IUser, + | '_id' + | 'type' + | 'name' + | 'username' + | 'emails' + | 'status' + | 'statusDefault' + | 'statusText' + | 'statusConnection' + | 'avatarOrigin' + | 'utcOffset' + | 'language' + | 'settings' + | 'roles' + | 'active' + | 'defaultRoom' + | 'customFields' + | 'statusLivechat' + | 'oauth' + | 'createdAt' + | '_updatedAt' + | 'avatarETag' + > +>; + +export type MeEndpoints = { + '/v1/me': { + GET: () => RawUserData; + }; +}; diff --git a/packages/rest-typings/src/v1/misc.ts b/packages/rest-typings/src/v1/misc.ts index f41ffe905860..c2d36fcc83cd 100644 --- a/packages/rest-typings/src/v1/misc.ts +++ b/packages/rest-typings/src/v1/misc.ts @@ -171,7 +171,7 @@ const MethodCallAnonSchema = { export const isMethodCallAnonProps = ajv.compile(MethodCallAnonSchema); export type MiscEndpoints = { - 'stdout.queue': { + '/v1/stdout.queue': { GET: () => { queue: { id: string; diff --git a/packages/rest-typings/src/v1/oauthapps.ts b/packages/rest-typings/src/v1/oauthapps.ts index 4928257efd1e..9dd2dfe3b984 100644 --- a/packages/rest-typings/src/v1/oauthapps.ts +++ b/packages/rest-typings/src/v1/oauthapps.ts @@ -8,13 +8,13 @@ const ajv = new Ajv({ export type OauthAppsGetParams = { clientId: string } | { appId: string }; export type OAuthAppsEndpoint = { - 'oauth-apps.list': { + '/v1/oauth-apps.list': { GET: (params: { uid: IUser['_id'] }) => { oauthApps: IOAuthApps[]; }; }; - 'oauth-apps.get': { + '/v1/oauth-apps.get': { GET: (params: OauthAppsGetParams) => { oauthApp: IOAuthApps; }; diff --git a/packages/rest-typings/src/v1/omnichannel.ts b/packages/rest-typings/src/v1/omnichannel.ts index fc2ab9e23fd9..ff1a26f8e9b7 100644 --- a/packages/rest-typings/src/v1/omnichannel.ts +++ b/packages/rest-typings/src/v1/omnichannel.ts @@ -778,12 +778,12 @@ const LivechatUsersAgentSchema = { export const isLivechatUsersAgentProps = ajv.compile(LivechatUsersAgentSchema); export type OmnichannelEndpoints = { - 'livechat/appearance': { + '/v1/livechat/appearance': { GET: () => { appearance: ISetting[]; }; }; - 'livechat/visitors.info': { + '/v1/livechat/visitors.info': { GET: (params: LivechatVisitorsInfo) => { visitor: { visitorEmails: Array<{ @@ -792,23 +792,28 @@ export type OmnichannelEndpoints = { }; }; }; - 'livechat/room.onHold': { + '/v1/livechat/room': { + GET: (params: { token: string; rid: IRoom['_id'] }) => { + room: IOmnichannelRoom; + }; + }; + '/v1/livechat/room.onHold': { POST: (params: LivechatRoomOnHold) => void; }; - 'livechat/room.join': { + '/v1/livechat/room.join': { GET: (params: LiveChatRoomJoin) => { success: boolean }; }; - 'livechat/monitors.list': { + '/v1/livechat/monitors.list': { GET: (params: LivechatMonitorsListProps) => PaginatedResult<{ monitors: ILivechatMonitor[]; }>; }; - 'livechat/tags.list': { + '/v1/livechat/tags.list': { GET: (params: LivechatTagsListProps) => PaginatedResult<{ tags: ILivechatTag[]; }>; }; - 'livechat/department': { + '/v1/livechat/department': { GET: (params: LivechatDepartmentProps) => PaginatedResult<{ departments: ILivechatDepartment[]; }>; @@ -817,7 +822,7 @@ export type OmnichannelEndpoints = { agents: any[]; }; }; - 'livechat/department/:_id': { + '/v1/livechat/department/:_id': { GET: (params: LivechatDepartmentId) => { department: ILivechatDepartmentRecord | null; agents?: ILivechatDepartmentAgents[]; @@ -828,39 +833,39 @@ export type OmnichannelEndpoints = { }; DELETE: () => void; }; - 'livechat/department.autocomplete': { + '/v1/livechat/department.autocomplete': { GET: (params: LivechatDepartmentAutocomplete) => { items: ILivechatDepartment[]; }; }; - 'livechat/department/:departmentId/agents': { + '/v1/livechat/department/:departmentId/agents': { GET: (params: LivechatDepartmentDepartmentIdAgentsGET) => PaginatedResult<{ agents: ILivechatDepartmentAgents[] }>; POST: (params: LivechatDepartmentDepartmentIdAgentsPOST) => void; }; - 'livechat/departments.available-by-unit/:id': { + '/v1/livechat/departments.available-by-unit/:id': { GET: (params: LivechatDepartmentsAvailableByUnitIdProps) => PaginatedResult<{ departments: ILivechatDepartment[]; }>; }; - 'livechat/departments.by-unit/': { + '/v1/livechat/departments.by-unit/': { GET: (params: LivechatDepartmentsByUnitProps) => PaginatedResult<{ departments: ILivechatDepartment[]; }>; }; - 'livechat/departments.by-unit/:id': { + '/v1/livechat/departments.by-unit/:id': { GET: (params: LivechatDepartmentsByUnitIdProps) => PaginatedResult<{ departments: ILivechatDepartment[]; }>; }; - 'livechat/department.listByIds': { + '/v1/livechat/department.listByIds': { GET: (params: { ids: string[]; fields?: Record }) => { departments: ILivechatDepartment[]; }; }; - 'livechat/custom-fields': { + '/v1/livechat/custom-fields': { GET: (params: LivechatCustomFieldsProps) => PaginatedResult<{ customFields: [ { @@ -870,25 +875,25 @@ export type OmnichannelEndpoints = { ]; }>; }; - 'livechat/rooms': { + '/v1/livechat/rooms': { GET: (params: LivechatRoomsProps) => PaginatedResult<{ rooms: IOmnichannelRoom[]; }>; }; - 'livechat/:rid/messages': { + '/v1/livechat/:rid/messages': { GET: (params: LivechatRidMessagesProps) => PaginatedResult<{ messages: IMessage[]; }>; }; - 'livechat/users/manager': { + '/v1/livechat/users/manager': { GET: (params: LivechatUsersManagerGETProps) => PaginatedResult<{ users: ILivechatAgent[]; }>; POST: (params: { username: string }) => { success: boolean }; }; - 'livechat/users/manager/:_id': { + '/v1/livechat/users/manager/:_id': { GET: ( params: PaginatedRequest<{ text: string; @@ -897,14 +902,14 @@ export type OmnichannelEndpoints = { DELETE: () => void; }; - 'livechat/users/agent': { + '/v1/livechat/users/agent': { GET: (params: PaginatedRequest<{ text?: string }>) => PaginatedResult<{ users: ILivechatAgent[]; }>; POST: (params: LivechatUsersManagerPOSTProps) => { success: boolean }; }; - 'livechat/users/agent/:_id': { + '/v1/livechat/users/agent/:_id': { GET: ( params: PaginatedRequest<{ text: string; @@ -913,38 +918,38 @@ export type OmnichannelEndpoints = { DELETE: () => { success: boolean }; }; - 'livechat/visitor': { + '/v1/livechat/visitor': { POST: (params: { visitor: ILivechatVisitorDTO }) => { visitor: ILivechatVisitor; }; }; - 'livechat/visitor/:token': { + '/v1/livechat/visitor/:token': { GET: (params: LivechatVisitorTokenGet) => { visitor: ILivechatVisitor }; DELETE: (params: LivechatVisitorTokenDelete) => { visitor: { _id: string; ts: string }; }; }; - 'livechat/visitor/:token/room': { + '/v1/livechat/visitor/:token/room': { GET: (params: LivechatVisitorTokenRoom) => { rooms: IOmnichannelRoom[] }; }; - 'livechat/visitor.callStatus': { + '/v1/livechat/visitor.callStatus': { POST: (params: LivechatVisitorCallStatus) => { token: string; callStatus: string; }; }; - 'livechat/visitor.status': { + '/v1/livechat/visitor.status': { POST: (params: LivechatVisitorStatus) => { token: string; status: string; }; }; - 'livechat/queue': { + '/v1/livechat/queue': { GET: (params: LivechatQueueProps) => { queue: { chats: number; @@ -956,13 +961,21 @@ export type OmnichannelEndpoints = { total: number; }; }; - 'livechat/agents/:uid/departments': { + '/v1/livechat/agents/:uid/departments': { GET: (params: { enableDepartmentsOnly: 'true' | 'false' | '0' | '1' }) => { departments: ILivechatDepartmentAgents[] }; }; - 'canned-responses': { + '/v1/canned-responses': { GET: (params: CannedResponsesProps) => PaginatedResult<{ cannedResponses: IOmnichannelCannedResponse[]; }>; }; + + '/v1/livechat/webrtc.call': { + GET: (params: { rid: string }) => void; + }; + + '/v1/livechat/webrtc.call/:callId': { + PUT: (params: { rid: string; status: 'ended' }) => void; + }; }; diff --git a/packages/rest-typings/src/v1/permissions.ts b/packages/rest-typings/src/v1/permissions.ts index 3128db4ec259..1021c9e72e99 100644 --- a/packages/rest-typings/src/v1/permissions.ts +++ b/packages/rest-typings/src/v1/permissions.ts @@ -54,13 +54,13 @@ const permissionUpdatePropsSchema = { export const isBodyParamsValidPermissionUpdate = ajv.compile(permissionUpdatePropsSchema); export type PermissionsEndpoints = { - 'permissions.listAll': { + '/v1/permissions.listAll': { GET: (params: PermissionsListAllProps) => { update: IPermission[]; remove: IPermission[]; }; }; - 'permissions.update': { + '/v1/permissions.update': { POST: (params: PermissionsUpdateProps) => { permissions: IPermission[]; }; diff --git a/packages/rest-typings/src/v1/push.ts b/packages/rest-typings/src/v1/push.ts index 54568328bc42..c9f439357975 100644 --- a/packages/rest-typings/src/v1/push.ts +++ b/packages/rest-typings/src/v1/push.ts @@ -53,11 +53,11 @@ const PushGetPropsSchema = { export const isPushGetProps = ajv.compile(PushGetPropsSchema); export type PushEndpoints = { - 'push.token': { + '/v1/push.token': { POST: (payload: PushTokenProps) => { result: IPushToken }; DELETE: (payload: { token: string }) => void; }; - 'push.get': { + '/v1/push.get': { GET: (params: PushGetProps) => { data: { message: IMessage; diff --git a/packages/rest-typings/src/v1/roles.ts b/packages/rest-typings/src/v1/roles.ts index 8c4135fc4b5b..499bc7273ea6 100644 --- a/packages/rest-typings/src/v1/roles.ts +++ b/packages/rest-typings/src/v1/roles.ts @@ -198,12 +198,12 @@ type RoleSyncProps = { }; export type RolesEndpoints = { - 'roles.list': { + '/v1/roles.list': { GET: () => { roles: IRole[]; }; }; - 'roles.sync': { + '/v1/roles.sync': { GET: (params: RoleSyncProps) => { roles: { update: IRole[]; @@ -211,36 +211,36 @@ export type RolesEndpoints = { }; }; }; - 'roles.create': { + '/v1/roles.create': { POST: (params: RoleCreateProps) => { role: IRole; }; }; - 'roles.addUserToRole': { + '/v1/roles.addUserToRole': { POST: (params: RoleAddUserToRoleProps) => { role: IRole; }; }; - 'roles.getUsersInRole': { + '/v1/roles.getUsersInRole': { GET: (params: RolesGetUsersInRoleProps) => { users: IUserInRole[]; total: number; }; }; - 'roles.update': { + '/v1/roles.update': { POST: (role: RoleUpdateProps) => { role: IRole; }; }; - 'roles.delete': { + '/v1/roles.delete': { POST: (prop: RoleDeleteProps) => void; }; - 'roles.removeUserFromRole': { + '/v1/roles.removeUserFromRole': { POST: (props: RoleRemoveUserFromRoleProps) => { role: IRole; }; diff --git a/packages/rest-typings/src/v1/rooms.ts b/packages/rest-typings/src/v1/rooms.ts index a57757a8476d..8cf6f32f49d4 100644 --- a/packages/rest-typings/src/v1/rooms.ts +++ b/packages/rest-typings/src/v1/rooms.ts @@ -394,30 +394,27 @@ const RoomsSaveRoomSettingsSchema = { export const isRoomsSaveRoomSettingsProps = ajv.compile(RoomsSaveRoomSettingsSchema); export type RoomsEndpoints = { - 'rooms.autocomplete.channelAndPrivate': { + '/v1/rooms.autocomplete.channelAndPrivate': { GET: (params: RoomsAutoCompleteChannelAndPrivateProps) => { items: IRoom[]; }; }; - 'rooms.autocomplete.channelAndPrivate.withPagination': { - GET: (params: RoomsAutocompleteChannelAndPrivateWithPaginationProps) => { + '/v1/rooms.autocomplete.channelAndPrivate.withPagination': { + GET: (params: RoomsAutocompleteChannelAndPrivateWithPaginationProps) => PaginatedResult<{ items: IRoom[]; - count: number; - offset: number; - total: number; - }; + }>; }; - 'rooms.autocomplete.availableForTeams': { + '/v1/rooms.autocomplete.availableForTeams': { GET: (params: RoomsAutocompleteAvailableForTeamsProps) => { items: IRoom[]; }; }; - 'rooms.info': { + '/v1/rooms.info': { GET: (params: RoomsInfoProps) => { room: IRoom; }; }; - 'rooms.cleanHistory': { + '/v1/rooms.cleanHistory': { POST: (params: { roomId: IRoom['_id']; latest: string; @@ -431,32 +428,45 @@ export type RoomsEndpoints = { ignoreThreads?: boolean; }) => { _id: IRoom['_id']; count: number; success: boolean }; }; - 'rooms.createDiscussion': { + '/v1/rooms.createDiscussion': { POST: (params: RoomsCreateDiscussionProps) => { discussion: IRoom; }; }; - 'rooms.export': { + '/v1/rooms.export': { POST: (params: RoomsExportProps) => { missing?: []; success: boolean; }; }; - 'rooms.adminRooms': { + '/v1/rooms.adminRooms': { GET: (params: RoomsAdminRoomsProps) => PaginatedResult<{ rooms: Pick[] }>; }; - 'rooms.adminRooms.getRoom': { + '/v1/rooms.adminRooms.getRoom': { GET: (params: RoomsAdminRoomsGetRoomProps) => Pick; }; - 'rooms.saveRoomSettings': { + '/v1/rooms.saveRoomSettings': { POST: (params: RoomsSaveRoomSettingsProps) => { success: boolean; rid: string; }; }; - 'rooms.changeArchivationState': { + '/v1/rooms.changeArchivationState': { POST: (params: RoomsChangeArchivationStateProps) => { success: boolean; }; }; + + '/v1/rooms.upload/:rid': { + POST: (params: { + file: File; + description?: string; + avatar?: string; + emoji?: string; + alias?: string; + groupable?: boolean; + msg?: string; + tmid?: string; + }) => { message: IMessage }; + }; }; diff --git a/packages/rest-typings/src/v1/settings.ts b/packages/rest-typings/src/v1/settings.ts index 8e9b09a3bddf..694ea9f59318 100644 --- a/packages/rest-typings/src/v1/settings.ts +++ b/packages/rest-typings/src/v1/settings.ts @@ -65,34 +65,34 @@ type SettingsUpdatePropDefault = { export const isSettingsUpdatePropDefault = (props: Partial): props is SettingsUpdatePropDefault => 'value' in props; export type SettingsEndpoints = { - 'settings.public': { + '/v1/settings.public': { GET: () => PaginatedResult & { settings: Array; }; }; - 'settings.oauth': { + '/v1/settings.oauth': { GET: () => { services: Partial[]; }; }; - 'settings.addCustomOAuth': { + '/v1/settings.addCustomOAuth': { POST: (params: { name: string }) => void; }; - 'settings': { + '/v1/settings': { GET: () => { settings: ISetting[]; }; }; - 'settings/:_id': { + '/v1/settings/:_id': { GET: () => Pick; POST: (params: SettingsUpdateProps) => void; }; - 'service.configurations': { + '/v1/service.configurations': { GET: () => { configurations: Array<{ appId: string; diff --git a/packages/rest-typings/src/v1/statistics.ts b/packages/rest-typings/src/v1/statistics.ts index 837bbf1afa0e..e9424b28c1cc 100644 --- a/packages/rest-typings/src/v1/statistics.ts +++ b/packages/rest-typings/src/v1/statistics.ts @@ -74,10 +74,10 @@ const StatisticsListSchema = { export const isStatisticsListProps = ajv.compile(StatisticsListSchema); export type StatisticsEndpoints = { - 'statistics': { + '/v1/statistics': { GET: (params: StatisticsProps) => IStats; }; - 'statistics.list': { + '/v1/statistics.list': { GET: (params: StatisticsListProps) => { statistics: IStats[]; count: number; @@ -85,7 +85,7 @@ export type StatisticsEndpoints = { total: number; }; }; - 'statistics.telemetry': { + '/v1/statistics.telemetry': { POST: (params: TelemetryPayload) => any; }; }; diff --git a/packages/rest-typings/src/v1/teams/index.ts b/packages/rest-typings/src/v1/teams/index.ts index 5aaccb50533c..82f1b1d0abcf 100644 --- a/packages/rest-typings/src/v1/teams/index.ts +++ b/packages/rest-typings/src/v1/teams/index.ts @@ -51,13 +51,13 @@ export const isTeamPropsWithTeamName = (props: T): props is export const isTeamPropsWithTeamId = (props: T): props is T & { teamId: string } => 'teamId' in props; export type TeamsEndpoints = { - 'teams.list': { + '/v1/teams.list': { GET: () => PaginatedResult & { teams: ITeam[] }; }; - 'teams.listAll': { + '/v1/teams.listAll': { GET: () => { teams: ITeam[] } & PaginatedResult; }; - 'teams.create': { + '/v1/teams.create': { POST: (params: { name: ITeam['name']; type: ITeam['type']; @@ -95,19 +95,19 @@ export type TeamsEndpoints = { }; }; - 'teams.convertToChannel': { + '/v1/teams.convertToChannel': { POST: (params: TeamsConvertToChannelProps) => void; }; - 'teams.addRooms': { + '/v1/teams.addRooms': { POST: (params: { rooms: IRoom['_id'][]; teamId: string } | { rooms: IRoom['_id'][]; teamName: string }) => { rooms: IRoom[] }; }; - 'teams.removeRoom': { + '/v1/teams.removeRoom': { POST: (params: TeamsRemoveRoomProps) => { room: IRoom }; }; - 'teams.members': { + '/v1/teams.members': { GET: ( params: ({ teamId: string } | { teamName: string }) & { status?: string[]; @@ -117,41 +117,41 @@ export type TeamsEndpoints = { ) => PaginatedResult & { members: ITeamMemberInfo[] }; }; - 'teams.addMembers': { + '/v1/teams.addMembers': { POST: (params: TeamsAddMembersProps) => void; }; - 'teams.updateMember': { + '/v1/teams.updateMember': { POST: (params: TeamsUpdateMemberProps) => void; }; - 'teams.removeMember': { + '/v1/teams.removeMember': { POST: (params: TeamsRemoveMemberProps) => void; }; - 'teams.leave': { + '/v1/teams.leave': { POST: (params: TeamsLeaveProps) => void; }; - 'teams.info': { + '/v1/teams.info': { GET: (params: ({ teamId: string } | { teamName: string }) & {}) => { teamInfo: Partial; }; }; - 'teams.autocomplete': { + '/v1/teams.autocomplete': { GET: (params: { name: string }) => { teams: ITeamAutocompleteResult[] }; }; - 'teams.update': { + '/v1/teams.update': { POST: (params: TeamsUpdateProps) => void; }; - 'teams.delete': { + '/v1/teams.delete': { POST: (params: TeamsDeleteProps) => void; }; - 'teams.listRoomsOfUser': { + '/v1/teams.listRoomsOfUser': { GET: ( params: | { @@ -167,7 +167,7 @@ export type TeamsEndpoints = { ) => PaginatedResult & { rooms: IRoom[] }; }; - 'teams.listRooms': { + '/v1/teams.listRooms': { GET: ( params: PaginatedRequest & ({ teamId: string } | { teamName: string }) & { @@ -177,7 +177,7 @@ export type TeamsEndpoints = { ) => PaginatedResult & { rooms: IRoom[] }; }; - 'teams.updateRoom': { + '/v1/teams.updateRoom': { POST: (params: { roomId: IRoom['_id']; isDefault: boolean }) => { room: IRoom; }; diff --git a/packages/rest-typings/src/v1/users.ts b/packages/rest-typings/src/v1/users.ts index dc6e6b736558..df11fb399124 100644 --- a/packages/rest-typings/src/v1/users.ts +++ b/packages/rest-typings/src/v1/users.ts @@ -114,27 +114,36 @@ const UsersResetAvatarSchema = { export const isUsersResetAvatarProps = ajv.compile(UsersResetAvatarSchema); +type UsersPresencePayload = { + users: UserPresence[]; + full: boolean; +}; + +export type UserPresence = Readonly< + Partial> & Required> +>; + export type UsersEndpoints = { - 'users.info': { + '/v1/users.info': { GET: (params: UsersInfo) => { user: IUser; }; }; - 'users.2fa.sendEmailCode': { + '/v1/users.2fa.sendEmailCode': { POST: (params: Users2faSendEmailCode) => void; }; - 'users.autocomplete': { + '/v1/users.autocomplete': { GET: (params: UsersAutocomplete) => { items: Required>[]; }; }; - 'users.listTeams': { + '/v1/users.listTeams': { GET: (params: UsersListTeams) => { teams: Array }; }; - 'users.setAvatar': { + '/v1/users.setAvatar': { POST: (params: UsersSetAvatar) => void; }; - 'users.resetAvatar': { + '/v1/users.resetAvatar': { POST: (params: UsersResetAvatar) => void; }; }; diff --git a/packages/rest-typings/src/v1/videoConference.ts b/packages/rest-typings/src/v1/videoConference.ts index c314e43deb6c..d6a4717e5131 100644 --- a/packages/rest-typings/src/v1/videoConference.ts +++ b/packages/rest-typings/src/v1/videoConference.ts @@ -27,7 +27,7 @@ export const isVideoConferenceJitsiUpdateTimeoutProps = ajv.compile { jitsiTimeout: number; }; diff --git a/packages/rest-typings/src/v1/voip.ts b/packages/rest-typings/src/v1/voip.ts index fbdeafc23fef..516b1131e9ac 100644 --- a/packages/rest-typings/src/v1/voip.ts +++ b/packages/rest-typings/src/v1/voip.ts @@ -489,53 +489,53 @@ const VoipRoomCloseSchema: JSONSchemaType = { export const isVoipRoomCloseProps = ajv.compile(VoipRoomCloseSchema); export type VoipEndpoints = { - 'connector.extension.getRegistrationInfoByUserId': { + '/v1/connector.extension.getRegistrationInfoByUserId': { GET: (params: ConnectorExtensionGetRegistrationInfoByUserId) => IRegistrationInfo | { result: string }; }; - 'voip/queues.getSummary': { + '/v1/voip/queues.getSummary': { GET: () => { summary: IQueueSummary[] }; }; - 'voip/queues.getQueuedCallsForThisExtension': { + '/v1/voip/queues.getQueuedCallsForThisExtension': { GET: (params: VoipQueuesGetQueuedCallsForThisExtension) => IQueueMembershipDetails; }; - 'voip/queues.getMembershipSubscription': { + '/v1/voip/queues.getMembershipSubscription': { GET: (params: VoipQueuesGetMembershipSubscription) => IQueueMembershipSubscription; }; - 'omnichannel/extensions': { + '/v1/omnichannel/extensions': { GET: (params: OmnichannelExtensions) => PaginatedResult<{ extensions: IVoipExtensionWithAgentInfo[] }>; }; - 'omnichannel/extension': { + '/v1/omnichannel/extension': { GET: (params: OmnichannelExtension) => { extensions: string[]; }; }; - 'omnichannel/agent/extension': { + '/v1/omnichannel/agent/extension': { GET: (params: OmnichannelAgentExtensionGET) => { extension: Pick }; POST: (params: OmnichannelAgentExtensionPOST) => void; DELETE: (params: OmnichannelAgentExtensionDELETE) => void; }; - 'omnichannel/agents/available': { + '/v1/omnichannel/agents/available': { GET: (params: OmnichannelAgentsAvailable) => PaginatedResult<{ agents: ILivechatAgent[] }>; }; - 'voip/events': { + '/v1/voip/events': { POST: (params: VoipEvents) => void; }; - 'voip/room': { + '/v1/voip/room': { GET: (params: VoipRoom) => { room: IVoipRoom; newRoom: boolean; }; }; - 'voip/managementServer/checkConnection': { + '/v1/voip/managementServer/checkConnection': { GET: (params: VoipManagementServerCheckConnection) => IManagementServerConnectionStatus; }; - 'voip/callServer/checkConnection': { + '/v1/voip/callServer/checkConnection': { GET: (params: VoipCallServerCheckConnection) => IManagementServerConnectionStatus; }; - 'voip/rooms': { + '/v1/voip/rooms': { GET: (params: VoipRooms) => PaginatedResult<{ rooms: IVoipRoom[] }>; }; - 'voip/room.close': { + '/v1/voip/room.close': { POST: (params: VoipRoomClose) => { rid: string }; }; }; diff --git a/packages/rest-typings/src/v1/webdav.ts b/packages/rest-typings/src/v1/webdav.ts index 51a810b9ce15..a4ee811c546a 100644 --- a/packages/rest-typings/src/v1/webdav.ts +++ b/packages/rest-typings/src/v1/webdav.ts @@ -1,7 +1,7 @@ import type { IWebdavAccount } from '@rocket.chat/core-typings'; export type WebdavEndpoints = { - 'webdav.getMyAccounts': { + '/v1/webdav.getMyAccounts': { GET: () => { accounts: Pick[]; }; diff --git a/packages/ui-contexts/src/ServerContext/ServerContext.ts b/packages/ui-contexts/src/ServerContext/ServerContext.ts index 4920adb44e96..41fe3215f4e4 100644 --- a/packages/ui-contexts/src/ServerContext/ServerContext.ts +++ b/packages/ui-contexts/src/ServerContext/ServerContext.ts @@ -1,5 +1,5 @@ import type { IServerInfo, Serialized } from '@rocket.chat/core-typings'; -import type { Method, PathFor, OperationParams, MatchPathPattern, OperationResult } from '@rocket.chat/rest-typings'; +import type { Method, OperationParams, MatchPathPattern, OperationResult, PathFor } from '@rocket.chat/rest-typings'; import { createContext } from 'react'; import type { ServerMethodName, ServerMethodParameters, ServerMethodReturn } from './methods'; @@ -23,8 +23,7 @@ export type ServerContextValue = { params: Serialized>>, ) => Promise>>>; uploadToEndpoint: ( - endpoint: string, - params: any, + endpoint: PathFor<'POST'>, formData: any, ) => | Promise diff --git a/packages/ui-contexts/src/hooks/useUpload.ts b/packages/ui-contexts/src/hooks/useUpload.ts index 802022643d8b..97bd49703619 100644 --- a/packages/ui-contexts/src/hooks/useUpload.ts +++ b/packages/ui-contexts/src/hooks/useUpload.ts @@ -1,10 +1,9 @@ +import type { PathFor } from '@rocket.chat/rest-typings'; import { useCallback, useContext } from 'react'; import { ServerContext, UploadResult } from '../ServerContext'; -export const useUpload = ( - endpoint: string, -): ((params: any, formData: any) => Promise | { promise: Promise }) => { +export const useUpload = (endpoint: PathFor<'POST'>): ((formData: any) => Promise | { promise: Promise }) => { const { uploadToEndpoint } = useContext(ServerContext); - return useCallback((params, formData: any) => uploadToEndpoint(endpoint, params, formData), [endpoint, uploadToEndpoint]); + return useCallback((formData: any) => uploadToEndpoint(endpoint, formData), [endpoint, uploadToEndpoint]); }; diff --git a/yarn.lock b/yarn.lock index 79f53cff45e9..9d7d07407dd7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4222,6 +4222,25 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/api-client@workspace:^, @rocket.chat/api-client@workspace:packages/api-client": + version: 0.0.0-use.local + resolution: "@rocket.chat/api-client@workspace:packages/api-client" + dependencies: + "@rocket.chat/core-typings": "workspace:^" + "@rocket.chat/rest-typings": "workspace:^" + "@types/jest": ^27.4.1 + "@types/strict-uri-encode": ^2 + eslint: ^8.12.0 + filter-obj: ^3.0.0 + jest: ^27.5.1 + query-string: ^7.1.1 + split-on-first: ^3.0.0 + strict-uri-encode: ^2.0.0 + ts-jest: ^27.1.4 + typescript: ~4.3.5 + languageName: unknown + linkType: soft + "@rocket.chat/apps-engine@npm:1.32.0": version: 1.32.0 resolution: "@rocket.chat/apps-engine@npm:1.32.0" @@ -4772,6 +4791,7 @@ __metadata: "@nivo/line": 0.62.0 "@nivo/pie": 0.73.0 "@playwright/test": ^1.21.1 + "@rocket.chat/api-client": "workspace:^" "@rocket.chat/apps-engine": 1.32.0 "@rocket.chat/core-typings": "workspace:^" "@rocket.chat/css-in-js": ~0.31.12 @@ -4858,6 +4878,7 @@ __metadata: "@types/sharp": ^0.30.2 "@types/sinon": ^10.0.11 "@types/speakeasy": ^2.0.7 + "@types/strict-uri-encode": ^2 "@types/string-strip-html": ^5.0.0 "@types/supertest": ^2.0.11 "@types/ua-parser-js": ^0.7.36 @@ -5020,6 +5041,7 @@ __metadata: source-map: ^0.7.3 speakeasy: ^2.0.0 stream-buffers: ^3.0.2 + strict-uri-encode: ^2.0.0 string-strip-html: ^7.0.3 stylelint: ^13.13.1 stylelint-order: ^4.1.0 @@ -7889,6 +7911,13 @@ __metadata: languageName: node linkType: hard +"@types/strict-uri-encode@npm:^2": + version: 2.0.0 + resolution: "@types/strict-uri-encode@npm:2.0.0" + checksum: e37b6e39fc1440d30bb5f114b3c56a4ecc5db1b7bb1f705bcf607d0eb9e8798953ccaa3792b35c97e74fe61fcc0ff80982d963c22bba056a0745098d4a5c6699 + languageName: node + linkType: hard + "@types/string-strip-html@npm:^5.0.0": version: 5.0.1 resolution: "@types/string-strip-html@npm:5.0.1" @@ -16194,6 +16223,13 @@ __metadata: languageName: node linkType: hard +"filter-obj@npm:^3.0.0": + version: 3.0.0 + resolution: "filter-obj@npm:3.0.0" + checksum: 93bee3cecc2bbd87cb9d786c4ba2ea36fbad5d237aec991deed419dcc892020dd46d0a77c982224ef45d922b1573c77be1588274b9d524c7389ccf8d1a91c330 + languageName: node + linkType: hard + "finalhandler@npm:1.1.2, finalhandler@npm:~1.1.2": version: 1.1.2 resolution: "finalhandler@npm:1.1.2" @@ -27274,6 +27310,18 @@ __metadata: languageName: node linkType: hard +"query-string@npm:^7.1.1": + version: 7.1.1 + resolution: "query-string@npm:7.1.1" + dependencies: + decode-uri-component: ^0.2.0 + filter-obj: ^1.1.0 + split-on-first: ^1.0.0 + strict-uri-encode: ^2.0.0 + checksum: b227d1f588ae93f9f0ad078c6b811295fa151dc5a160a03bb2bac5fa0e6919cb1daa570aad1d288e77c8e89fde5362ba505b1014e6e793da9b1e885b59a690a6 + languageName: node + linkType: hard + "querystring-es3@npm:*, querystring-es3@npm:^0.2.0": version: 0.2.1 resolution: "querystring-es3@npm:0.2.1" @@ -30281,6 +30329,13 @@ __metadata: languageName: node linkType: hard +"split-on-first@npm:^3.0.0": + version: 3.0.0 + resolution: "split-on-first@npm:3.0.0" + checksum: 75dc27ecbac65cfbeab9a3b90cf046307220192d3d7a30e46aa0f19571cc9b4802aac813f3de2cc9b16f2e46aae72f275659b5d2614bb5369c77724d739e5f73 + languageName: node + linkType: hard + "split-string@npm:^3.0.1, split-string@npm:^3.0.2": version: 3.1.0 resolution: "split-string@npm:3.1.0" From 993745d3996450036e8c39150a0b6926cdbbd869 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Wed, 8 Jun 2022 23:23:18 -0600 Subject: [PATCH 15/59] [BREAK] Remove RDStation integration (#25774) ## Proposed changes (including videos or screenshots) ## Issue(s) ## Steps to test or reproduce ## Further comments Co-authored-by: Guilherme Gazzo <5263975+ggazzo@users.noreply.github.com> --- apps/meteor/app/livechat/server/config.ts | 9 --- .../app/livechat/server/hooks/RDStation.js | 62 ------------------- apps/meteor/app/livechat/server/index.js | 1 - .../rocketchat-i18n/i18n/en.i18n.json | 2 - .../meteor/server/startup/migrations/index.ts | 1 + apps/meteor/server/startup/migrations/v268.ts | 10 +++ 6 files changed, 11 insertions(+), 74 deletions(-) delete mode 100644 apps/meteor/app/livechat/server/hooks/RDStation.js create mode 100644 apps/meteor/server/startup/migrations/v268.ts diff --git a/apps/meteor/app/livechat/server/config.ts b/apps/meteor/app/livechat/server/config.ts index 0e4042ce70d4..89339e29e2a2 100644 --- a/apps/meteor/app/livechat/server/config.ts +++ b/apps/meteor/app/livechat/server/config.ts @@ -471,15 +471,6 @@ Meteor.startup(function () { enableQuery: omnichannelEnabledQuery, }); - this.add('Livechat_RDStation_Token', '', { - type: 'string', - group: 'Omnichannel', - public: false, - section: 'RD Station', - i18nLabel: 'RDStation_Token', - enableQuery: omnichannelEnabledQuery, - }); - this.add('Livechat_Routing_Method', 'Auto_Selection', { type: 'select', group: 'Omnichannel', diff --git a/apps/meteor/app/livechat/server/hooks/RDStation.js b/apps/meteor/app/livechat/server/hooks/RDStation.js deleted file mode 100644 index b4f5398018c1..000000000000 --- a/apps/meteor/app/livechat/server/hooks/RDStation.js +++ /dev/null @@ -1,62 +0,0 @@ -import { HTTP } from 'meteor/http'; - -import { settings } from '../../../settings'; -import { callbacks } from '../../../../lib/callbacks'; -import { Livechat } from '../lib/Livechat'; -import { SystemLogger } from '../../../../server/lib/logger/system'; - -function sendToRDStation(room) { - if (!settings.get('Livechat_RDStation_Token')) { - return room; - } - - const livechatData = Livechat.getLivechatRoomGuestInfo(room); - - if (!livechatData.visitor.email) { - return room; - } - - const email = Array.isArray(livechatData.visitor.email) ? livechatData.visitor.email[0].address : livechatData.visitor.email; - - const options = { - headers: { - 'Content-Type': 'application/json', - }, - data: { - token_rdstation: settings.get('Livechat_RDStation_Token'), - identificador: 'rocketchat-livechat', - client_id: livechatData.visitor._id, - email, - }, - }; - - options.data.nome = livechatData.visitor.name || livechatData.visitor.username; - - if (livechatData.visitor.phone) { - options.data.telefone = livechatData.visitor.phone; - } - - if (livechatData.tags) { - options.data.tags = livechatData.tags; - } - - Object.keys(livechatData.customFields || {}).forEach((field) => { - options.data[field] = livechatData.customFields[field]; - }); - - Object.keys(livechatData.visitor.customFields || {}).forEach((field) => { - options.data[field] = livechatData.visitor.customFields[field]; - }); - - try { - HTTP.call('POST', 'https://www.rdstation.com.br/api/1.3/conversions', options); - } catch (e) { - SystemLogger.error('Error sending lead to RD Station ->', e); - } - - return room; -} - -callbacks.add('livechat.closeRoom', sendToRDStation, callbacks.priority.MEDIUM, 'livechat-rd-station-close-room'); - -callbacks.add('livechat.saveInfo', sendToRDStation, callbacks.priority.MEDIUM, 'livechat-rd-station-save-info'); diff --git a/apps/meteor/app/livechat/server/index.js b/apps/meteor/app/livechat/server/index.js index a6f03ba9c4dd..217db0c7a35b 100644 --- a/apps/meteor/app/livechat/server/index.js +++ b/apps/meteor/app/livechat/server/index.js @@ -10,7 +10,6 @@ import './hooks/leadCapture'; import './hooks/markRoomResponded'; import './hooks/offlineMessage'; import './hooks/offlineMessageToChannel'; -import './hooks/RDStation'; import './hooks/saveAnalyticsData'; import './hooks/sendToCRM'; import './hooks/sendToFacebook'; diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index 3a8be97ee912..3e3c83355f4d 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -3596,8 +3596,6 @@ "Rate Limiter_Description": "Control the rate of requests sent or recieved by your server to prevent cyber attacks and scraping.", "Rate_Limiter_Limit_RegisterUser": "Default number calls to the rate limiter for registering a user", "Rate_Limiter_Limit_RegisterUser_Description": "Number of default calls for user registering endpoints(REST and real-time API's), allowed within the time range defined in the API Rate Limiter section.", - "RD Station": "RD Station", - "RDStation_Token": "RD Station Token", "Reached_seat_limit_banner_warning": "*No more seats available* \nThis workspace has reached its seat limit so no more members can join. *[Request More Seats](__url__)*", "React_when_read_only": "Allow Reacting", "React_when_read_only_changed_successfully": "Allow reacting when read only changed successfully", diff --git a/apps/meteor/server/startup/migrations/index.ts b/apps/meteor/server/startup/migrations/index.ts index 440d32220fdf..6c045d2f447b 100644 --- a/apps/meteor/server/startup/migrations/index.ts +++ b/apps/meteor/server/startup/migrations/index.ts @@ -91,4 +91,5 @@ import './v264'; import './v265'; import './v266'; import './v267'; +import './v268'; import './xrun'; diff --git a/apps/meteor/server/startup/migrations/v268.ts b/apps/meteor/server/startup/migrations/v268.ts new file mode 100644 index 000000000000..b3202af1a870 --- /dev/null +++ b/apps/meteor/server/startup/migrations/v268.ts @@ -0,0 +1,10 @@ +import { addMigration } from '../../lib/migrations'; +import { Settings } from '../../../app/models/server/raw'; + +// Removes deprecated RDStation functionality from Omnichannel +addMigration({ + version: 268, + async up() { + await Settings.removeById('Livechat_RDStation_Token'); + }, +}); From c27412b0bcce47745d5b13938353073a5fddcd8f Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Thu, 9 Jun 2022 09:44:00 -0300 Subject: [PATCH 16/59] Regression: fix apps path (#25809) --- apps/meteor/app/api/server/v1/misc.ts | 28 ++-- .../app/apps/client/@types/IOrchestrator.ts | 19 +-- apps/meteor/app/apps/client/orchestrator.ts | 157 +++++++++--------- apps/meteor/client/lib/meteorCallWrapper.ts | 8 +- apps/meteor/client/lib/userData.ts | 27 ++- .../client/providers/ServerProvider.tsx | 8 +- .../views/admin/apps/AppDetailsPage.tsx | 4 +- .../views/admin/apps/hooks/useCategories.ts | 3 +- packages/core-typings/src/IUser.ts | 1 + packages/rest-typings/src/apps/index.ts | 63 ++++++- packages/rest-typings/src/v1/me.ts | 71 ++++---- packages/rest-typings/src/v1/misc.ts | 28 +--- 12 files changed, 251 insertions(+), 166 deletions(-) diff --git a/apps/meteor/app/api/server/v1/misc.ts b/apps/meteor/app/api/server/v1/misc.ts index 9a7b7135a56a..841cca8e885b 100644 --- a/apps/meteor/app/api/server/v1/misc.ts +++ b/apps/meteor/app/api/server/v1/misc.ts @@ -14,6 +14,7 @@ import { isMethodCallAnonProps, isMeteorCall, } from '@rocket.chat/rest-typings'; +import { IUser } from '@rocket.chat/core-typings'; import { hasPermission } from '../../../authorization/server'; import { Users } from '../../../models/server'; @@ -166,17 +167,24 @@ API.v1.addRoute( 'me', { authRequired: true }, { - get() { + async get() { const fields = getDefaultUserFields(); - const user = Users.findOneById(this.userId, { fields }); - - // The password hash shouldn't be leaked but the client may need to know if it exists. - if (user?.services?.password?.bcrypt) { - user.services.password.exists = true; - delete user.services.password.bcrypt; - } - - return API.v1.success(this.getUserInfo(user)); + const { services, ...user } = Users.findOneById(this.userId, { fields }) as IUser; + + return API.v1.success( + this.getUserInfo({ + ...user, + ...(services && { + services: { + ...services, + password: { + // The password hash shouldn't be leaked but the client may need to know if it exists. + exists: Boolean(services?.password?.bcrypt), + } as any, + }, + }), + }), + ); }, }, ); diff --git a/apps/meteor/app/apps/client/@types/IOrchestrator.ts b/apps/meteor/app/apps/client/@types/IOrchestrator.ts index f178cd03960d..55400407a946 100644 --- a/apps/meteor/app/apps/client/@types/IOrchestrator.ts +++ b/apps/meteor/app/apps/client/@types/IOrchestrator.ts @@ -1,4 +1,3 @@ -import { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata/IAppInfo'; import { ISetting } from '@rocket.chat/apps-engine/definition/settings/ISetting'; export interface IDetailedDescription { @@ -148,7 +147,6 @@ export interface IAppLanguage { export interface IAppExternalURL { url: string; - success: boolean; } export interface ICategory { @@ -159,10 +157,10 @@ export interface ICategory { title: string; } -export interface IDeletedInstalledApp { - app: IAppInfo; - success: boolean; -} +// export interface IDeletedInstalledApp { +// app: IAppInfo; +// success: boolean; +// } export interface IAppSynced { app: IAppFromMarketplace; @@ -193,12 +191,3 @@ export interface ISettingsReturn { settings: ISettings; success: boolean; } - -export interface ISettingsPayload { - settings: ISetting[]; -} - -export interface ISettingsSetReturn { - updated: ISettings; - success: boolean; -} diff --git a/apps/meteor/app/apps/client/orchestrator.ts b/apps/meteor/app/apps/client/orchestrator.ts index 0062d6206990..dc37aef84e74 100644 --- a/apps/meteor/app/apps/client/orchestrator.ts +++ b/apps/meteor/app/apps/client/orchestrator.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-var-requires */ +import type { ISetting } from '@rocket.chat/apps-engine/definition/settings'; import { AppClientManager } from '@rocket.chat/apps-engine/client/AppClientManager'; import { IApiEndpointMetadata } from '@rocket.chat/apps-engine/definition/api'; import { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus'; @@ -6,6 +7,7 @@ import { IPermission } from '@rocket.chat/apps-engine/definition/permissions/IPe import { IAppStorageItem } from '@rocket.chat/apps-engine/server/storage/IAppStorageItem'; import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; +import { AppScreenshot, Serialized } from '@rocket.chat/core-typings'; import { App } from '../../../client/views/admin/apps/types'; import { dispatchToastMessage } from '../../../client/lib/toast'; @@ -15,27 +17,23 @@ import { createDeferredValue } from '../lib/misc/DeferredValue'; import { IPricingPlan, EAppPurchaseType, - IAppFromMarketplace, + // IAppFromMarketplace, IAppLanguage, IAppExternalURL, ICategory, - IDeletedInstalledApp, - IAppSynced, - IAppScreenshots, + // IAppSynced, + // IAppScreenshots, + // IScreenshot, IAuthor, IDetailedChangelog, IDetailedDescription, ISubscriptionInfo, - ISettingsReturn, - ISettingsPayload, - ISettingsSetReturn, } from './@types/IOrchestrator'; import { AppWebsocketReceiver } from './communication'; import { handleI18nResources } from './i18n'; import { RealAppsEngineUIHost } from './RealAppsEngineUIHost'; - -const { APIClient } = require('../../utils'); -const { hasAtLeastOnePermission } = require('../../authorization'); +import { APIClient } from '../../utils/client'; +import { hasAtLeastOnePermission } from '../../authorization/client'; export interface IAppsFromMarketplace { price: number; @@ -123,8 +121,9 @@ class AppClientOrchestrator { } } - public screenshots(appId: string): IAppScreenshots { - return APIClient.get(`/v1/apps/${appId}/screenshots`); + public async screenshots(appId: string): Promise { + const { screenshots } = await APIClient.get(`/apps/${appId}/screenshots`); + return screenshots; } public isEnabled(): Promise | undefined { @@ -132,79 +131,87 @@ class AppClientOrchestrator { } public async getApps(): Promise { - const { apps } = await APIClient.get('/v1/apps'); - return apps; + const result = await APIClient.get('/apps'); + if ('apps' in result) { + return result.apps; + } + throw new Error('Apps not found'); } - public async getAppsFromMarketplace(): Promise { - const appsOverviews: IAppFromMarketplace[] = await APIClient.get('/v1/apps', { marketplace: 'true' }); - return appsOverviews.map((app: IAppFromMarketplace) => { - const { latest, price, pricingPlans, purchaseType, isEnterpriseOnly, modifiedAt } = app; - return { - ...latest, - price, - pricingPlans, - purchaseType, - isEnterpriseOnly, - modifiedAt, - }; - }); + public async getAppsFromMarketplace(): Promise { + const result = await APIClient.get('/apps', { marketplace: 'true' }); + + if ('apps' in result) { + const { apps: appsOverviews } = result; + return appsOverviews.map((app) => { + const { latest, price, pricingPlans, purchaseType, isEnterpriseOnly, modifiedAt } = app; + return { + ...latest, + price, + pricingPlans, + purchaseType, + isEnterpriseOnly, + modifiedAt, + }; + }); + } + throw new Error('Apps not found'); } public async getAppsOnBundle(bundleId: string): Promise { - const { apps } = await APIClient.get(`/v1/apps/bundles/${bundleId}/apps`); + const { apps } = await APIClient.get(`/apps/bundles/${bundleId}/apps`); return apps; } public async getAppsLanguages(): Promise { - const { apps } = await APIClient.get('/v1/apps/languages'); + const { apps } = await APIClient.get('/apps/languages'); return apps; } public async getApp(appId: string): Promise { - const { app } = await APIClient.get(`/v1/apps/${appId}`); + const { app } = await APIClient.get(`/apps/${appId}` as any); return app; } public async getAppFromMarketplace(appId: string, version: string): Promise { - const { app } = await APIClient.get(`/v1/apps/${appId}`, { - marketplace: 'true', - version, - }); - return app; + const result = await APIClient.get( + `/apps/${appId}` as any, + { + marketplace: 'true', + version, + } as any, + ); + return result; } public async getLatestAppFromMarketplace(appId: string, version: string): Promise { - const { app } = await APIClient.get(`/v1/apps/${appId}`, { - marketplace: 'true', - update: 'true', - appVersion: version, - }); + const { app } = await APIClient.get( + `/apps/${appId}` as any, + { + marketplace: 'true', + update: 'true', + appVersion: version, + } as any, + ); return app; } - public async getAppSettings(appId: string): Promise { - const { settings } = await APIClient.get(`/v1/apps/${appId}/settings`); - return settings; - } - - public async setAppSettings(appId: string, settings: ISettingsPayload): Promise { - const { updated } = await APIClient.post(`/v1/apps/${appId}/settings`, undefined, { settings }); - return updated; + public async setAppSettings(appId: string, settings: ISetting[]): Promise { + await APIClient.post(`/apps/${appId}/settings`, { settings }); } public async getAppApis(appId: string): Promise { - const { apis } = await APIClient.get(`/v1/apps/${appId}/apis`); + const { apis } = await APIClient.get(`/apps/${appId}/apis`); return apis; } public async getAppLanguages(appId: string): Promise { - const { languages } = await APIClient.get(`/v1/apps/${appId}/languages`); + const { languages } = await APIClient.get(`/apps/${appId}/languages`); return languages; } - public async installApp(appId: string, version: string, permissionsGranted: IPermission[]): Promise { - const { app } = await APIClient.post('/v1/apps/', { + public async installApp(appId: string, version: string, permissionsGranted: IPermission[]): Promise { + const { app } = await APIClient.post('/apps', { appId, marketplace: true, version, @@ -214,48 +221,48 @@ class AppClientOrchestrator { } public async updateApp(appId: string, version: string, permissionsGranted: IPermission[]): Promise { - const { app } = await APIClient.post(`/v1/apps/${appId}`, { + const result = (await (APIClient.post as any)(`/apps/${appId}` as any, { appId, marketplace: true, version, permissionsGranted, - }); - return app; - } + })) as any; - public uninstallApp(appId: string): IDeletedInstalledApp { - return APIClient.delete(`apps/${appId}`); - } - - public syncApp(appId: string): IAppSynced { - return APIClient.post(`/v1/apps/${appId}/sync`); + if ('app' in result) { + return result; + } + throw new Error('App not found'); } public async setAppStatus(appId: string, status: AppStatus): Promise { - const { status: effectiveStatus } = await APIClient.post(`/v1/apps/${appId}/status`, { status }); + const { status: effectiveStatus } = await APIClient.post(`/apps/${appId}/status`, { status }); return effectiveStatus; } - public enableApp(appId: string): Promise { - return this.setAppStatus(appId, AppStatus.MANUALLY_ENABLED); - } - public disableApp(appId: string): Promise { return this.setAppStatus(appId, AppStatus.MANUALLY_ENABLED); } - public buildExternalUrl(appId: string, purchaseType = 'buy', details = false): IAppExternalURL { - return APIClient.get('/v1/apps', { + public async buildExternalUrl(appId: string, purchaseType: 'buy' | 'subscription' = 'buy', details = false): Promise { + const result = await APIClient.get('/apps', { buildExternalUrl: 'true', appId, purchaseType, - details, + details: `${details}`, }); + + if ('url' in result) { + return result; + } + throw new Error('Failed to build external url'); } - public async getCategories(): Promise { - const categories = await APIClient.get('/v1/apps', { categories: 'true' }); - return categories; + public async getCategories(): Promise[]> { + const result = await APIClient.get('/apps', { categories: 'true' }); + if ('categories' in result) { + return result.categories; + } + throw new Error('Categories not found'); } public getUIHost(): RealAppsEngineUIHost { @@ -267,7 +274,7 @@ export const Apps = new AppClientOrchestrator(); Meteor.startup(() => { CachedCollectionManager.onLogin(() => { - Meteor.call('apps/is-enabled', (error: Error, isEnabled: boolean) => { + Meteor.call('/apps/is-enabled', (error: Error, isEnabled: boolean) => { if (error) { Apps.handleError(error); return; @@ -279,7 +286,7 @@ Meteor.startup(() => { }); Tracker.autorun(() => { - const isEnabled = settings.get('Apps_Framework_enabled'); + const isEnabled = settings.get('/Apps_Framework_enabled'); Apps.load(isEnabled); }); }); diff --git a/apps/meteor/client/lib/meteorCallWrapper.ts b/apps/meteor/client/lib/meteorCallWrapper.ts index 2615b6c4385d..7368cb328e38 100644 --- a/apps/meteor/client/lib/meteorCallWrapper.ts +++ b/apps/meteor/client/lib/meteorCallWrapper.ts @@ -49,15 +49,13 @@ function wrapMeteorDDPCalls(): void { }); Meteor.connection.onMessage(_message); }; + const method = encodeURIComponent(message.method.replace(/\//g, ':')); - APIClient.post( - `/v1/${endpoint}/${encodeURIComponent(message.method.replace(/\//g, ':'))}` as Parameters[0], - restParams as any, - ) + APIClient.post(`/v1/${endpoint}/${method}`, restParams) .then(({ message: _message }) => { processResult(_message); if (message.method === 'login') { - const parsedMessage = DDPCommon.parseDDP(_message) as { result?: { token?: string } }; + const parsedMessage = DDPCommon.parseDDP(_message as any) as { result?: { token?: string } }; if (parsedMessage.result?.token) { Meteor.loginWithToken(parsedMessage.result.token); } diff --git a/apps/meteor/client/lib/userData.ts b/apps/meteor/client/lib/userData.ts index 112028faa0cc..a75cc865f191 100644 --- a/apps/meteor/client/lib/userData.ts +++ b/apps/meteor/client/lib/userData.ts @@ -81,10 +81,35 @@ export const synchronizeUserData = async (uid: Meteor.User['_id']): Promise ({ + ...token, + when: new Date(token.when), + })), + }, + }), + }), + ...(lastLogin && { + lastLogin: new Date(lastLogin), + }), + ldap: Boolean(ldap), createdAt: new Date(userData.createdAt), _updatedAt: new Date(userData._updatedAt), }); diff --git a/apps/meteor/client/providers/ServerProvider.tsx b/apps/meteor/client/providers/ServerProvider.tsx index 18e6ac307680..50d288d73ec1 100644 --- a/apps/meteor/client/providers/ServerProvider.tsx +++ b/apps/meteor/client/providers/ServerProvider.tsx @@ -30,20 +30,20 @@ const callEndpoint = >( ): Promise>>> => { switch (method) { case 'GET': - return APIClient.get(path as Parameters[0], params) as any; + return APIClient.get(path as Parameters[0], params as any | undefined) as any; case 'POST': - return APIClient.post(path as Parameters[0], params) as ReturnType; + return APIClient.post(path as Parameters[0], params as never) as ReturnType; case 'DELETE': - return APIClient.delete(path as Parameters[0], params) as ReturnType; + return APIClient.delete(path as Parameters[0], params as never) as ReturnType; default: throw new Error('Invalid HTTP method'); } }; -const uploadToEndpoint = (endpoint: PathFor<'POST'>, formData: any): Promise => APIClient.post(endpoint, formData); +const uploadToEndpoint = (endpoint: PathFor<'POST'>, formData: any): Promise => APIClient.post(endpoint, formData as never); const getStream = (streamName: string, options: {} = {}): ((eventName: string, callback: (data: T) => void) => () => void) => { const streamer = Meteor.StreamerCentral.instances[streamName] diff --git a/apps/meteor/client/views/admin/apps/AppDetailsPage.tsx b/apps/meteor/client/views/admin/apps/AppDetailsPage.tsx index 2d1748e392cf..c78ece156ece 100644 --- a/apps/meteor/client/views/admin/apps/AppDetailsPage.tsx +++ b/apps/meteor/client/views/admin/apps/AppDetailsPage.tsx @@ -4,7 +4,7 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useTranslation, useCurrentRoute, useRoute, useRouteParameter } from '@rocket.chat/ui-contexts'; import React, { useState, useCallback, useRef, FC } from 'react'; -import { ISettings, ISettingsPayload } from '../../../../app/apps/client/@types/IOrchestrator'; +import { ISettings } from '../../../../app/apps/client/@types/IOrchestrator'; import { Apps } from '../../../../app/apps/client/orchestrator'; import Page from '../../../components/Page'; import APIsDisplay from './APIsDisplay'; @@ -49,7 +49,7 @@ const AppDetailsPage: FC<{ id: string }> = function AppDetailsPage({ id }) { (Object.values(settings || {}) as ISetting[]).map((value) => ({ ...value, value: current?.[value.id], - })) as unknown as ISettingsPayload, + })), ); } catch (e) { handleAPIError(e); diff --git a/apps/meteor/client/views/admin/apps/hooks/useCategories.ts b/apps/meteor/client/views/admin/apps/hooks/useCategories.ts index 075cc139f8ca..61dfae4e2999 100644 --- a/apps/meteor/client/views/admin/apps/hooks/useCategories.ts +++ b/apps/meteor/client/views/admin/apps/hooks/useCategories.ts @@ -1,7 +1,6 @@ import { useTranslation } from '@rocket.chat/ui-contexts'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { ICategory } from '../../../../../app/apps/client/@types/IOrchestrator'; import { Apps } from '../../../../../app/apps/client/orchestrator'; import { CategoryDropdownItem, CategoryDropDownListProps } from '../definitions/CategoryDropdownDefinitions'; import { handleAPIError } from '../helpers'; @@ -21,7 +20,7 @@ export const useCategories = (): [ try { const fetchedCategories = await Apps.getCategories(); - const mappedCategories = fetchedCategories.map((currentCategory: ICategory) => ({ + const mappedCategories = fetchedCategories.map((currentCategory) => ({ id: currentCategory.id, label: currentCategory.title, checked: false, diff --git a/packages/core-typings/src/IUser.ts b/packages/core-typings/src/IUser.ts index 9d0b4547d639..0dde24a2d7ba 100644 --- a/packages/core-typings/src/IUser.ts +++ b/packages/core-typings/src/IUser.ts @@ -116,6 +116,7 @@ export interface IUser extends IRocketChatRecord { status?: UserStatus; statusConnection?: string; lastLogin?: Date; + bio?: string; avatarOrigin?: string; avatarETag?: string; utcOffset?: number; diff --git a/packages/rest-typings/src/apps/index.ts b/packages/rest-typings/src/apps/index.ts index 50b9160d23e2..d7f6fd10c9b2 100644 --- a/packages/rest-typings/src/apps/index.ts +++ b/packages/rest-typings/src/apps/index.ts @@ -1,13 +1,26 @@ import type { IApiEndpointMetadata } from '@rocket.chat/apps-engine/definition/api'; +import type { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus'; import type { IExternalComponent } from '@rocket.chat/apps-engine/definition/externalComponent'; +import type { IPermission } from '@rocket.chat/apps-engine/definition/permissions/IPermission'; +import type { ISetting } from '@rocket.chat/apps-engine/definition/settings'; import type { IUIActionButton } from '@rocket.chat/apps-engine/definition/ui'; -import type { ISetting, AppScreenshot, App } from '@rocket.chat/core-typings'; +import type { AppScreenshot, App } from '@rocket.chat/core-typings'; export type AppsEndpoints = { '/apps/externalComponents': { GET: () => { externalComponents: IExternalComponent[] }; }; + '/apps/:id': { + GET: (params: { marketplace?: 'true' | 'false'; version?: string; appVersion?: string; update?: 'true' | 'false' }) => { + app: App; + }; + DELETE: () => void; + POST: (params: { marketplace: boolean; version: string; permissionsGranted: IPermission[]; appId: string }) => { + app: App; + }; + }; + '/apps/actionButtons': { GET: () => IUIActionButton[]; }; @@ -18,8 +31,9 @@ export type AppsEndpoints = { '/apps/:id/settings': { GET: () => { - [key: string]: ISetting; + settings: { [key: string]: ISetting }; }; + POST: (params: { settings: ISetting[] }) => { updated: { [key: string]: ISetting } }; }; '/apps/:id/screenshots': { @@ -34,8 +48,49 @@ export type AppsEndpoints = { }; }; - '/apps/:id': { - GET: (params: { marketplace?: 'true' | 'false'; update?: 'true' | 'false'; appVersion: string }) => { + '/apps/bundles/:id/apps': { + GET: () => { + apps: App[]; + }; + }; + + '/apps/:id/sync': { + POST: () => { + app: App; + }; + }; + + '/apps/:id/status': { + POST: (params: { status: AppStatus }) => { + status: string; + }; + }; + + '/apps': { + GET: + | ((params: { buildExternalUrl: 'true'; purchaseType?: 'buy' | 'subscription'; appId?: string; details?: 'true' | 'false' }) => { + url: string; + }) + | ((params: { + purchaseType?: 'buy' | 'subscription'; + marketplace?: 'true' | 'false'; + version?: string; + appId?: string; + details?: 'true' | 'false'; + }) => { + apps: App[]; + }) + | ((params: { categories: 'true' | 'false' }) => { + categories: { + createdDate: string; + description: string; + id: string; + modifiedDate: Date; + title: string; + }[]; + }); + + POST: (params: { appId: string; marketplace: boolean; version: string; permissionsGranted: IPermission[] }) => { app: App; }; }; diff --git a/packages/rest-typings/src/v1/me.ts b/packages/rest-typings/src/v1/me.ts index af406ac1d4d4..048bd03f1f97 100644 --- a/packages/rest-typings/src/v1/me.ts +++ b/packages/rest-typings/src/v1/me.ts @@ -1,35 +1,48 @@ -import type { IUser, Serialized } from '@rocket.chat/core-typings'; +import type { IUser } from '@rocket.chat/core-typings'; -type RawUserData = Serialized< - Pick< - IUser, - | '_id' - | 'type' - | 'name' - | 'username' - | 'emails' - | 'status' - | 'statusDefault' - | 'statusText' - | 'statusConnection' - | 'avatarOrigin' - | 'utcOffset' - | 'language' - | 'settings' - | 'roles' - | 'active' - | 'defaultRoom' - | 'customFields' - | 'statusLivechat' - | 'oauth' - | 'createdAt' - | '_updatedAt' - | 'avatarETag' - > ->; +type Keys = + | 'name' + | 'username' + | 'nickname' + | 'emails' + | 'status' + | 'statusDefault' + | 'statusText' + | 'statusConnection' + | 'bio' + | 'avatarOrigin' + | 'utcOffset' + | 'language' + | 'settings' + | 'idleTimeLimit' + | 'roles' + | 'active' + | 'defaultRoom' + | 'customFields' + | 'requirePasswordChange' + | 'requirePasswordChangeReason' + | 'services.github' + | 'services.gitlab' + | 'services.tokenpass' + | 'services.password.bcrypt' + | 'services.totp.enabled' + | 'services.email2fa.enabled' + | 'statusLivechat' + | 'banners' + | 'oauth.authorizedClients' + | '_updatedAt' + | 'avatarETag' + | 'extension'; export type MeEndpoints = { '/v1/me': { - GET: () => RawUserData; + GET: (params: { fields: Record | Record; user: IUser }) => IUser & { + email?: string; + settings: { + profile: {}; + preferences: unknown; + }; + avatarUrl: string; + }; }; }; diff --git a/packages/rest-typings/src/v1/misc.ts b/packages/rest-typings/src/v1/misc.ts index c2d36fcc83cd..5271af72212b 100644 --- a/packages/rest-typings/src/v1/misc.ts +++ b/packages/rest-typings/src/v1/misc.ts @@ -180,45 +180,35 @@ export type MiscEndpoints = { }[]; }; }; - 'me': { - GET: (params: { fields: { [k: string]: number }; user: IUser }) => IUser & { - email?: string; - settings: { - profile: {}; - preferences: unknown; - }; - avatarUrl: string; - }; - }; - 'shield.svg': { + '/v1/shield.svg': { GET: (params: ShieldSvg) => { svg: string; }; }; - 'spotlight': { + '/v1/spotlight': { GET: (params: Spotlight) => { users: Pick[]; rooms: IRoom[]; }; }; - 'directory': { + '/v1/directory': { GET: (params: Directory) => PaginatedResult<{ result: (IUser | IRoom | ITeam)[]; }>; }; - 'method.call': { - POST: (params: MethodCall) => { - result: unknown; + '/v1/method.call/:method': { + POST: (params: { message: string }) => { + message: unknown; }; }; - 'method.callAnon': { - POST: (params: MethodCallAnon) => { - result: unknown; + '/v1/method.callAnon/:method': { + POST: (params: { message: string }) => { + message: unknown; }; }; }; From 11a9d235895b70aa8b4f3536c087c0c31858fc7a Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Thu, 9 Jun 2022 07:19:31 -0600 Subject: [PATCH 17/59] Chore: use params instead of URL building on livechat endpoints (#25810) ## Proposed changes (including videos or screenshots) ## Issue(s) ## Steps to test or reproduce ## Further comments --- .../livechat/client/lib/stream/queueManager.js | 2 +- .../client/views/app/dialog/closeRoom.js | 4 ++-- .../client/views/app/livechatReadOnly.js | 6 +++--- .../views/app/tabbar/contactChatHistory.js | 18 ++++++++++-------- .../client/oauth/oauth2-client.js | 2 +- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/apps/meteor/app/livechat/client/lib/stream/queueManager.js b/apps/meteor/app/livechat/client/lib/stream/queueManager.js index 1a17c2169b30..10bdfe1d5820 100644 --- a/apps/meteor/app/livechat/client/lib/stream/queueManager.js +++ b/apps/meteor/app/livechat/client/lib/stream/queueManager.js @@ -68,7 +68,7 @@ const updateInquiries = async (inquiries = []) => inquiries.forEach((inquiry) => LivechatInquiry.upsert({ _id: inquiry._id }, { ...inquiry, _updatedAt: new Date(inquiry._updatedAt) })); const getAgentsDepartments = async (userId) => { - const { departments } = await APIClient.get(`/v1/livechat/agents/${userId}/departments?enabledDepartmentsOnly=true`); + const { departments } = await APIClient.get(`/v1/livechat/agents/${userId}/departments`, { enabledDepartmentsOnly: true }); return departments; }; diff --git a/apps/meteor/app/livechat/client/views/app/dialog/closeRoom.js b/apps/meteor/app/livechat/client/views/app/dialog/closeRoom.js index 779b7e9f5938..767bd0168d75 100644 --- a/apps/meteor/app/livechat/client/views/app/dialog/closeRoom.js +++ b/apps/meteor/app/livechat/client/views/app/dialog/closeRoom.js @@ -167,11 +167,11 @@ Template.closeRoom.onCreated(async function () { this.onEnterTag = () => this.invalidTags.set(!validateRoomTags(this.tagsRequired.get(), this.tags.get())); const { rid } = Template.currentData(); - const { room } = await APIClient.get(`/v1/rooms.info?roomId=${rid}`); + const { room } = await APIClient.get(`/v1/rooms.info`, { roomId: rid }); this.tags.set(room?.tags || []); if (room?.departmentId) { - const { department } = await APIClient.get(`/v1/livechat/department/${room.departmentId}?includeAgents=false`); + const { department } = await APIClient.get(`/v1/livechat/department/${room.departmentId}`, { includeAgents: false }); this.tagsRequired.set(department?.requestTagBeforeClosingChat); } diff --git a/apps/meteor/app/livechat/client/views/app/livechatReadOnly.js b/apps/meteor/app/livechat/client/views/app/livechatReadOnly.js index a42d81f029bb..e860e67869a6 100644 --- a/apps/meteor/app/livechat/client/views/app/livechatReadOnly.js +++ b/apps/meteor/app/livechat/client/views/app/livechatReadOnly.js @@ -61,7 +61,7 @@ Template.livechatReadOnly.events({ event.stopPropagation(); try { - const { success } = (await APIClient.get(`/v1/livechat/room.join?roomId=${this.rid}`)) || {}; + const { success } = (await APIClient.get(`/v1/livechat/room.join`, { roomId: this.rid })) || {}; if (!success) { throw new Meteor.Error('error-join-room', 'Error joining room'); } @@ -99,13 +99,13 @@ Template.livechatReadOnly.onCreated(async function () { this.loadRoomAndInquiry = async (roomId) => { this.preparing.set(true); - const { inquiry } = await APIClient.get(`/v1/livechat/inquiries.getOne?roomId=${roomId}`); + const { inquiry } = await APIClient.get(`/v1/livechat/inquiries.getOne`, { roomId }); this.inquiry.set(inquiry); if (inquiry && inquiry._id) { inquiryDataStream.on(inquiry._id, this.updateInquiry); } - const { room } = await APIClient.get(`/v1/rooms.info?roomId=${roomId}`); + const { room } = await APIClient.get(`/v1/rooms.info`, { roomId }); this.room.set(room); if (room && room._id) { RoomManager.roomStream.on(roomId, (room) => this.room.set(room)); diff --git a/apps/meteor/app/livechat/client/views/app/tabbar/contactChatHistory.js b/apps/meteor/app/livechat/client/views/app/tabbar/contactChatHistory.js index 10e05932b3fb..690402564a14 100644 --- a/apps/meteor/app/livechat/client/views/app/tabbar/contactChatHistory.js +++ b/apps/meteor/app/livechat/client/views/app/tabbar/contactChatHistory.js @@ -70,22 +70,24 @@ Template.contactChatHistory.onCreated(async function () { const offset = this.offset.get(); const searchTerm = this.searchTerm.get(); - let baseUrl = `/v1/livechat/visitors.searchChats/room/${ - currentData.rid - }/visitor/${this.visitorId.get()}?count=${limit}&offset=${offset}&closedChatsOnly=true&servedChatsOnly=true`; - if (searchTerm) { - baseUrl += `&searchText=${searchTerm}`; - } + const baseUrl = `/v1/livechat/visitors.searchChats/room/${currentData.rid}/visitor/${this.visitorId.get()}`; + const params = { + count: limit, + offset, + closedChatsOnly: true, + servedChatsOnly: true, + ...(searchTerm && { searchText: searchTerm }), + }; this.isLoading.set(true); - const { history, total } = await APIClient.get(baseUrl); + const { history, total } = await APIClient.get(baseUrl, params); this.history.set(offset === 0 ? history : this.history.get().concat(history)); this.hasMore.set(total > this.history.get().length); this.isLoading.set(false); }); this.autorun(async () => { - const { room } = await APIClient.get(`/v1/rooms.info?roomId=${currentData.rid}`); + const { room } = await APIClient.get(`/v1/rooms.info`, { roomId: currentData.rid }); if (room?.v) { this.visitorId.set(room.v._id); } diff --git a/apps/meteor/app/oauth2-server-config/client/oauth/oauth2-client.js b/apps/meteor/app/oauth2-server-config/client/oauth/oauth2-client.js index 55d7162d7f3a..05edccb9972d 100644 --- a/apps/meteor/app/oauth2-server-config/client/oauth/oauth2-client.js +++ b/apps/meteor/app/oauth2-server-config/client/oauth/oauth2-client.js @@ -7,7 +7,7 @@ import { APIClient } from '../../../utils/client'; Template.authorize.onCreated(async function () { this.oauthApp = new ReactiveVar({}); - const { oauthApp } = await APIClient.get(`/v1/oauth-apps.get?clientId=${this.data.client_id()}`); + const { oauthApp } = await APIClient.get(`/v1/oauth-apps.get`, { clientId: this.data.client_id() }); this.oauthApp.set(oauthApp); }); From 6b3908bf6e9496e711b3da9542c62ba87ba6960f Mon Sep 17 00:00:00 2001 From: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Date: Thu, 9 Jun 2022 18:49:43 +0530 Subject: [PATCH 18/59] [FIX] allow only livechat-agents to be contact manager for any omnichannel contact (#25451) ## Proposed changes (including videos or screenshots) ## Issue(s) ![image](https://user-images.githubusercontent.com/34130764/169371479-30b0b88b-08e8-4975-8f36-6387d968a568.png) ## Steps to test or reproduce ## Further comments Tasks: - [x] Frontend change on auto-select component - [x] Backend API checks --- .../app/lib/server/functions/deleteUser.ts | 8 ++--- .../app/livechat/server/api/v1/contact.js | 4 ++- .../app/livechat/server/lib/Contacts.js | 13 ++++++++ .../app/livechat/server/lib/Livechat.js | 3 +- .../models/server/models/LivechatVisitors.js | 14 -------- .../app/models/server/raw/LivechatVisitors.ts | 17 +++++++++- .../additionalForms/ContactManager.js | 32 ++++++++++++++++--- packages/core-typings/src/ILivechatVisitor.ts | 3 ++ 8 files changed, 68 insertions(+), 26 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/deleteUser.ts b/apps/meteor/app/lib/server/functions/deleteUser.ts index f9901701628a..63339692bb5d 100644 --- a/apps/meteor/app/lib/server/functions/deleteUser.ts +++ b/apps/meteor/app/lib/server/functions/deleteUser.ts @@ -3,8 +3,8 @@ import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import { FileProp } from '@rocket.chat/core-typings'; import { FileUpload } from '../../../file-upload/server'; -import { Users, Subscriptions, Messages, Rooms, LivechatDepartmentAgents, LivechatVisitors } from '../../../models/server'; -import { FederationServers, Integrations } from '../../../models/server/raw'; +import { Users, Subscriptions, Messages, Rooms, LivechatDepartmentAgents } from '../../../models/server'; +import { FederationServers, Integrations, LivechatVisitors } from '../../../models/server/raw'; import { settings } from '../../../settings/server'; import { updateGroupDMsName } from './updateGroupDMsName'; import { relinquishRoomOwnerships } from './relinquishRoomOwnerships'; @@ -65,13 +65,13 @@ export async function deleteUser(userId: string, confirmRelinquish = false): Pro if (user.roles.includes('livechat-agent')) { // Remove user as livechat agent LivechatDepartmentAgents.removeByAgentId(userId); - LivechatVisitors.removeContactManagerByUsername(user.username); + await LivechatVisitors.removeContactManagerByUsername(user.username); } if (user.roles.includes('livechat-monitor')) { // Remove user as Unit Monitor LivechatUnitMonitors.removeByMonitorId(userId); - LivechatVisitors.removeContactManagerByUsername(user.username); + await LivechatVisitors.removeContactManagerByUsername(user.username); } // removes user's avatar diff --git a/apps/meteor/app/livechat/server/api/v1/contact.js b/apps/meteor/app/livechat/server/api/v1/contact.js index 21cc25519faa..41a2ee9ec6d3 100644 --- a/apps/meteor/app/livechat/server/api/v1/contact.js +++ b/apps/meteor/app/livechat/server/api/v1/contact.js @@ -18,7 +18,9 @@ API.v1.addRoute( email: Match.Maybe(String), phone: Match.Maybe(String), customFields: Match.Maybe(Object), - contactManager: Match.Maybe(Object), + contactManager: Match.Maybe({ + username: String, + }), }); const contact = Contacts.registerContact(this.bodyParams); diff --git a/apps/meteor/app/livechat/server/lib/Contacts.js b/apps/meteor/app/livechat/server/lib/Contacts.js index 980828f0a360..24909685e6a9 100644 --- a/apps/meteor/app/livechat/server/lib/Contacts.js +++ b/apps/meteor/app/livechat/server/lib/Contacts.js @@ -1,7 +1,9 @@ import { check } from 'meteor/check'; +import { Meteor } from 'meteor/meteor'; import s from 'underscore.string'; import { LivechatVisitors, LivechatCustomField, LivechatRooms, Rooms, LivechatInquiry, Subscriptions } from '../../../models'; +import { Users } from '../../../models/server/raw'; export const Contacts = { registerContact({ token, name, email, phone, username, customFields = {}, contactManager = {} } = {}) { @@ -9,6 +11,17 @@ export const Contacts = { const visitorEmail = s.trim(email).toLowerCase(); + if (contactManager?.username) { + // verify if the user exists with this username and has a livechat-agent role + const user = Promise.await(Users.findOneByUsername(contactManager.username, { projection: { roles: 1 } })); + if (!user) { + throw new Meteor.Error('error-contact-manager-not-found', `No user found with username ${contactManager.username}`); + } + if (!user.roles || !Array.isArray(user.roles) || !user.roles.includes('livechat-agent')) { + throw new Meteor.Error('error-invalid-contact-manager', 'The contact manager must have the role "livechat-agent"'); + } + } + let contactId; const updateUser = { $set: { diff --git a/apps/meteor/app/livechat/server/lib/Livechat.js b/apps/meteor/app/livechat/server/lib/Livechat.js index 3c387b73f532..d71d12a3e606 100644 --- a/apps/meteor/app/livechat/server/lib/Livechat.js +++ b/apps/meteor/app/livechat/server/lib/Livechat.js @@ -40,7 +40,7 @@ import { normalizeTransferredByData, parseAgentCustomFields, updateDepartmentAge import { Apps, AppEvents } from '../../../apps/server'; import { businessHourManager } from '../business-hour'; import notifications from '../../../notifications/server/lib/Notifications'; -import { Users as UsersRaw } from '../../../models/server/raw'; +import { Users as UsersRaw, LivechatVisitors as LivechatVisitorsRaw } from '../../../models/server/raw'; import { addUserRoles } from '../../../../server/lib/roles/addUserRoles'; import { removeUserFromRoles } from '../../../../server/lib/roles/removeUserFromRoles'; @@ -925,6 +925,7 @@ export const Livechat = { Users.removeLivechatData(_id); this.setUserStatusLivechat(_id, 'not-available'); LivechatDepartmentAgents.removeByAgentId(_id); + Promise.await(LivechatVisitorsRaw.removeContactManagerByUsername(username)); return true; } diff --git a/apps/meteor/app/models/server/models/LivechatVisitors.js b/apps/meteor/app/models/server/models/LivechatVisitors.js index 01642e36e84d..ffffe1095fcd 100644 --- a/apps/meteor/app/models/server/models/LivechatVisitors.js +++ b/apps/meteor/app/models/server/models/LivechatVisitors.js @@ -248,20 +248,6 @@ export class LivechatVisitors extends Base { const query = { _id }; return this.remove(query); } - - removeContactManagerByUsername(manager) { - const query = { - contactManager: { - username: manager, - }, - }; - const update = { - $unset: { - contactManager: 1, - }, - }; - return this.update(query, update); - } } export default new LivechatVisitors(); diff --git a/apps/meteor/app/models/server/raw/LivechatVisitors.ts b/apps/meteor/app/models/server/raw/LivechatVisitors.ts index 8cb7ebbfb75f..222b44918617 100644 --- a/apps/meteor/app/models/server/raw/LivechatVisitors.ts +++ b/apps/meteor/app/models/server/raw/LivechatVisitors.ts @@ -1,5 +1,5 @@ import { escapeRegExp } from '@rocket.chat/string-helpers'; -import { AggregationCursor, Cursor, FilterQuery, FindOneOptions, WithoutProjection } from 'mongodb'; +import { AggregationCursor, Cursor, FilterQuery, FindOneOptions, UpdateWriteOpResult, WithoutProjection } from 'mongodb'; import type { ILivechatVisitor } from '@rocket.chat/core-typings'; import { BaseRaw } from './BaseRaw'; @@ -105,4 +105,19 @@ export class LivechatVisitorsRaw extends BaseRaw { return this.find(query, options); } + + removeContactManagerByUsername(manager: string): Promise { + return this.updateMany( + { + contactManager: { + username: manager, + }, + }, + { + $unset: { + contactManager: true, + }, + }, + ); + } } diff --git a/apps/meteor/ee/client/omnichannel/additionalForms/ContactManager.js b/apps/meteor/ee/client/omnichannel/additionalForms/ContactManager.js index db8b1d9fe03a..4eaefaf29beb 100644 --- a/apps/meteor/ee/client/omnichannel/additionalForms/ContactManager.js +++ b/apps/meteor/ee/client/omnichannel/additionalForms/ContactManager.js @@ -1,17 +1,39 @@ import { Field } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; -import React from 'react'; +import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; +import React, { useEffect, useState, useCallback } from 'react'; -import UserAutoComplete from '../../../../client/components/UserAutoComplete'; +import AutoCompleteAgent from '../../../../client/components/AutoCompleteAgent'; -export const ContactManager = ({ value, handler }) => { +export const ContactManager = ({ value: username, handler }) => { const t = useTranslation(); + const [userId, setUserId] = useState(); + + const getUserData = useEndpoint('GET', 'users.info'); + + const fetchUserId = async () => { + const { user } = await getUserData({ username }); + user._id && setUserId(user._id); + }; + + const handleAgent = useCallback( + async (e) => { + setUserId(e); + const { user } = await getUserData({ userId: e }); + handler(user.username); + }, + [handler, setUserId, getUserData], + ); + + useEffect(() => { + fetchUserId(); + }); + return ( {t('Contact_Manager')} - + ); diff --git a/packages/core-typings/src/ILivechatVisitor.ts b/packages/core-typings/src/ILivechatVisitor.ts index 3145854c3868..d7e2772721e6 100644 --- a/packages/core-typings/src/ILivechatVisitor.ts +++ b/packages/core-typings/src/ILivechatVisitor.ts @@ -32,6 +32,9 @@ export interface ILivechatVisitor extends IRocketChatRecord { ip?: string; host?: string; visitorEmails?: IVisitorEmail[]; + contactManager?: { + username: string; + }; } export interface ILivechatVisitorDTO { From 5a74a5c164ce54e8e0d3a52ef7c48d395ae88383 Mon Sep 17 00:00:00 2001 From: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Date: Thu, 9 Jun 2022 20:07:39 +0530 Subject: [PATCH 19/59] [FIX] Voip endpoint permissions (#25783) ## Proposed changes (including videos or screenshots) ## Issue(s) Earlier we didn't check for any permissions while creating or closing VoIP room. This new PR will enforce those permission checks ## Steps to test or reproduce ## Further comments --- apps/meteor/app/api/server/v1/voip/rooms.ts | 8 ++++++-- apps/meteor/tests/end-to-end/api/02-channels.js | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/meteor/app/api/server/v1/voip/rooms.ts b/apps/meteor/app/api/server/v1/voip/rooms.ts index f04feaf0e084..79e77c607972 100644 --- a/apps/meteor/app/api/server/v1/voip/rooms.ts +++ b/apps/meteor/app/api/server/v1/voip/rooms.ts @@ -82,7 +82,11 @@ const parseAndValidate = (property: string, date?: string): DateParam => { API.v1.addRoute( 'voip/room', - { authRequired: false, rateLimiterOptions: { numRequestsAllowed: 5, intervalTimeInMS: 60000 } }, + { + authRequired: true, + rateLimiterOptions: { numRequestsAllowed: 5, intervalTimeInMS: 60000 }, + permissionsRequired: ['inbound-voip-calls'], + }, { async get() { const defaultCheckParams = { @@ -212,7 +216,7 @@ API.v1.addRoute( */ API.v1.addRoute( 'voip/room.close', - { authRequired: true }, + { authRequired: true, permissionsRequired: ['inbound-voip-calls'] }, { async post() { check(this.bodyParams, { diff --git a/apps/meteor/tests/end-to-end/api/02-channels.js b/apps/meteor/tests/end-to-end/api/02-channels.js index 80e7435c8e8f..5469295f68bd 100644 --- a/apps/meteor/tests/end-to-end/api/02-channels.js +++ b/apps/meteor/tests/end-to-end/api/02-channels.js @@ -305,11 +305,13 @@ describe('[Channels]', function () { before(() => updateSetting('VoIP_Enabled', true)); const createVoipRoom = async () => { const testUser = await createUser({ roles: ['user', 'livechat-agent'] }); + const testUserCredentials = await login(testUser.username, password); const visitor = await createVisitor(); const roomResponse = await createRoom({ token: visitor.token, type: 'v', agentId: testUser._id, + credentials: testUserCredentials, }); return roomResponse.body.room; }; From 92a25795180eb53638f1471671dc9c2e8dcdeabc Mon Sep 17 00:00:00 2001 From: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Date: Thu, 9 Jun 2022 21:08:39 +0530 Subject: [PATCH 20/59] Chore: Remove snap files from Houston config (#25819) ## Proposed changes (including videos or screenshots) ## Issue(s) Since these snap files were recently removed [here](https://github.com/RocketChat/Rocket.Chat/pull/25790), we'd need to update out release CLI accordingly ## Steps to test or reproduce ## Further comments --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index 866f9e6e8f03..36f1c2c2d07c 100644 --- a/package.json +++ b/package.json @@ -47,8 +47,6 @@ "updateFiles": [ "package.json", "apps/meteor/package.json", - "apps/meteor/.snapcraft/snap/snapcraft.yaml", - "apps/meteor/.snapcraft/resources/prepareRocketChat", "apps/meteor/.docker/Dockerfile.rhel", "apps/meteor/app/utils/rocketchat.info" ] From 9f2c03c2c2006a06a48a3c7d07ced28e00d95774 Mon Sep 17 00:00:00 2001 From: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Date: Thu, 9 Jun 2022 21:12:42 +0530 Subject: [PATCH 21/59] [FIX] Access issue on chat.getThreadsList (#25750) ## Proposed changes (including videos or screenshots) ## Issue(s) ## Steps to test or reproduce ## Further comments Clickup task: https://app.clickup.com/t/201cbrc --- apps/meteor/app/api/server/v1/chat.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/meteor/app/api/server/v1/chat.js b/apps/meteor/app/api/server/v1/chat.js index 76629a85349b..424568cf2ced 100644 --- a/apps/meteor/app/api/server/v1/chat.js +++ b/apps/meteor/app/api/server/v1/chat.js @@ -524,12 +524,13 @@ API.v1.addRoute( { get() { const { rid, type, text } = this.queryParams; + check(rid, String); + check(type, Match.Maybe(String)); + check(text, Match.Maybe(String)); + const { offset, count } = this.getPaginationItems(); const { sort, fields, query } = this.parseJsonQuery(); - if (!rid) { - throw new Meteor.Error('The required "rid" query param is missing.'); - } if (!settings.get('Threads_enabled')) { throw new Meteor.Error('error-not-allowed', 'Threads Disabled'); } @@ -547,7 +548,7 @@ API.v1.addRoute( msg: new RegExp(escapeRegExp(text), 'i'), }; - const threadQuery = { ...query, ...typeThread, rid, tcount: { $exists: true } }; + const threadQuery = { ...query, ...typeThread, rid: room._id, tcount: { $exists: true } }; const cursor = Messages.find(threadQuery, { sort: sort || { tlm: -1 }, skip: offset, From a741aecd35e84c1d80598589b0f0a0ce58bd03ce Mon Sep 17 00:00:00 2001 From: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Date: Thu, 9 Jun 2022 23:34:59 +0530 Subject: [PATCH 22/59] Chore: Move voip's Wrap-up and On-hold functionality to EE (Backend) (#25160) ## Proposed changes (including videos or screenshots) ## Issue(s) ## Steps to test or reproduce ## Further comments --- apps/meteor/app/api/server/v1/voip/rooms.ts | 13 +-- .../providers/CallProvider/CallProvider.tsx | 10 +- .../ee/app/voip-enterprise/server/index.ts | 1 + .../server/lib/calculateOnHoldTimeForRoom.ts | 37 ++++++ .../server/services/voipService.ts | 41 +++++++ apps/meteor/ee/server/index.ts | 1 + .../sdk/types/IOmnichannelVoipService.ts | 8 +- .../omnichannel-voip/internalTypes.ts | 4 + .../services/omnichannel-voip/service.ts | 108 ++++++++---------- packages/rest-typings/src/v1/voip.ts | 26 +++-- 10 files changed, 161 insertions(+), 88 deletions(-) create mode 100644 apps/meteor/ee/app/voip-enterprise/server/index.ts create mode 100644 apps/meteor/ee/app/voip-enterprise/server/lib/calculateOnHoldTimeForRoom.ts create mode 100644 apps/meteor/ee/app/voip-enterprise/server/services/voipService.ts diff --git a/apps/meteor/app/api/server/v1/voip/rooms.ts b/apps/meteor/app/api/server/v1/voip/rooms.ts index 79e77c607972..ee3a37fc1347 100644 --- a/apps/meteor/app/api/server/v1/voip/rooms.ts +++ b/apps/meteor/app/api/server/v1/voip/rooms.ts @@ -1,6 +1,7 @@ import { Match, check } from 'meteor/check'; import { Random } from 'meteor/random'; import type { ILivechatAgent } from '@rocket.chat/core-typings'; +import { isVoipRoomCloseProps } from '@rocket.chat/rest-typings/dist/v1/voip'; import { API } from '../../api'; import { VoipRoom, LivechatVisitors, Users } from '../../../../models/server/raw'; @@ -216,16 +217,10 @@ API.v1.addRoute( */ API.v1.addRoute( 'voip/room.close', - { authRequired: true, permissionsRequired: ['inbound-voip-calls'] }, + { authRequired: true, validateParams: isVoipRoomCloseProps, permissionsRequired: ['inbound-voip-calls'] }, { async post() { - check(this.bodyParams, { - rid: String, - token: String, - comment: Match.Maybe(String), - tags: Match.Maybe([String]), - }); - const { rid, token, comment, tags } = this.bodyParams; + const { rid, token, options } = this.bodyParams; const visitor = await LivechatVisitors.getVisitorByToken(token, {}); if (!visitor) { @@ -238,7 +233,7 @@ API.v1.addRoute( if (!room.open) { return API.v1.failure('room-closed'); } - const closeResult = await LivechatVoip.closeRoom(visitor, room, this.user, comment, tags); + const closeResult = await LivechatVoip.closeRoom(visitor, room, this.user, 'voip-call-wrapup', options); if (!closeResult) { return API.v1.failure(); } diff --git a/apps/meteor/client/providers/CallProvider/CallProvider.tsx b/apps/meteor/client/providers/CallProvider/CallProvider.tsx index a597897a8fb8..fc6c9120b807 100644 --- a/apps/meteor/client/providers/CallProvider/CallProvider.tsx +++ b/apps/meteor/client/providers/CallProvider/CallProvider.tsx @@ -319,14 +319,8 @@ export const CallProvider: FC = ({ children }) => { } return ''; }, - closeRoom: async (data?: { comment: string; tags: string[] }): Promise => { - roomInfo && - (await voipCloseRoomEndpoint({ - rid: roomInfo.rid, - token: roomInfo.v.token || '', - comment: data?.comment || '', - tags: data?.tags, - })); + closeRoom: async ({ comment, tags }: { comment?: string; tags?: string[] }): Promise => { + roomInfo && (await voipCloseRoomEndpoint({ rid: roomInfo.rid, token: roomInfo.v.token || '', options: { comment, tags } })); homeRoute.push({}); const queueAggregator = voipClient.getAggregator(); if (queueAggregator) { diff --git a/apps/meteor/ee/app/voip-enterprise/server/index.ts b/apps/meteor/ee/app/voip-enterprise/server/index.ts new file mode 100644 index 000000000000..7317f94f4fc8 --- /dev/null +++ b/apps/meteor/ee/app/voip-enterprise/server/index.ts @@ -0,0 +1 @@ +import './services/voipService'; diff --git a/apps/meteor/ee/app/voip-enterprise/server/lib/calculateOnHoldTimeForRoom.ts b/apps/meteor/ee/app/voip-enterprise/server/lib/calculateOnHoldTimeForRoom.ts new file mode 100644 index 000000000000..3a0b20328a3f --- /dev/null +++ b/apps/meteor/ee/app/voip-enterprise/server/lib/calculateOnHoldTimeForRoom.ts @@ -0,0 +1,37 @@ +import { IVoipRoom } from '@rocket.chat/core-typings'; + +import { PbxEvent } from '../../../../../app/models/server/raw'; + +export const calculateOnHoldTimeForRoom = async (room: IVoipRoom, closedAt: Date): Promise => { + if (!room.callUniqueId) { + return 0; + } + + const events = await PbxEvent.findByEvents(room.callUniqueId, ['Hold', 'Unhold']).toArray(); + if (!events.length) { + // if there's no events, that means no hold time + return 0; + } + + if (events.length === 1 && events[0].event === 'Unhold') { + // if the only event is an unhold event, something bad happened + return 0; + } + + if (events.length === 1 && events[0].event === 'Hold') { + // if the only event is a hold event, the call was ended while on hold + // hold time = room.closedAt - event.ts + return closedAt.getTime() - events[0].ts.getTime(); + } + + let currentOnHoldTime = 0; + + for (let i = 0; i < events.length; i += 2) { + const onHold = events[i].ts; + const unHold = events[i + 1]?.ts || closedAt; + + currentOnHoldTime += unHold.getTime() - onHold.getTime(); + } + + return currentOnHoldTime; +}; diff --git a/apps/meteor/ee/app/voip-enterprise/server/services/voipService.ts b/apps/meteor/ee/app/voip-enterprise/server/services/voipService.ts new file mode 100644 index 000000000000..1e65a0cb9420 --- /dev/null +++ b/apps/meteor/ee/app/voip-enterprise/server/services/voipService.ts @@ -0,0 +1,41 @@ +import { ILivechatAgent, ILivechatVisitor, IRoomClosingInfo, IUser, IVoipRoom } from '@rocket.chat/core-typings'; + +import { IOmniRoomClosingMessage } from '../../../../../server/services/omnichannel-voip/internalTypes'; +import { OmnichannelVoipService } from '../../../../../server/services/omnichannel-voip/service'; +import { overwriteClassOnLicense } from '../../../license/server'; +import { calculateOnHoldTimeForRoom } from '../lib/calculateOnHoldTimeForRoom'; + +overwriteClassOnLicense('livechat-enterprise', OmnichannelVoipService, { + getRoomClosingData( + _originalFn: ( + closer: ILivechatVisitor | ILivechatAgent, + room: IVoipRoom, + user: IUser, + sysMessageId?: 'voip-call-wrapup' | 'voip-call-ended-unexpectedly', + options?: { comment?: string | null; tags?: string[] | null }, + ) => Promise, + closeInfo: IRoomClosingInfo, + closeSystemMsgData: IOmniRoomClosingMessage, + room: IVoipRoom, + sysMessageId: 'voip-call-wrapup' | 'voip-call-ended-unexpectedly', + options?: { comment?: string; tags?: string[] }, + ): { closeInfo: IRoomClosingInfo; closeSystemMsgData: IOmniRoomClosingMessage } { + const { comment, tags } = options || {}; + if (comment) { + closeSystemMsgData.msg = comment; + } + if (tags?.length) { + closeInfo.tags = tags; + } + + if (sysMessageId === 'voip-call-wrapup' && !comment) { + closeSystemMsgData.t = 'voip-call-ended'; + } + + const now = new Date(); + const callTotalHoldTime = Promise.await(calculateOnHoldTimeForRoom(room, now)); + closeInfo.callTotalHoldTime = callTotalHoldTime; + + return { closeInfo, closeSystemMsgData }; + }, +}); diff --git a/apps/meteor/ee/server/index.ts b/apps/meteor/ee/server/index.ts index 6933a3eb9f27..01c0c7c06bfa 100644 --- a/apps/meteor/ee/server/index.ts +++ b/apps/meteor/ee/server/index.ts @@ -7,6 +7,7 @@ import '../app/auditing/server/index'; import '../app/authorization/server/index'; import '../app/canned-responses/server/index'; import '../app/livechat-enterprise/server/index'; +import '../app/voip-enterprise/server/index'; import '../app/settings/server/index'; import '../app/teams-mention/server/index'; import './api'; diff --git a/apps/meteor/server/sdk/types/IOmnichannelVoipService.ts b/apps/meteor/server/sdk/types/IOmnichannelVoipService.ts index ab28d4b8ac15..3bfd3b95db96 100644 --- a/apps/meteor/server/sdk/types/IOmnichannelVoipService.ts +++ b/apps/meteor/server/sdk/types/IOmnichannelVoipService.ts @@ -24,7 +24,13 @@ export interface IOmnichannelVoipService { options: FindOneOptions, ): Promise; findRoom(token: string, rid: string): Promise; - closeRoom(closer: ILivechatVisitor | ILivechatAgent, room: IVoipRoom, user: IUser, comment?: string, tags?: string[]): Promise; + closeRoom( + closer: ILivechatVisitor | ILivechatAgent, + room: IVoipRoom, + user: IUser, + sysMessageId?: 'voip-call-wrapup' | 'voip-call-ended-unexpectedly', + options?: { comment?: string | null; tags?: string[] | null }, + ): Promise; handleEvent( event: VoipClientEvents, room: IRoom, diff --git a/apps/meteor/server/services/omnichannel-voip/internalTypes.ts b/apps/meteor/server/services/omnichannel-voip/internalTypes.ts index 008020f2cce2..e4fadd52c208 100644 --- a/apps/meteor/server/services/omnichannel-voip/internalTypes.ts +++ b/apps/meteor/server/services/omnichannel-voip/internalTypes.ts @@ -1,3 +1,5 @@ +import { IMessage } from '@rocket.chat/core-typings'; + export type FindVoipRoomsParams = { agents?: string[]; open?: boolean; @@ -13,3 +15,5 @@ export type FindVoipRoomsParams = { offset?: number; }; }; + +export type IOmniRoomClosingMessage = Pick & Partial>; diff --git a/apps/meteor/server/services/omnichannel-voip/service.ts b/apps/meteor/server/services/omnichannel-voip/service.ts index e45480bf1030..8f79f22cb48a 100644 --- a/apps/meteor/server/services/omnichannel-voip/service.ts +++ b/apps/meteor/server/services/omnichannel-voip/service.ts @@ -27,7 +27,7 @@ import { UsersRaw } from '../../../app/models/server/raw/Users'; import { VoipRoomsRaw } from '../../../app/models/server/raw/VoipRooms'; import { PbxEventsRaw } from '../../../app/models/server/raw/PbxEvents'; import { sendMessage } from '../../../app/lib/server/functions/sendMessage'; -import { FindVoipRoomsParams } from './internalTypes'; +import { FindVoipRoomsParams, IOmniRoomClosingMessage } from './internalTypes'; import { api } from '../../sdk/api'; export class OmnichannelVoipService extends ServiceClassInternal implements IOmnichannelVoipService { @@ -99,7 +99,7 @@ export class OmnichannelVoipService extends ServiceClassInternal implements IOmn // and multiple rooms are left opened for one single agent. Best case this will iterate once for await (const room of openRooms) { await this.handleEvent(VoipClientEvents['VOIP-CALL-ENDED'], room, agent, 'Agent disconnected abruptly'); - await this.closeRoom(agent, room, agent, 'Agent disconnected abruptly', undefined, 'voip-call-ended-unexpectedly'); + await this.closeRoom(agent, room, agent, 'voip-call-ended-unexpectedly', { comment: 'Agent disconnected abruptly' }); } } @@ -266,87 +266,75 @@ export class OmnichannelVoipService extends ServiceClassInternal implements IOmn return this.voipRoom.findOneByIdAndVisitorToken(rid, token, { projection }); } - private async calculateOnHoldTimeForRoom(room: IVoipRoom, closedAt: Date): Promise { - if (!room || !room.callUniqueId) { - return 0; - } - - const events = await this.pbxEvents.findByEvents(room.callUniqueId, ['Hold', 'Unhold']).toArray(); - if (!events.length) { - // if there's no events, that means no hold time - return 0; - } - - if (events.length === 1 && events[0].event === 'Unhold') { - // if the only event is an unhold event, something bad happened - return 0; - } - - if (events.length === 1 && events[0].event === 'Hold') { - // if the only event is a hold event, the call was ended while on hold - // hold time = room.closedAt - event.ts - return closedAt.getTime() - events[0].ts.getTime(); - } - - let currentOnHoldTime = 0; - - for (let i = 0; i < events.length; i += 2) { - const onHold = events[i].ts; - const unHold = events[i + 1]?.ts || closedAt; - - currentOnHoldTime += unHold.getTime() - onHold.getTime(); - } - - return currentOnHoldTime; - } - - // Comment can be used to store wrapup call data async closeRoom( closerParam: ILivechatVisitor | ILivechatAgent, room: IVoipRoom, user: IUser, - comment?: string, - tags?: string[], sysMessageId: 'voip-call-wrapup' | 'voip-call-ended-unexpectedly' = 'voip-call-wrapup', + options?: { comment?: string; tags?: string[] }, ): Promise { this.logger.debug(`Attempting to close room ${room._id}`); if (!room || room.t !== 'v' || !room.open) { return false; } + let { closeInfo, closeSystemMsgData } = await this.getBaseRoomClosingData(closerParam, room, sysMessageId, options); + const finalClosingData = this.getRoomClosingData(closeInfo, closeSystemMsgData, room, sysMessageId, options); + closeInfo = finalClosingData.closeInfo; + closeSystemMsgData = finalClosingData.closeSystemMsgData; + + await sendMessage(user, closeSystemMsgData, room); + + // There's a race condition between receiving the call and receiving the event + // Sometimes it happens before the connection on client, sometimes it happens after + // For now, this data will be appended as a metric on room closing + await this.setCallWaitingQueueTimers(room); + + this.logger.debug(`Room ${room._id} closed and timers set`); + this.logger.debug(`Room ${room._id} was closed at ${closeInfo.closedAt} (duration ${closeInfo.callDuration})`); + this.voipRoom.closeByRoomId(room._id, closeInfo); + + return true; + } + + getRoomClosingData( + closeInfo: IRoomClosingInfo, + closeSystemMsgData: IOmniRoomClosingMessage, + _room: IVoipRoom, + _sysMessageId: 'voip-call-wrapup' | 'voip-call-ended-unexpectedly', + _options?: { comment?: string; tags?: string[] }, + ): { closeInfo: IRoomClosingInfo; closeSystemMsgData: IOmniRoomClosingMessage } { + return { closeInfo, closeSystemMsgData }; + } + + async getBaseRoomClosingData( + closerParam: ILivechatVisitor | ILivechatAgent, + room: IVoipRoom, + sysMessageId: 'voip-call-wrapup' | 'voip-call-ended-unexpectedly', + _options?: { comment?: string; tags?: string[] }, + ): Promise<{ closeInfo: IRoomClosingInfo; closeSystemMsgData: IOmniRoomClosingMessage }> { const now = new Date(); - const { _id: rid } = room; const closer = isILivechatVisitor(closerParam) ? 'visitor' : 'user'; - const callTotalHoldTime = await this.calculateOnHoldTimeForRoom(room, now); + const closeData: IRoomClosingInfo = { closedAt: now, callDuration: now.getTime() - room.ts.getTime(), closer, - callTotalHoldTime, - tags, - }; - this.logger.debug(`Closing room ${room._id} by ${closer} ${closerParam._id}`); - closeData.closedBy = { - _id: closerParam._id, - username: closerParam.username, + closedBy: { + _id: closerParam._id, + username: closerParam.username, + }, }; - const message = { + const message: IOmniRoomClosingMessage = { t: sysMessageId, - msg: comment, groupable: false, }; - await sendMessage(user, message, room); - // There's a race condition between receiving the call and receiving the event - // Sometimes it happens before the connection on client, sometimes it happens after - // For now, this data will be appended as a metric on room closing - await this.setCallWaitingQueueTimers(room); - - this.logger.debug(`Room ${room._id} closed and timers set`); - this.logger.debug(`Room ${room._id} was closed at ${closeData.closedAt} (duration ${closeData.callDuration})`); - this.voipRoom.closeByRoomId(rid, closeData); - return true; + return { + closeInfo: closeData, + closeSystemMsgData: message, + }; } private getQueuesForExt( diff --git a/packages/rest-typings/src/v1/voip.ts b/packages/rest-typings/src/v1/voip.ts index 516b1131e9ac..9ec925154779 100644 --- a/packages/rest-typings/src/v1/voip.ts +++ b/packages/rest-typings/src/v1/voip.ts @@ -460,7 +460,7 @@ const VoipRoomsSchema: JSONSchemaType = { export const isVoipRoomsProps = ajv.compile(VoipRoomsSchema); -type VoipRoomClose = { rid: string; token: string; comment: string; tags?: string[] }; +type VoipRoomClose = { rid: string; token: string; options: { comment?: string; tags?: string[] } }; const VoipRoomCloseSchema: JSONSchemaType = { type: 'object', @@ -471,18 +471,24 @@ const VoipRoomCloseSchema: JSONSchemaType = { token: { type: 'string', }, - comment: { - type: 'string', - }, - tags: { - type: 'array', - items: { - type: 'string', + options: { + type: 'object', + properties: { + comment: { + type: 'string', + nullable: true, + }, + tags: { + type: 'array', + items: { + type: 'string', + }, + nullable: true, + }, }, - nullable: true, }, }, - required: ['rid', 'token', 'comment'], + required: ['rid', 'token'], additionalProperties: false, }; From 83851a0ad551ca0ca34cd91703a62f253b0e863a Mon Sep 17 00:00:00 2001 From: Tiago Evangelista Pinto Date: Thu, 9 Jun 2022 16:39:44 -0300 Subject: [PATCH 23/59] [FIX] AgentsPage pagination (#25820) ## Proposed changes (including videos or screenshots) ## Issue(s) ## Steps to test or reproduce ## Further comments --- apps/meteor/client/views/omnichannel/agents/AgentsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/client/views/omnichannel/agents/AgentsPage.tsx b/apps/meteor/client/views/omnichannel/agents/AgentsPage.tsx index 72b53541f8f6..287214342729 100644 --- a/apps/meteor/client/views/omnichannel/agents/AgentsPage.tsx +++ b/apps/meteor/client/views/omnichannel/agents/AgentsPage.tsx @@ -97,7 +97,7 @@ const AgentsPage = (): ReactElement => { Date: Thu, 9 Jun 2022 19:49:26 -0300 Subject: [PATCH 24/59] [FIX] `You and @yourUsername reacted with`title on reactions (#25733) ## Proposed changes (including videos or screenshots) ## Issue(s) ## Steps to test or reproduce ## Further comments --- .../components/MessageReaction.tsx | 20 +++---------------- .../providers/MessageListProvider.tsx | 4 ++-- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/apps/meteor/client/views/room/MessageList/components/MessageReaction.tsx b/apps/meteor/client/views/room/MessageList/components/MessageReaction.tsx index d4c0bf148946..9bf79dfd973a 100644 --- a/apps/meteor/client/views/room/MessageList/components/MessageReaction.tsx +++ b/apps/meteor/client/views/room/MessageList/components/MessageReaction.tsx @@ -4,21 +4,7 @@ import React, { FC, useRef } from 'react'; import { getEmojiClassNameAndDataTitle } from '../../../../lib/utils/renderEmoji'; -type TranslationRepliesKey = - | 'You_have_reacted' - | 'Users_and_more_reacted_with' - | 'You_and_more_Reacted_with' - | 'You_users_and_more_Reacted_with' - | 'Users_reacted_with' - | 'You_and_users_Reacted_with'; - -// "You": "You", -// "You_user_have_reacted": "You have reacted", -// "Users_and_more_reacted_with": "__users__ and __count__ more have reacted with __emoji__", -// "You_and_more_Reacted_with": "You, __users__ and __count__ more have reacted with __emoji__", -// "You_and_Reacted_with": "You and __count__ more have reacted with __emoji__", - -const getTranslationKey = (users: string[], mine: boolean): TranslationRepliesKey => { +const getTranslationKey = (users: string[], mine: boolean): TranslationKey => { if (users.length === 0) { if (mine) { return 'You_have_reacted'; @@ -27,7 +13,7 @@ const getTranslationKey = (users: string[], mine: boolean): TranslationRepliesKe if (users.length > 15) { if (mine) { - return 'You_and_more_Reacted_with'; + return 'You_users_and_more_Reacted_with'; } return 'Users_and_more_reacted_with'; } @@ -68,7 +54,7 @@ export const MessageReaction: FC<{ ref.current && openTooltip( <> - {t(key as TranslationKey, { + {t(key, { counter: names.length, users: names.join(', '), emoji: name, diff --git a/apps/meteor/client/views/room/MessageList/providers/MessageListProvider.tsx b/apps/meteor/client/views/room/MessageList/providers/MessageListProvider.tsx index 999e295ab1cc..88d55ccebb23 100644 --- a/apps/meteor/client/views/room/MessageList/providers/MessageListProvider.tsx +++ b/apps/meteor/client/views/room/MessageList/providers/MessageListProvider.tsx @@ -48,7 +48,7 @@ export const MessageListProvider: FC<{ return []; } if (!isMessageReactionsNormalized(message)) { - return (message.reactions && message.reactions[reaction]?.usernames.map((username) => `@${username}`)) || []; + return message.reactions?.[reaction]?.usernames.filter((user) => user !== username).map((username) => `@${username}`) || []; } if (!username) { return message.reactions[reaction].names; @@ -64,7 +64,7 @@ export const MessageListProvider: FC<{ useUserHasReacted: username ? (message) => (reaction): boolean => - Boolean(message.reactions && message.reactions[reaction].usernames.includes(username)) + Boolean(message.reactions?.[reaction].usernames.includes(username)) : () => (): boolean => false, useShowFollowing: uid ? ({ message }): boolean => Boolean(message.replies && message.replies.indexOf(uid) > -1) From e38b5bc1d9a0ff7cf827adb79e8baf9718dc352c Mon Sep 17 00:00:00 2001 From: Debdut Chakraborty Date: Fri, 10 Jun 2022 09:10:51 +0530 Subject: [PATCH 25/59] Chore: Remove compose from main repo (#23426) Closes #23584 Closes #23416 Closes #23402 Closes #24636 Closes #24160 Closes #23479 Closes #23341 --- apps/meteor/docker-compose.yml | 81 ---------------------------------- 1 file changed, 81 deletions(-) delete mode 100644 apps/meteor/docker-compose.yml diff --git a/apps/meteor/docker-compose.yml b/apps/meteor/docker-compose.yml deleted file mode 100644 index 76a663537960..000000000000 --- a/apps/meteor/docker-compose.yml +++ /dev/null @@ -1,81 +0,0 @@ -version: '2' - -services: - rocketchat: - image: registry.rocket.chat/rocketchat/rocket.chat:latest - command: > - bash -c - "for i in `seq 1 30`; do - node main.js && - s=$$? && break || s=$$?; - echo \"Tried $$i times. Waiting 5 secs...\"; - sleep 5; - done; (exit $$s)" - restart: unless-stopped - volumes: - - ./uploads:/app/uploads - environment: - - PORT=3000 - - ROOT_URL=http://localhost:3000 - - MONGO_URL=mongodb://mongo:27017/rocketchat - - MONGO_OPLOG_URL=mongodb://mongo:27017/local - - REG_TOKEN=${REG_TOKEN} -# - MAIL_URL=smtp://smtp.email -# - HTTP_PROXY=http://proxy.domain.com -# - HTTPS_PROXY=http://proxy.domain.com - depends_on: - - mongo - ports: - - 3000:3000 - labels: - - "traefik.backend=rocketchat" - - "traefik.frontend.rule=Host: your.domain.tld" - - mongo: - image: mongo:4.0 - restart: unless-stopped - volumes: - - ./data/db:/data/db - #- ./data/dump:/dump - command: mongod --smallfiles --oplogSize 128 --replSet rs0 --storageEngine=mmapv1 - labels: - - "traefik.enable=false" - - # this container's job is just run the command to initialize the replica set. - # it will run the command and remove himself (it will not stay running) - mongo-init-replica: - image: mongo:4.0 - command: > - bash -c - "for i in `seq 1 30`; do - mongo mongo/rocketchat --eval \" - rs.initiate({ - _id: 'rs0', - members: [ { _id: 0, host: 'localhost:27017' } ]})\" && - s=$$? && break || s=$$?; - echo \"Tried $$i times. Waiting 5 secs...\"; - sleep 5; - done; (exit $$s)" - depends_on: - - mongo - - #traefik: - # image: traefik:latest - # restart: unless-stopped - # command: > - # traefik - # --docker - # --acme=true - # --acme.domains='your.domain.tld' - # --acme.email='your@email.tld' - # --acme.entrypoint=https - # --acme.storagefile=acme.json - # --defaultentrypoints=http - # --defaultentrypoints=https - # --entryPoints='Name:http Address::80 Redirect.EntryPoint:https' - # --entryPoints='Name:https Address::443 TLS.Certificates:' - # ports: - # - 80:80 - # - 443:443 - # volumes: - # - /var/run/docker.sock:/var/run/docker.sock From a3580173e4949fc89065ce70e0ebb47d2c28d562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrique=20Guimar=C3=A3es=20Ribeiro?= Date: Fri, 10 Jun 2022 10:14:15 -0300 Subject: [PATCH 26/59] Regression: Fix apps wrong typing (#25824) --- apps/meteor/app/apps/client/orchestrator.ts | 47 ++++++++++++--------- packages/rest-typings/src/apps/index.ts | 23 ++++++---- 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/apps/meteor/app/apps/client/orchestrator.ts b/apps/meteor/app/apps/client/orchestrator.ts index dc37aef84e74..ea224409fbc4 100644 --- a/apps/meteor/app/apps/client/orchestrator.ts +++ b/apps/meteor/app/apps/client/orchestrator.ts @@ -132,30 +132,33 @@ class AppClientOrchestrator { public async getApps(): Promise { const result = await APIClient.get('/apps'); + if ('apps' in result) { - return result.apps; + // TODO: chapter day: multiple results are returned, but we only need one + return result.apps as App[]; } - throw new Error('Apps not found'); + throw new Error('Invalid response from API'); } public async getAppsFromMarketplace(): Promise { const result = await APIClient.get('/apps', { marketplace: 'true' }); - if ('apps' in result) { - const { apps: appsOverviews } = result; - return appsOverviews.map((app) => { - const { latest, price, pricingPlans, purchaseType, isEnterpriseOnly, modifiedAt } = app; - return { - ...latest, - price, - pricingPlans, - purchaseType, - isEnterpriseOnly, - modifiedAt, - }; - }); + if (!Array.isArray(result)) { + // TODO: chapter day: multiple results are returned, but we only need one + throw new Error('Invalid response from API'); } - throw new Error('Apps not found'); + + return (result as App[]).map((app: App) => { + const { latest, price, pricingPlans, purchaseType, isEnterpriseOnly, modifiedAt } = app; + return { + ...latest, + price, + pricingPlans, + purchaseType, + isEnterpriseOnly, + modifiedAt, + }; + }); } public async getAppsOnBundle(bundleId: string): Promise { @@ -257,12 +260,14 @@ class AppClientOrchestrator { throw new Error('Failed to build external url'); } - public async getCategories(): Promise[]> { + public async getCategories(): Promise> { const result = await APIClient.get('/apps', { categories: 'true' }); - if ('categories' in result) { - return result.categories; + + if (Array.isArray(result)) { + // TODO: chapter day: multiple results are returned, but we only need one + return result as Serialized[]; } - throw new Error('Categories not found'); + throw new Error('Failed to get categories'); } public getUIHost(): RealAppsEngineUIHost { @@ -274,7 +279,7 @@ export const Apps = new AppClientOrchestrator(); Meteor.startup(() => { CachedCollectionManager.onLogin(() => { - Meteor.call('/apps/is-enabled', (error: Error, isEnabled: boolean) => { + Meteor.call('apps/is-enabled', (error: Error, isEnabled: boolean) => { if (error) { Apps.handleError(error); return; diff --git a/packages/rest-typings/src/apps/index.ts b/packages/rest-typings/src/apps/index.ts index d7f6fd10c9b2..1a8c801bf790 100644 --- a/packages/rest-typings/src/apps/index.ts +++ b/packages/rest-typings/src/apps/index.ts @@ -73,22 +73,27 @@ export type AppsEndpoints = { }) | ((params: { purchaseType?: 'buy' | 'subscription'; - marketplace?: 'true' | 'false'; + marketplace?: 'false'; version?: string; appId?: string; details?: 'true' | 'false'; }) => { apps: App[]; }) + | ((params: { + purchaseType?: 'buy' | 'subscription'; + marketplace: 'true'; + version?: string; + appId?: string; + details?: 'true' | 'false'; + }) => App[]) | ((params: { categories: 'true' | 'false' }) => { - categories: { - createdDate: string; - description: string; - id: string; - modifiedDate: Date; - title: string; - }[]; - }); + createdDate: Date; + description: string; + id: string; + modifiedDate: Date; + title: string; + }[]); POST: (params: { appId: string; marketplace: boolean; version: string; permissionsGranted: IPermission[] }) => { app: App; From 5a287fff1df02c5136217aa4f7074c3564655275 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Fri, 10 Jun 2022 16:57:41 -0300 Subject: [PATCH 27/59] Chore: Add auto label and improve Kodiak configuration (#25829) ## Proposed changes (including videos or screenshots) ## Issue(s) ## Steps to test or reproduce ## Further comments --- .github/auto-label-action-config.json | 1 + .github/workflows/auto-label.yml | 11 +++++++++++ .kodiak.toml | 7 +++++-- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 .github/auto-label-action-config.json create mode 100644 .github/workflows/auto-label.yml diff --git a/.github/auto-label-action-config.json b/.github/auto-label-action-config.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/.github/auto-label-action-config.json @@ -0,0 +1 @@ +{} diff --git a/.github/workflows/auto-label.yml b/.github/workflows/auto-label.yml new file mode 100644 index 000000000000..2a453ccdb4d2 --- /dev/null +++ b/.github/workflows/auto-label.yml @@ -0,0 +1,11 @@ +name: 'Auto label QA' +on: + pull_request: + types: [opened, synchronize, labeled, unlabeled] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: ggazzo/gh-action-auto-label@beta-5 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.kodiak.toml b/.kodiak.toml index bbbfca713a5e..3865c8954586 100644 --- a/.kodiak.toml +++ b/.kodiak.toml @@ -1,10 +1,13 @@ # .kodiak.toml version = 1 - [merge] method = "squash" -automerge_label = ["stat: ready to merge", "QA tested", "automerge"] +automerge_label = ["stat: ready to merge", "automerge"] +block_on_neutral_required_check_runs = true +blocking_labels = ["stat: needs QA", "Invalid PR Title"] +prioritize_ready_to_merge = true + [merge.message] title = "pull_request_title" # default: "github_default" From e3d184bc4e6d6c7bd44fde4dfbf75f798a8ea52c Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Fri, 10 Jun 2022 17:41:55 -0300 Subject: [PATCH 28/59] Regression: Fix users.create call (#25834) ## Proposed changes (including videos or screenshots) ## Issue(s) ## Steps to test or reproduce ## Further comments --- apps/meteor/client/views/admin/users/AddUser.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/meteor/client/views/admin/users/AddUser.js b/apps/meteor/client/views/admin/users/AddUser.js index 5aa3eb2cf9c9..79e523cee7f8 100644 --- a/apps/meteor/client/views/admin/users/AddUser.js +++ b/apps/meteor/client/views/admin/users/AddUser.js @@ -77,8 +77,8 @@ export function AddUser({ roles, onReload, ...props }) { [router], ); - const saveAction = useEndpointAction('POST', 'users.create', values, t('User_created_successfully!')); - const eventStats = useEndpointAction('POST', 'statistics.telemetry', { + const saveAction = useEndpointAction('POST', '/v1/users.create', values, t('User_created_successfully!')); + const eventStats = useEndpointAction('POST', '/v1/statistics.telemetry', { params: [{ eventName: 'updateCounter', settingsId: 'Manual_Entry_User_Count' }], }); From e01c8eac92467fd1e4e6a21c19539e2950d4f8dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Jaeger=20Foresti?= <60678893+juliajforesti@users.noreply.github.com> Date: Fri, 10 Jun 2022 19:18:50 -0300 Subject: [PATCH 29/59] Chore: Convert MemoizedSetting, Setting, Section (#25572) ## Proposed changes (including videos or screenshots) ## Issue(s) ## Steps to test or reproduce ## Further comments Co-authored-by: Tasso Evangelista <2263066+tassoevan@users.noreply.github.com> --- .../client/components/Sidebar/Header.tsx | 4 +- .../contexts/TranslationContextMock.tsx | 4 +- .../views/admin/EditableSettingsContext.ts | 20 +++--- .../settings/EditableSettingsProvider.tsx | 12 ++-- .../client/views/admin/settings/GroupPage.tsx | 4 +- ...MemoizedSetting.js => MemoizedSetting.tsx} | 67 +++++++++++++------ .../settings/{Section.js => Section.tsx} | 54 +++++++++++---- .../settings/{Setting.js => Setting.tsx} | 53 +++++++++------ .../settings/inputs/BooleanSettingInput.tsx | 4 +- .../ui-contexts/src/TranslationContext.ts | 4 +- 10 files changed, 144 insertions(+), 82 deletions(-) rename apps/meteor/client/views/admin/settings/{MemoizedSetting.js => MemoizedSetting.tsx} (54%) rename apps/meteor/client/views/admin/settings/{Section.js => Section.tsx} (57%) rename apps/meteor/client/views/admin/settings/{Setting.js => Setting.tsx} (52%) diff --git a/apps/meteor/client/components/Sidebar/Header.tsx b/apps/meteor/client/components/Sidebar/Header.tsx index 2bc7e5cfb480..278f47401587 100644 --- a/apps/meteor/client/components/Sidebar/Header.tsx +++ b/apps/meteor/client/components/Sidebar/Header.tsx @@ -1,8 +1,8 @@ import { Box, ActionButton } from '@rocket.chat/fuselage'; -import React, { FC, ReactElement } from 'react'; +import React, { FC, ReactNode } from 'react'; type HeaderProps = { - title?: ReactElement | string; + title?: ReactNode; onClose?: () => void; }; diff --git a/apps/meteor/client/stories/contexts/TranslationContextMock.tsx b/apps/meteor/client/stories/contexts/TranslationContextMock.tsx index d4ffd1459154..059f3147dba6 100644 --- a/apps/meteor/client/stories/contexts/TranslationContextMock.tsx +++ b/apps/meteor/client/stories/contexts/TranslationContextMock.tsx @@ -1,4 +1,4 @@ -import { TranslationContext } from '@rocket.chat/ui-contexts'; +import { TranslationContext, TranslationKey } from '@rocket.chat/ui-contexts'; import i18next from 'i18next'; import React, { ContextType, ReactElement, ReactNode, useContext, useMemo } from 'react'; @@ -41,7 +41,7 @@ const TranslationContextMock = ({ children }: TranslationContextMockProps): Reac }); }; - translate.has = (key: string): boolean => !!key && i18next.exists(key); + translate.has = (key: string | undefined): key is TranslationKey => !!key && i18next.exists(key); return { ...parent, diff --git a/apps/meteor/client/views/admin/EditableSettingsContext.ts b/apps/meteor/client/views/admin/EditableSettingsContext.ts index af21e8d7f4d4..2c74b679fc9e 100644 --- a/apps/meteor/client/views/admin/EditableSettingsContext.ts +++ b/apps/meteor/client/views/admin/EditableSettingsContext.ts @@ -1,24 +1,24 @@ -import { ISettingBase, SectionName, SettingId, GroupId, TabId } from '@rocket.chat/core-typings'; +import { ISettingBase, SectionName, SettingId, GroupId, TabId, ISettingColor } from '@rocket.chat/core-typings'; import { SettingsContextQuery } from '@rocket.chat/ui-contexts'; import { createContext, useContext, useMemo } from 'react'; import { useSubscription, Subscription, Unsubscribe } from 'use-subscription'; -export interface IEditableSetting extends ISettingBase { +export type EditableSetting = (ISettingBase | ISettingColor) & { disabled: boolean; changed: boolean; invisible: boolean; -} +}; export type EditableSettingsContextQuery = SettingsContextQuery & { changed?: boolean; }; export type EditableSettingsContextValue = { - readonly queryEditableSetting: (_id: SettingId) => Subscription; - readonly queryEditableSettings: (query: EditableSettingsContextQuery) => Subscription; + readonly queryEditableSetting: (_id: SettingId) => Subscription; + readonly queryEditableSettings: (query: EditableSettingsContextQuery) => Subscription; readonly queryGroupSections: (_id: GroupId, tab?: TabId) => Subscription; readonly queryGroupTabs: (_id: GroupId) => Subscription; - readonly dispatch: (changes: Partial[]) => void; + readonly dispatch: (changes: Partial[]) => void; }; export const EditableSettingsContext = createContext({ @@ -27,7 +27,7 @@ export const EditableSettingsContext = createContext (): void => undefined, }), queryEditableSettings: () => ({ - getCurrentValue: (): IEditableSetting[] => [], + getCurrentValue: (): EditableSetting[] => [], subscribe: (): Unsubscribe => (): void => undefined, }), queryGroupSections: () => ({ @@ -41,14 +41,14 @@ export const EditableSettingsContext = createContext undefined, }); -export const useEditableSetting = (_id: SettingId): IEditableSetting | undefined => { +export const useEditableSetting = (_id: SettingId): EditableSetting | undefined => { const { queryEditableSetting } = useContext(EditableSettingsContext); const subscription = useMemo(() => queryEditableSetting(_id), [queryEditableSetting, _id]); return useSubscription(subscription); }; -export const useEditableSettings = (query?: EditableSettingsContextQuery): IEditableSetting[] => { +export const useEditableSettings = (query?: EditableSettingsContextQuery): EditableSetting[] => { const { queryEditableSettings } = useContext(EditableSettingsContext); const subscription = useMemo(() => queryEditableSettings(query ?? {}), [queryEditableSettings, query]); return useSubscription(subscription); @@ -68,5 +68,5 @@ export const useEditableSettingsGroupTabs = (_id: SettingId): TabId[] => { return useSubscription(subscription); }; -export const useEditableSettingsDispatch = (): ((changes: Partial[]) => void) => +export const useEditableSettingsDispatch = (): ((changes: Partial[]) => void) => useContext(EditableSettingsContext).dispatch; diff --git a/apps/meteor/client/views/admin/settings/EditableSettingsProvider.tsx b/apps/meteor/client/views/admin/settings/EditableSettingsProvider.tsx index 4c0d102baa9f..4a469f053218 100644 --- a/apps/meteor/client/views/admin/settings/EditableSettingsProvider.tsx +++ b/apps/meteor/client/views/admin/settings/EditableSettingsProvider.tsx @@ -7,7 +7,7 @@ import { FilterQuery } from 'mongodb'; import React, { useEffect, useMemo, FunctionComponent, useRef, MutableRefObject } from 'react'; import { createReactiveSubscriptionFactory } from '../../../providers/createReactiveSubscriptionFactory'; -import { EditableSettingsContext, IEditableSetting, EditableSettingsContextValue } from '../EditableSettingsContext'; +import { EditableSettingsContext, EditableSetting, EditableSettingsContextValue } from '../EditableSettingsContext'; const defaultQuery: SettingsContextQuery = {}; @@ -16,7 +16,7 @@ type EditableSettingsProviderProps = { }; const EditableSettingsProvider: FunctionComponent = ({ children, query = defaultQuery }) => { - const settingsCollectionRef = useRef>(null) as MutableRefObject>; + const settingsCollectionRef = useRef>(null) as MutableRefObject>; const persistedSettings = useSettings(query); const getSettingsCollection = useMutableCallback(() => { @@ -25,7 +25,7 @@ const EditableSettingsProvider: FunctionComponent } return settingsCollectionRef.current; - }) as () => Mongo.Collection; + }) as () => Mongo.Collection; useEffect(() => { const settingsCollection = getSettingsCollection(); @@ -39,7 +39,7 @@ const EditableSettingsProvider: FunctionComponent const queryEditableSetting = useMemo(() => { const validateSettingQueries = ( query: undefined | string | FilterQuery | FilterQuery[], - settingsCollection: Mongo.Collection, + settingsCollection: Mongo.Collection, ): boolean => { if (!query) { return true; @@ -49,7 +49,7 @@ const EditableSettingsProvider: FunctionComponent return queries.every((query) => settingsCollection.find(query).count() > 0); }; - return createReactiveSubscriptionFactory((_id: SettingId): IEditableSetting | undefined => { + return createReactiveSubscriptionFactory((_id: SettingId): EditableSetting | undefined => { const settingsCollection = getSettingsCollection(); const editableSetting = settingsCollection.findOne(_id); @@ -169,7 +169,7 @@ const EditableSettingsProvider: FunctionComponent [getSettingsCollection], ); - const dispatch = useMutableCallback((changes: Partial[]): void => { + const dispatch = useMutableCallback((changes: Partial[]): void => { for (const { _id, ...data } of changes) { if (!_id) { continue; diff --git a/apps/meteor/client/views/admin/settings/GroupPage.tsx b/apps/meteor/client/views/admin/settings/GroupPage.tsx index 19b16adee851..20510393b48b 100644 --- a/apps/meteor/client/views/admin/settings/GroupPage.tsx +++ b/apps/meteor/client/views/admin/settings/GroupPage.tsx @@ -14,7 +14,7 @@ import { import React, { useMemo, memo, FC, ReactNode, FormEvent, MouseEvent } from 'react'; import Page from '../../../components/Page'; -import { useEditableSettingsDispatch, useEditableSettings, IEditableSetting } from '../EditableSettingsContext'; +import { useEditableSettingsDispatch, useEditableSettings, EditableSetting } from '../EditableSettingsContext'; import GroupPageSkeleton from './GroupPageSkeleton'; type GroupPageProps = { @@ -129,7 +129,7 @@ const GroupPage: FC = ({ }; }) .filter(Boolean); - dispatchToEditing(settingsToDispatch as Partial[]); + dispatchToEditing(settingsToDispatch as Partial[]); }); const handleSubmit = (event: FormEvent): void => { diff --git a/apps/meteor/client/views/admin/settings/MemoizedSetting.js b/apps/meteor/client/views/admin/settings/MemoizedSetting.tsx similarity index 54% rename from apps/meteor/client/views/admin/settings/MemoizedSetting.js rename to apps/meteor/client/views/admin/settings/MemoizedSetting.tsx index f3b93d838e28..446958d390d2 100644 --- a/apps/meteor/client/views/admin/settings/MemoizedSetting.js +++ b/apps/meteor/client/views/admin/settings/MemoizedSetting.tsx @@ -1,5 +1,6 @@ +import { ISettingBase, SettingEditor, SettingValue } from '@rocket.chat/core-typings'; import { Callout, Field, Margins } from '@rocket.chat/fuselage'; -import React, { memo } from 'react'; +import React, { ElementType, memo, ReactElement, ReactNode } from 'react'; import ActionSettingInput from './inputs/ActionSettingInput'; import AssetSettingInput from './inputs/AssetSettingInput'; @@ -18,40 +19,62 @@ import SelectSettingInput from './inputs/SelectSettingInput'; import SelectTimezoneSettingInput from './inputs/SelectTimezoneSettingInput'; import StringSettingInput from './inputs/StringSettingInput'; +// @todo: the props are loosely typed because `Setting` needs to typecheck them. +const inputsByType: Record> = { + boolean: BooleanSettingInput, + string: StringSettingInput, + relativeUrl: RelativeUrlSettingInput, + password: PasswordSettingInput, + int: IntSettingInput, + select: SelectSettingInput, + multiSelect: MultiSelectSettingInput, + language: LanguageSettingInput, + color: ColorSettingInput, + font: FontSettingInput, + code: CodeSettingInput, + action: ActionSettingInput, + asset: AssetSettingInput, + roomPick: RoomPickSettingInput, + timezone: SelectTimezoneSettingInput, + date: GenericSettingInput, // @todo: implement + group: GenericSettingInput, // @todo: implement +}; + +type MemoizedSettingProps = { + _id?: string; + type: ISettingBase['type']; + hint?: ReactNode; + callout?: ReactNode; + value?: SettingValue; + editor?: SettingEditor; + onChangeValue?: (value: unknown) => void; + onChangeEditor?: (value: unknown) => void; + onResetButtonClick?: () => void; + className?: string; + invisible?: boolean; + label?: string; + sectionChanged?: boolean; + hasResetButton?: boolean; + actionText?: string; +}; + const MemoizedSetting = ({ type, hint = undefined, callout = undefined, value = undefined, editor = undefined, - onChangeValue = () => {}, - onChangeEditor = () => {}, + onChangeValue, + onChangeEditor, className = undefined, invisible = undefined, ...inputProps -}) => { +}: MemoizedSettingProps): ReactElement | null => { if (invisible) { return null; } - const InputComponent = - { - boolean: BooleanSettingInput, - string: StringSettingInput, - relativeUrl: RelativeUrlSettingInput, - password: PasswordSettingInput, - int: IntSettingInput, - select: SelectSettingInput, - multiSelect: MultiSelectSettingInput, - language: LanguageSettingInput, - color: ColorSettingInput, - font: FontSettingInput, - code: CodeSettingInput, - action: ActionSettingInput, - asset: AssetSettingInput, - roomPick: RoomPickSettingInput, - timezone: SelectTimezoneSettingInput, - }[type] || GenericSettingInput; + const InputComponent = inputsByType[type]; return ( diff --git a/apps/meteor/client/views/admin/settings/Section.js b/apps/meteor/client/views/admin/settings/Section.tsx similarity index 57% rename from apps/meteor/client/views/admin/settings/Section.js rename to apps/meteor/client/views/admin/settings/Section.tsx index e2c4ca138e66..4649426e10a0 100644 --- a/apps/meteor/client/views/admin/settings/Section.js +++ b/apps/meteor/client/views/admin/settings/Section.tsx @@ -1,13 +1,24 @@ +import { isSettingColor } from '@rocket.chat/core-typings'; import { Accordion, Box, Button, FieldGroup } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { useTranslation } from '@rocket.chat/ui-contexts'; -import React, { useMemo } from 'react'; +import { TranslationKey, useTranslation } from '@rocket.chat/ui-contexts'; +import React, { ReactElement, ReactNode, useMemo } from 'react'; import { useEditableSettings, useEditableSettingsDispatch } from '../EditableSettingsContext'; import SectionSkeleton from './SectionSkeleton'; import Setting from './Setting'; -function Section({ groupId, hasReset = true, sectionName, tabName = '', solo, ...props }) { +type SectionProps = { + groupId: string; + hasReset?: boolean; + sectionName: string; + tabName?: string; + solo: boolean; + help?: ReactNode; + children?: ReactNode; +}; + +function Section({ groupId, hasReset = true, sectionName, tabName = '', solo, help, children }: SectionProps): ReactElement { const editableSettings = useEditableSettings( useMemo( () => ({ @@ -32,26 +43,41 @@ function Section({ groupId, hasReset = true, sectionName, tabName = '', solo, .. dispatch( editableSettings .filter(({ disabled }) => !disabled) - .map(({ _id, value, packageValue, editor, packageEditor }) => ({ - _id, - value: packageValue, - editor: packageEditor, - changed: JSON.stringify(value) !== JSON.stringify(packageValue) || JSON.stringify(editor) !== JSON.stringify(packageEditor), - })), + .map((setting) => { + if (isSettingColor(setting)) { + return { + _id: setting._id, + value: setting.packageValue, + editor: setting.packageEditor, + changed: + JSON.stringify(setting.value) !== JSON.stringify(setting.packageValue) || + JSON.stringify(setting.editor) !== JSON.stringify(setting.packageEditor), + }; + } + return { + _id: setting._id, + value: setting.packageValue, + changed: JSON.stringify(setting.value) !== JSON.stringify(setting.packageValue), + }; + }), ); }); const t = useTranslation(); - const handleResetSectionClick = () => { + const handleResetSectionClick = (): void => { reset(); }; return ( - - {props.help && ( + + {help && ( - {props.help} + {help} )} @@ -60,7 +86,7 @@ function Section({ groupId, hasReset = true, sectionName, tabName = '', solo, .. ))} - {props.children} + {children} {hasReset && canReset && (