From 05f613f4354fe2a74b3d6b315da2523c395b97c6 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Fri, 29 Sep 2023 18:21:07 -0300 Subject: [PATCH] chore: refactor cloud sync (#30401) --- .../server/functions/buildRegistrationData.ts | 24 +- .../server/functions/connectWorkspace.ts | 85 ++++-- .../functions/finishOAuthAuthorization.ts | 22 +- .../server/functions/getConfirmationPoll.ts | 18 +- .../getWorkspaceAccessTokenWithScope.ts | 12 +- .../server/functions/getWorkspaceLicense.ts | 112 +++++--- .../registerPreIntentWorkspaceWizard.ts | 10 +- .../functions/startRegisterWorkspace.ts | 17 +- .../startRegisterWorkspaceSetupWizard.ts | 16 +- .../functions/syncWorkspace/syncCloudData.ts | 267 ++++++++++++++---- .../app/cloud/server/functions/userLogout.ts | 3 +- .../app/statistics/server/lib/statistics.ts | 34 ++- .../lib/errors/CloudWorkspaceAccessError.ts | 8 + .../errors/CloudWorkspaceConnectionError.ts | 8 + apps/meteor/lib/errors/CloudWorkspaceError.ts | 6 + .../lib/errors/CloudWorkspaceLicenseError.ts | 8 + .../errors/CloudWorkspaceRegistrationError.ts | 8 + apps/meteor/package.json | 3 +- .../server/models/CloudAnnouncements.ts | 6 + .../server/models/raw/CloudAnnouncements.ts | 11 + apps/meteor/server/models/startup.ts | 1 + apps/meteor/server/services/banner/service.ts | 2 +- package.json | 2 +- .../core-services/src/types/IBannerService.ts | 2 +- packages/core-typings/.eslintrc.json | 5 +- packages/core-typings/package.json | 15 +- packages/core-typings/src/IStats.ts | 10 +- .../core-typings/src/cloud/Announcement.ts | 28 ++ .../src/cloud/NpsSurveyAnnouncement.ts | 7 + .../src/cloud/WorkspaceLicensePayload.ts | 10 + .../src/cloud/WorkspaceSyncPayload.ts | 33 +++ packages/core-typings/src/cloud/index.ts | 4 + packages/core-typings/src/index.ts | 2 + packages/model-typings/src/index.ts | 1 + .../model-typings/src/models/IBannersModel.ts | 4 +- .../src/models/ICloudAnnouncementsModel.ts | 6 + packages/models/src/index.ts | 2 + .../AutotranslateSaveSettingsParamsPOST.ts | 2 +- .../FederationVerifyMatrixIdProps.ts | 2 +- .../src/v1/moderation/ReportHistoryProps.ts | 1 + packages/rest-typings/src/v1/omnichannel.ts | 18 +- yarn.lock | 187 +++++++++--- 42 files changed, 757 insertions(+), 265 deletions(-) create mode 100644 apps/meteor/lib/errors/CloudWorkspaceAccessError.ts create mode 100644 apps/meteor/lib/errors/CloudWorkspaceConnectionError.ts create mode 100644 apps/meteor/lib/errors/CloudWorkspaceError.ts create mode 100644 apps/meteor/lib/errors/CloudWorkspaceLicenseError.ts create mode 100644 apps/meteor/lib/errors/CloudWorkspaceRegistrationError.ts create mode 100644 apps/meteor/server/models/CloudAnnouncements.ts create mode 100644 apps/meteor/server/models/raw/CloudAnnouncements.ts create mode 100644 packages/core-typings/src/cloud/Announcement.ts create mode 100644 packages/core-typings/src/cloud/NpsSurveyAnnouncement.ts create mode 100644 packages/core-typings/src/cloud/WorkspaceLicensePayload.ts create mode 100644 packages/core-typings/src/cloud/WorkspaceSyncPayload.ts create mode 100644 packages/core-typings/src/cloud/index.ts create mode 100644 packages/model-typings/src/models/ICloudAnnouncementsModel.ts diff --git a/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts b/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts index d65897b72094..10e0d7f7f7ee 100644 --- a/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts +++ b/apps/meteor/app/cloud/server/functions/buildRegistrationData.ts @@ -5,7 +5,7 @@ import { settings } from '../../../settings/server'; import { statistics } from '../../../statistics/server'; import { LICENSE_VERSION } from '../license'; -type WorkspaceRegistrationData = { +export type WorkspaceRegistrationData = { uniqueId: string; workspaceId: SettingValue; address: SettingValue; @@ -14,11 +14,11 @@ type WorkspaceRegistrationData = { seats: number; allowMarketing: SettingValue; accountName: SettingValue; - organizationType: unknown; - industry: unknown; - orgSize: unknown; - country: unknown; - language: unknown; + organizationType: string; + industry: string; + orgSize: string; + country: string; + language: string; agreePrivacyTerms: SettingValue; website: SettingValue; siteName: SettingValue; @@ -61,15 +61,15 @@ export async function buildWorkspaceRegistrationData { + const cloudUrl = settings.get('Cloud_Url'); + const response = await fetch(`${cloudUrl}/api/oauth/clients`, { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + }, + body, + }); + + if (!response.ok) { + try { + const { error } = await response.json(); + throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${error}`); + } catch (error) { + throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${response.statusText}`); + } + } + + const payload = await response.json(); + + if (!payload) { + return undefined; + } + + return payload; +}; + export async function connectWorkspace(token: string) { - // shouldn't get here due to checking this on the method - // but this is just to double check if (!token) { - return new Error('Invalid token; the registration token is required.'); + throw new CloudWorkspaceConnectionError('Invalid registration token'); } - const redirectUri = getRedirectUri(); + try { + const redirectUri = getRedirectUri(); - const regInfo = { - email: settings.get('Organization_Email'), - client_name: settings.get('Site_Name'), - redirect_uris: [redirectUri], - }; + const body = { + email: settings.get('Organization_Email'), + client_name: settings.get('Site_Name'), + redirect_uris: [redirectUri], + }; - const cloudUrl = settings.get('Cloud_Url'); - let result; - try { - const request = await fetch(`${cloudUrl}/api/oauth/clients`, { - method: 'POST', - headers: { - Authorization: `Bearer ${token}`, - }, - body: regInfo, - }); + const payload = await fetchRegistrationDataPayload({ token, body }); - if (!request.ok) { - throw new Error((await request.json()).error); + if (!payload) { + return false; } - result = await request.json(); - } catch (err: any) { + await saveRegistrationData(payload); + + return true; + } catch (err) { SystemLogger.error({ msg: 'Failed to Connect with Rocket.Chat Cloud', url: '/api/oauth/clients', @@ -45,12 +76,4 @@ export async function connectWorkspace(token: string) { return false; } - - if (!result) { - return false; - } - - await saveRegistrationData(result); - - return true; } diff --git a/apps/meteor/app/cloud/server/functions/finishOAuthAuthorization.ts b/apps/meteor/app/cloud/server/functions/finishOAuthAuthorization.ts index 780aa5c67a99..61b3a77966e7 100644 --- a/apps/meteor/app/cloud/server/functions/finishOAuthAuthorization.ts +++ b/apps/meteor/app/cloud/server/functions/finishOAuthAuthorization.ts @@ -14,15 +14,15 @@ export async function finishOAuthAuthorization(code: string, state: string) { }); } - const cloudUrl = settings.get('Cloud_Url'); const clientId = settings.get('Cloud_Workspace_Client_Id'); const clientSecret = settings.get('Cloud_Workspace_Client_Secret'); const scope = userScopes.join(' '); - let result; + let payload; try { - const request = await fetch(`${cloudUrl}/api/oauth/token`, { + const cloudUrl = settings.get('Cloud_Url'); + const response = await fetch(`${cloudUrl}/api/oauth/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, params: new URLSearchParams({ @@ -35,11 +35,11 @@ export async function finishOAuthAuthorization(code: string, state: string) { }), }); - if (!request.ok) { - throw new Error((await request.json()).error); + if (!response.ok) { + throw new Error((await response.json()).error); } - result = await request.json(); + payload = await response.json(); } catch (err) { SystemLogger.error({ msg: 'Failed to finish OAuth authorization with Rocket.Chat Cloud', @@ -51,7 +51,7 @@ export async function finishOAuthAuthorization(code: string, state: string) { } const expiresAt = new Date(); - expiresAt.setSeconds(expiresAt.getSeconds() + result.expires_in); + expiresAt.setSeconds(expiresAt.getSeconds() + payload.expires_in); const uid = Meteor.userId(); if (!uid) { @@ -65,11 +65,11 @@ export async function finishOAuthAuthorization(code: string, state: string) { { $set: { 'services.cloud': { - accessToken: result.access_token, + accessToken: payload.access_token, expiresAt, - scope: result.scope, - tokenType: result.token_type, - refreshToken: result.refresh_token, + scope: payload.scope, + tokenType: payload.token_type, + refreshToken: payload.refresh_token, }, }, }, diff --git a/apps/meteor/app/cloud/server/functions/getConfirmationPoll.ts b/apps/meteor/app/cloud/server/functions/getConfirmationPoll.ts index 4a35c9834ba5..2c5d9dec77dc 100644 --- a/apps/meteor/app/cloud/server/functions/getConfirmationPoll.ts +++ b/apps/meteor/app/cloud/server/functions/getConfirmationPoll.ts @@ -5,16 +5,16 @@ import { SystemLogger } from '../../../../server/lib/logger/system'; import { settings } from '../../../settings/server'; export async function getConfirmationPoll(deviceCode: string): Promise { - const cloudUrl = settings.get('Cloud_Url'); - - let result; + let payload; try { - const request = await fetch(`${cloudUrl}/api/v2/register/workspace/poll`, { params: { token: deviceCode } }); - if (!request.ok) { - throw new Error((await request.json()).error); + const cloudUrl = settings.get('Cloud_Url'); + const response = await fetch(`${cloudUrl}/api/v2/register/workspace/poll`, { params: { token: deviceCode } }); + + if (!response.ok) { + throw new Error((await response.json()).error); } - result = await request.json(); + payload = await response.json(); } catch (err: any) { SystemLogger.error({ msg: 'Failed to get confirmation poll from Rocket.Chat Cloud', @@ -25,9 +25,9 @@ export async function getConfirmationPoll(deviceCode: string): Promise('Cloud_Url'); // eslint-disable-next-line @typescript-eslint/naming-convention const client_secret = settings.get('Cloud_Workspace_Client_Secret'); const redirectUri = getRedirectUri(); - let authTokenResult; + let payload; try { const body = new URLSearchParams(); body.append('client_id', client_id); @@ -40,12 +39,13 @@ export async function getWorkspaceAccessTokenWithScope(scope = '') { body.append('grant_type', 'client_credentials'); body.append('redirect_uri', redirectUri); - const result = await fetch(`${cloudUrl}/api/oauth/token`, { + const cloudUrl = settings.get('Cloud_Url'); + const response = await fetch(`${cloudUrl}/api/oauth/token`, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, method: 'POST', body, }); - authTokenResult = await result.json(); + payload = await response.json(); } catch (err: any) { SystemLogger.error({ msg: 'Failed to get Workspace AccessToken from Rocket.Chat Cloud', @@ -64,10 +64,10 @@ export async function getWorkspaceAccessTokenWithScope(scope = '') { } const expiresAt = new Date(); - expiresAt.setSeconds(expiresAt.getSeconds() + authTokenResult.expires_in); + expiresAt.setSeconds(expiresAt.getSeconds() + payload.expires_in); tokenResponse.expiresAt = expiresAt; - tokenResponse.token = authTokenResult.access_token; + tokenResponse.token = payload.access_token; return tokenResponse; } diff --git a/apps/meteor/app/cloud/server/functions/getWorkspaceLicense.ts b/apps/meteor/app/cloud/server/functions/getWorkspaceLicense.ts index 6be18f86d466..504de297791f 100644 --- a/apps/meteor/app/cloud/server/functions/getWorkspaceLicense.ts +++ b/apps/meteor/app/cloud/server/functions/getWorkspaceLicense.ts @@ -1,56 +1,94 @@ +import type { Cloud, Serialized } from '@rocket.chat/core-typings'; import { Settings } from '@rocket.chat/models'; import { serverFetch as fetch } from '@rocket.chat/server-fetch'; +import { v, compile } from 'suretype'; import { callbacks } from '../../../../lib/callbacks'; +import { CloudWorkspaceConnectionError } from '../../../../lib/errors/CloudWorkspaceConnectionError'; +import { CloudWorkspaceLicenseError } from '../../../../lib/errors/CloudWorkspaceLicenseError'; import { SystemLogger } from '../../../../server/lib/logger/system'; import { settings } from '../../../settings/server'; import { LICENSE_VERSION } from '../license'; -import { generateWorkspaceBearerHttpHeaderOrThrow } from './getWorkspaceAccessToken'; -import { handleResponse } from './supportedVersionsToken/supportedVersionsToken'; +import { getWorkspaceAccessToken } from './getWorkspaceAccessToken'; -export async function getWorkspaceLicense() { - const token = await generateWorkspaceBearerHttpHeaderOrThrow(); +const workspaceLicensePayloadSchema = v.object({ + version: v.number().required(), + address: v.string().required(), + license: v.string().required(), + updatedAt: v.string().format('date-time').required(), + modules: v.string().required(), + expireAt: v.string().format('date-time').required(), +}); - const currentLicense = await Settings.findOne('Cloud_Workspace_License'); +const assertWorkspaceLicensePayload = compile(workspaceLicensePayloadSchema); + +const fetchCloudWorkspaceLicensePayload = async ({ token }: { token: string }): Promise> => { + const workspaceRegistrationClientUri = settings.get('Cloud_Workspace_Registration_Client_Uri'); + const response = await fetch(`${workspaceRegistrationClientUri}/license`, { + headers: { + Authorization: `Bearer ${token}`, + }, + params: { + version: LICENSE_VERSION, + }, + }); - // TODO: check if this is the correct way to handle this - // If there is no license, in theory, it should be a new workspace non registered - // in this case the `generateWorkspaceBearerHttpHeaderOrThrow` show throw an error before - // so in theory, this should never happen - if (!currentLicense?._updatedAt) { - throw new Error('Failed to retrieve current license'); + if (!response.ok) { + try { + const { error } = await response.json(); + throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${error}`); + } catch (error) { + throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${response.statusText}`); + } } - const request = await handleResponse( - fetch(`${settings.get('Cloud_Workspace_Registration_Client_Uri')}/license`, { - headers: { - ...token, - }, - params: { - version: LICENSE_VERSION, - }, - }), - ); - - if (!request.success) { + const payload = await response.json(); + + assertWorkspaceLicensePayload(payload); + + return payload; +}; + +export async function getWorkspaceLicense(): Promise<{ updated: boolean; license: string }> { + const currentLicense = await Settings.findOne('Cloud_Workspace_License'); + + const fromCurrentLicense = async () => { + const license = currentLicense?.value as string | undefined; + if (license) { + callbacks.run('workspaceLicenseChanged', license); + } + + return { updated: false, license: license ?? '' }; + }; + + try { + const token = await getWorkspaceAccessToken(); + if (!token) { + return fromCurrentLicense(); + } + + if (!currentLicense?._updatedAt) { + throw new CloudWorkspaceLicenseError('Failed to retrieve current license'); + } + + const payload = await fetchCloudWorkspaceLicensePayload({ token }); + + if (Date.parse(payload.updatedAt) <= currentLicense._updatedAt.getTime()) { + return fromCurrentLicense(); + } + + await Settings.updateValueById('Cloud_Workspace_License', payload.license); + + await callbacks.run('workspaceLicenseChanged', payload.license); + + return { updated: true, license: payload.license }; + } catch (err) { SystemLogger.error({ msg: 'Failed to update license from Rocket.Chat Cloud', url: '/license', - err: request.error, + err, }); - if (currentLicense.value) { - return callbacks.run('workspaceLicenseChanged', currentLicense.value); - } - return; - } - const remoteLicense = request.result as any; - - if (remoteLicense.updatedAt <= currentLicense._updatedAt) { - return callbacks.run('workspaceLicenseChanged', currentLicense.value); + return fromCurrentLicense(); } - - await Settings.updateValueById('Cloud_Workspace_License', remoteLicense.license); - - await callbacks.run('workspaceLicenseChanged', remoteLicense.license); } diff --git a/apps/meteor/app/cloud/server/functions/registerPreIntentWorkspaceWizard.ts b/apps/meteor/app/cloud/server/functions/registerPreIntentWorkspaceWizard.ts index 2a04aa54cfe7..ce415d2aa983 100644 --- a/apps/meteor/app/cloud/server/functions/registerPreIntentWorkspaceWizard.ts +++ b/apps/meteor/app/cloud/server/functions/registerPreIntentWorkspaceWizard.ts @@ -15,16 +15,16 @@ export async function registerPreIntentWorkspaceWizard(): Promise { } const regInfo = await buildWorkspaceRegistrationData(email); - const cloudUrl = settings.get('Cloud_Url'); try { - const request = await fetch(`${cloudUrl}/api/v2/register/workspace/pre-intent`, { + const cloudUrl = settings.get('Cloud_Url'); + const response = await fetch(`${cloudUrl}/api/v2/register/workspace/pre-intent`, { + method: 'POST', body: regInfo, timeout: 10 * 1000, - method: 'POST', }); - if (!request.ok) { - throw new Error((await request.json()).error); + if (!response.ok) { + throw new Error((await response.json()).error); } return true; diff --git a/apps/meteor/app/cloud/server/functions/startRegisterWorkspace.ts b/apps/meteor/app/cloud/server/functions/startRegisterWorkspace.ts index 7f7c78a137e0..5f5df80d0d3d 100644 --- a/apps/meteor/app/cloud/server/functions/startRegisterWorkspace.ts +++ b/apps/meteor/app/cloud/server/functions/startRegisterWorkspace.ts @@ -19,22 +19,21 @@ export async function startRegisterWorkspace(resend = false) { const regInfo = await buildWorkspaceRegistrationData(undefined); - const cloudUrl = settings.get('Cloud_Url'); - - let result; + let payload; try { - const request = await fetch(`${cloudUrl}/api/v2/register/workspace`, { + const cloudUrl = settings.get('Cloud_Url'); + const response = await fetch(`${cloudUrl}/api/v2/register/workspace`, { method: 'POST', body: regInfo, params: { resend, }, }); - if (!request.ok) { - throw new Error((await request.json()).error); + if (!response.ok) { + throw new Error((await response.json()).error); } - result = await request.json(); + payload = await response.json(); } catch (err: any) { SystemLogger.error({ msg: 'Failed to register with Rocket.Chat Cloud', @@ -44,11 +43,11 @@ export async function startRegisterWorkspace(resend = false) { return false; } - if (!result) { + if (!payload) { return false; } - await Settings.updateValueById('Cloud_Workspace_Id', result.id); + await Settings.updateValueById('Cloud_Workspace_Id', payload.id); return true; } diff --git a/apps/meteor/app/cloud/server/functions/startRegisterWorkspaceSetupWizard.ts b/apps/meteor/app/cloud/server/functions/startRegisterWorkspaceSetupWizard.ts index 3afe84c409ec..382478db61c7 100644 --- a/apps/meteor/app/cloud/server/functions/startRegisterWorkspaceSetupWizard.ts +++ b/apps/meteor/app/cloud/server/functions/startRegisterWorkspaceSetupWizard.ts @@ -7,22 +7,22 @@ import { buildWorkspaceRegistrationData } from './buildRegistrationData'; export async function startRegisterWorkspaceSetupWizard(resend = false, email: string): Promise { const regInfo = await buildWorkspaceRegistrationData(email); - const cloudUrl = settings.get('Cloud_Url'); - let result; + let payload; try { - const request = await fetch(`${cloudUrl}/api/v2/register/workspace/intent`, { + const cloudUrl = settings.get('Cloud_Url'); + const response = await fetch(`${cloudUrl}/api/v2/register/workspace/intent`, { body: regInfo, method: 'POST', params: { resent: resend, }, }); - if (!request.ok) { - throw new Error((await request.json()).error); + if (!response.ok) { + throw new Error((await response.json()).error); } - result = await request.json(); + payload = await response.json(); } catch (err: any) { SystemLogger.error({ msg: 'Failed to register workspace intent with Rocket.Chat Cloud', @@ -33,9 +33,9 @@ export async function startRegisterWorkspaceSetupWizard(resend = false, email: s throw err; } - if (!result) { + if (!payload) { throw new Error('Failed to fetch registration intent endpoint'); } - return result; + return payload; } diff --git a/apps/meteor/app/cloud/server/functions/syncWorkspace/syncCloudData.ts b/apps/meteor/app/cloud/server/functions/syncWorkspace/syncCloudData.ts index 0dc56f31c5da..df63dda6d563 100644 --- a/apps/meteor/app/cloud/server/functions/syncWorkspace/syncCloudData.ts +++ b/apps/meteor/app/cloud/server/functions/syncWorkspace/syncCloudData.ts @@ -1,85 +1,242 @@ import { NPS, Banner } from '@rocket.chat/core-services'; -import { Settings } from '@rocket.chat/models'; +import { type Cloud, type Serialized } from '@rocket.chat/core-typings'; +import { CloudAnnouncements, Settings } from '@rocket.chat/models'; import { serverFetch as fetch } from '@rocket.chat/server-fetch'; +import { v, compile } from 'suretype'; +import { CloudWorkspaceAccessError } from '../../../../../lib/errors/CloudWorkspaceAccessError'; +import { CloudWorkspaceConnectionError } from '../../../../../lib/errors/CloudWorkspaceConnectionError'; +import { CloudWorkspaceRegistrationError } from '../../../../../lib/errors/CloudWorkspaceRegistrationError'; import { SystemLogger } from '../../../../../server/lib/logger/system'; import { getAndCreateNpsSurvey } from '../../../../../server/services/nps/getAndCreateNpsSurvey'; import { settings } from '../../../../settings/server'; +import type { WorkspaceRegistrationData } from '../buildRegistrationData'; import { buildWorkspaceRegistrationData } from '../buildRegistrationData'; -import { generateWorkspaceBearerHttpHeaderOrThrow } from '../getWorkspaceAccessToken'; -import { handleResponse } from '../supportedVersionsToken/supportedVersionsToken'; +import { getWorkspaceAccessToken } from '../getWorkspaceAccessToken'; +import { getWorkspaceLicense } from '../getWorkspaceLicense'; +import { retrieveRegistrationStatus } from '../retrieveRegistrationStatus'; + +const workspaceSyncPayloadSchema = v.object({ + workspaceId: v.string().required(), + publicKey: v.string(), + trial: v.object({ + trialing: v.boolean().required(), + trialID: v.string().required(), + endDate: v.string().format('date-time').required(), + marketing: v + .object({ + utmContent: v.string().required(), + utmMedium: v.string().required(), + utmSource: v.string().required(), + utmCampaign: v.string().required(), + }) + .required(), + DowngradesToPlan: v + .object({ + id: v.string().required(), + }) + .required(), + trialRequested: v.boolean().required(), + }), + nps: v.object({ + id: v.string().required(), + startAt: v.string().format('date-time').required(), + expireAt: v.string().format('date-time').required(), + }), + banners: v.array( + v.object({ + _id: v.string().required(), + _updatedAt: v.string().format('date-time').required(), + platform: v.array(v.string()).required(), + expireAt: v.string().format('date-time').required(), + startAt: v.string().format('date-time').required(), + roles: v.array(v.string()), + createdBy: v.object({ + _id: v.string().required(), + username: v.string(), + }), + createdAt: v.string().format('date-time').required(), + view: v.any(), + active: v.boolean(), + inactivedAt: v.string().format('date-time'), + snapshot: v.string(), + }), + ), + announcements: v.object({ + create: v.array( + v.object({ + _id: v.string().required(), + _updatedAt: v.string().format('date-time').required(), + selector: v.object({ + roles: v.array(v.string()), + }), + platform: v.array(v.string().enum('web', 'mobile')).required(), + expireAt: v.string().format('date-time').required(), + startAt: v.string().format('date-time').required(), + createdBy: v.string().enum('cloud', 'system').required(), + createdAt: v.string().format('date-time').required(), + dictionary: v.object({}).additional(v.object({}).additional(v.string())), + view: v.any(), + surface: v.string().enum('banner', 'modal').required(), + }), + ), + delete: v.array(v.string()), + }), +}); + +const assertWorkspaceSyncPayload = compile(workspaceSyncPayloadSchema); + +const fetchWorkspaceSyncPayload = async ({ + token, + workspaceRegistrationData, +}: { + token: string; + workspaceRegistrationData: WorkspaceRegistrationData; +}): Promise | undefined> => { + const workspaceRegistrationClientUri = settings.get('Cloud_Workspace_Registration_Client_Uri'); + const response = await fetch(`${workspaceRegistrationClientUri}/client`, { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + }, + body: workspaceRegistrationData, + }); + + if (!response.ok) { + try { + const { error } = await response.json(); + throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${error}`); + } catch (error) { + throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${response.statusText}`); + } + } -export async function syncCloudData() { - const info = await buildWorkspaceRegistrationData(undefined); + const payload = await response.json(); + + if (!payload) { + return undefined; + } - const token = await generateWorkspaceBearerHttpHeaderOrThrow(true); + assertWorkspaceSyncPayload(payload); - const request = await handleResponse( - fetch(`${settings.get('Cloud_Workspace_Registration_Client_Uri')}/client`, { - headers: { - ...token, - }, - body: info, - method: 'POST', - }), - ); + return payload; +}; - if (!request.success) { - return SystemLogger.error({ - msg: 'Failed to sync with Rocket.Chat Cloud', - url: '/client', - err: request.error, +const handleNpsOnWorkspaceSync = async (nps: Exclude['nps'], undefined>) => { + const { id: npsId, expireAt } = nps; + + const startAt = new Date(nps.startAt); + + await NPS.create({ + npsId, + startAt, + expireAt: new Date(expireAt), + createdBy: { + _id: 'rocket.cat', + username: 'rocket.cat', + }, + }); + + const now = new Date(); + + if (startAt.getFullYear() === now.getFullYear() && startAt.getMonth() === now.getMonth() && startAt.getDate() === now.getDate()) { + await getAndCreateNpsSurvey(npsId); + } +}; + +const handleBannerOnWorkspaceSync = async (banners: Exclude['banners'], undefined>) => { + for await (const banner of banners) { + const { createdAt, expireAt, startAt, inactivedAt, _updatedAt, ...rest } = banner; + + await Banner.create({ + ...rest, + createdAt: new Date(createdAt), + expireAt: new Date(expireAt), + startAt: new Date(startAt), + ...(inactivedAt && { inactivedAt: new Date(inactivedAt) }), }); } +}; + +const deserializeAnnouncement = (announcement: Serialized): Cloud.Announcement => ({ + ...announcement, + _updatedAt: new Date(announcement._updatedAt), + expireAt: new Date(announcement.expireAt), + startAt: new Date(announcement.startAt), + createdAt: new Date(announcement.createdAt), +}); + +const handleAnnouncementsOnWorkspaceSync = async ( + announcements: Exclude['announcements'], undefined>, +) => { + const { create, delete: deleteIds } = announcements; + + if (deleteIds) { + await CloudAnnouncements.deleteMany({ _id: { $in: deleteIds } }); + } - const data = request.result as any; - if (!data) { - return true; + for await (const announcement of create.map(deserializeAnnouncement)) { + const { _id, ...rest } = announcement; + + await CloudAnnouncements.updateOne({ _id }, { $set: rest }, { upsert: true }); } +}; - if (data.publicKey) { - await Settings.updateValueById('Cloud_Workspace_PublicKey', data.publicKey); +const consumeWorkspaceSyncPayload = async (result: Serialized) => { + if (result.publicKey) { + await Settings.updateValueById('Cloud_Workspace_PublicKey', result.publicKey); } - if (data.trial?.trialId) { + if (result.trial?.trialID) { await Settings.updateValueById('Cloud_Workspace_Had_Trial', true); } - if (data.nps) { - const { id: npsId, expireAt } = data.nps; + if (result.nps) { + await handleNpsOnWorkspaceSync(result.nps); + } - const startAt = new Date(data.nps.startAt); + // add banners + if (result.banners) { + await handleBannerOnWorkspaceSync(result.banners); + } - await NPS.create({ - npsId, - startAt, - expireAt: new Date(expireAt), - createdBy: { - _id: 'rocket.cat', - username: 'rocket.cat', - }, - }); + if (result.announcements) { + await handleAnnouncementsOnWorkspaceSync(result.announcements); + } +}; - const now = new Date(); +export async function syncCloudData() { + try { + const { workspaceRegistered } = await retrieveRegistrationStatus(); + if (!workspaceRegistered) { + throw new CloudWorkspaceRegistrationError('Workspace is not registered'); + } - if (startAt.getFullYear() === now.getFullYear() && startAt.getMonth() === now.getMonth() && startAt.getDate() === now.getDate()) { - await getAndCreateNpsSurvey(npsId); + const token = await getWorkspaceAccessToken(true); + if (!token) { + throw new CloudWorkspaceAccessError('Workspace does not have a valid access token'); } - } - // add banners - if (data.banners) { - for await (const banner of data.banners) { - const { createdAt, expireAt, startAt } = banner; - - await Banner.create({ - ...banner, - createdAt: new Date(createdAt), - expireAt: new Date(expireAt), - startAt: new Date(startAt), - }); + const workspaceRegistrationData = await buildWorkspaceRegistrationData(undefined); + + const payload = await fetchWorkspaceSyncPayload({ token, workspaceRegistrationData }); + + if (!payload) { + return true; } - } - return true; + await consumeWorkspaceSyncPayload(payload); + + return true; + } catch (err) { + SystemLogger.error({ + msg: 'Failed to sync with Rocket.Chat Cloud', + url: '/client', + err, + }); + + return false; + } finally { + await getWorkspaceLicense(); + } } diff --git a/apps/meteor/app/cloud/server/functions/userLogout.ts b/apps/meteor/app/cloud/server/functions/userLogout.ts index 7dd4aa094535..386137ced604 100644 --- a/apps/meteor/app/cloud/server/functions/userLogout.ts +++ b/apps/meteor/app/cloud/server/functions/userLogout.ts @@ -26,10 +26,11 @@ export async function userLogout(userId: string): Promise { return ''; } - const cloudUrl = settings.get('Cloud_Url'); const clientSecret = settings.get('Cloud_Workspace_Client_Secret'); const { refreshToken } = user.services.cloud; + + const cloudUrl = settings.get('Cloud_Url'); await fetch(`${cloudUrl}/api/oauth/revoke`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, diff --git a/apps/meteor/app/statistics/server/lib/statistics.ts b/apps/meteor/app/statistics/server/lib/statistics.ts index 54470a209196..b6b983d92fce 100644 --- a/apps/meteor/app/statistics/server/lib/statistics.ts +++ b/apps/meteor/app/statistics/server/lib/statistics.ts @@ -41,8 +41,6 @@ import { getAppsStatistics } from './getAppsStatistics'; import { getImporterStatistics } from './getImporterStatistics'; import { getServicesStatistics } from './getServicesStatistics'; -const wizardFields = ['Organization_Type', 'Industry', 'Size', 'Country', 'Language', 'Server_Type', 'Register_Server']; - const getUserLanguages = async (totalUsers: number): Promise<{ [key: string]: number }> => { const result = await Users.getUserLanguages(); @@ -70,17 +68,29 @@ export const statistics = { const statistics = {} as IStats; const statsPms = []; + const fetchWizardSettingValue = async (settingName: string): Promise => { + return ((await Settings.findOne(settingName))?.value as T | undefined) ?? undefined; + }; + // Setup Wizard - statistics.wizard = {}; - await Promise.all( - wizardFields.map(async (field) => { - const record = await Settings.findOne(field); - if (record) { - const wizardField = field.replace(/_/g, '').replace(field[0], field[0].toLowerCase()); - statistics.wizard[wizardField] = record.value; - } - }), - ); + const [organizationType, industry, size, country, language, serverType, registerServer] = await Promise.all([ + fetchWizardSettingValue('Organization_Type'), + fetchWizardSettingValue('Industry'), + fetchWizardSettingValue('Size'), + fetchWizardSettingValue('Country'), + fetchWizardSettingValue('Language'), + fetchWizardSettingValue('Server_Type'), + fetchWizardSettingValue('Register_Server'), + ]); + statistics.wizard = { + organizationType, + industry, + size, + country, + language, + serverType, + registerServer, + }; // Version const uniqueID = await Settings.findOne('uniqueID'); diff --git a/apps/meteor/lib/errors/CloudWorkspaceAccessError.ts b/apps/meteor/lib/errors/CloudWorkspaceAccessError.ts new file mode 100644 index 000000000000..4cea63a01f09 --- /dev/null +++ b/apps/meteor/lib/errors/CloudWorkspaceAccessError.ts @@ -0,0 +1,8 @@ +import { CloudWorkspaceError } from './CloudWorkspaceError'; + +export class CloudWorkspaceAccessError extends CloudWorkspaceError { + constructor(message: string) { + super(message); + this.name = CloudWorkspaceAccessError.name; + } +} diff --git a/apps/meteor/lib/errors/CloudWorkspaceConnectionError.ts b/apps/meteor/lib/errors/CloudWorkspaceConnectionError.ts new file mode 100644 index 000000000000..8b4edcf8f588 --- /dev/null +++ b/apps/meteor/lib/errors/CloudWorkspaceConnectionError.ts @@ -0,0 +1,8 @@ +import { CloudWorkspaceError } from './CloudWorkspaceError'; + +export class CloudWorkspaceConnectionError extends CloudWorkspaceError { + constructor(message: string) { + super(message); + this.name = CloudWorkspaceConnectionError.name; + } +} diff --git a/apps/meteor/lib/errors/CloudWorkspaceError.ts b/apps/meteor/lib/errors/CloudWorkspaceError.ts new file mode 100644 index 000000000000..d843c42ea520 --- /dev/null +++ b/apps/meteor/lib/errors/CloudWorkspaceError.ts @@ -0,0 +1,6 @@ +export class CloudWorkspaceError extends Error { + constructor(message: string) { + super(message); + this.name = CloudWorkspaceError.name; + } +} diff --git a/apps/meteor/lib/errors/CloudWorkspaceLicenseError.ts b/apps/meteor/lib/errors/CloudWorkspaceLicenseError.ts new file mode 100644 index 000000000000..96c9a28be82c --- /dev/null +++ b/apps/meteor/lib/errors/CloudWorkspaceLicenseError.ts @@ -0,0 +1,8 @@ +import { CloudWorkspaceError } from './CloudWorkspaceError'; + +export class CloudWorkspaceLicenseError extends CloudWorkspaceError { + constructor(message: string) { + super(message); + this.name = CloudWorkspaceLicenseError.name; + } +} diff --git a/apps/meteor/lib/errors/CloudWorkspaceRegistrationError.ts b/apps/meteor/lib/errors/CloudWorkspaceRegistrationError.ts new file mode 100644 index 000000000000..aecec757acee --- /dev/null +++ b/apps/meteor/lib/errors/CloudWorkspaceRegistrationError.ts @@ -0,0 +1,8 @@ +import { CloudWorkspaceError } from './CloudWorkspaceError'; + +export class CloudWorkspaceRegistrationError extends CloudWorkspaceError { + constructor(message: string) { + super(message); + this.name = CloudWorkspaceRegistrationError.name; + } +} diff --git a/apps/meteor/package.json b/apps/meteor/package.json index b59552d1fcc5..8bf3e3fe886a 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -289,7 +289,7 @@ "@xmldom/xmldom": "^0.8.8", "adm-zip": "0.5.10", "ajv": "^8.11.0", - "ajv-formats": "^2.1.1", + "ajv-formats": "~2.1.1", "apn": "2.2.0", "archiver": "^3.1.1", "asterisk-manager": "^0.2.0", @@ -417,6 +417,7 @@ "stream-buffers": "^3.0.2", "strict-uri-encode": "^2.0.0", "string-strip-html": "^7.0.3", + "suretype": "~2.4.1", "tar-stream": "^1.6.2", "textarea-caret": "^3.1.0", "tinykeys": "^1.4.0", diff --git a/apps/meteor/server/models/CloudAnnouncements.ts b/apps/meteor/server/models/CloudAnnouncements.ts new file mode 100644 index 000000000000..4f6692d67fc9 --- /dev/null +++ b/apps/meteor/server/models/CloudAnnouncements.ts @@ -0,0 +1,6 @@ +import { registerModel } from '@rocket.chat/models'; + +import { db } from '../database/utils'; +import { CloudAnnouncementsRaw } from './raw/CloudAnnouncements'; + +registerModel('ICloudAnnouncementsModel', new CloudAnnouncementsRaw(db)); diff --git a/apps/meteor/server/models/raw/CloudAnnouncements.ts b/apps/meteor/server/models/raw/CloudAnnouncements.ts new file mode 100644 index 000000000000..21b4304b2bd5 --- /dev/null +++ b/apps/meteor/server/models/raw/CloudAnnouncements.ts @@ -0,0 +1,11 @@ +import type { Cloud } from '@rocket.chat/core-typings'; +import type { ICloudAnnouncementsModel } from '@rocket.chat/model-typings'; +import type { Db } from 'mongodb'; + +import { BaseRaw } from './BaseRaw'; + +export class CloudAnnouncementsRaw extends BaseRaw implements ICloudAnnouncementsModel { + constructor(db: Db) { + super(db, 'cloud_announcements'); + } +} diff --git a/apps/meteor/server/models/startup.ts b/apps/meteor/server/models/startup.ts index 14b26e0f188f..d355d1febd16 100644 --- a/apps/meteor/server/models/startup.ts +++ b/apps/meteor/server/models/startup.ts @@ -68,3 +68,4 @@ import './Imports'; import './AppsTokens'; import './CronHistory'; import './Migrations'; +import './CloudAnnouncements'; diff --git a/apps/meteor/server/services/banner/service.ts b/apps/meteor/server/services/banner/service.ts index 4dc0dbbec494..d20b9e780875 100644 --- a/apps/meteor/server/services/banner/service.ts +++ b/apps/meteor/server/services/banner/service.ts @@ -26,7 +26,7 @@ export class BannerService extends ServiceClassInternal implements IBannerServic return true; } - async create(doc: Optional): Promise { + async create(doc: Optional): Promise { const bannerId = doc._id || uuidv4(); doc.view.appId = 'banner-core'; diff --git a/package.json b/package.json index f9029efb8b66..962e42d48c6e 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "@types/chart.js": "^2.9.37", "@types/js-yaml": "^4.0.5", "husky": "^7.0.4", - "turbo": "^1.10.13" + "turbo": "~1.10.14" }, "workspaces": [ "apps/*", diff --git a/packages/core-services/src/types/IBannerService.ts b/packages/core-services/src/types/IBannerService.ts index 1035bdd59510..50b8ab08275c 100644 --- a/packages/core-services/src/types/IBannerService.ts +++ b/packages/core-services/src/types/IBannerService.ts @@ -2,7 +2,7 @@ import type { BannerPlatform, IBanner, Optional } from '@rocket.chat/core-typing export interface IBannerService { getBannersForUser(userId: string, platform: BannerPlatform, bannerId?: string): Promise; - create(banner: Optional): Promise; + create(banner: Optional): Promise; dismiss(userId: string, bannerId: string): Promise; discardDismissal(bannerId: string): Promise; getById(bannerId: string): Promise; diff --git a/packages/core-typings/.eslintrc.json b/packages/core-typings/.eslintrc.json index 56a6f6602e33..44d74e043bc4 100644 --- a/packages/core-typings/.eslintrc.json +++ b/packages/core-typings/.eslintrc.json @@ -8,5 +8,8 @@ } } ], - "ignorePatterns": ["**/dist"] + "ignorePatterns": ["**/dist"], + "rules": { + "@typescript-eslint/no-empty-interface": "off" + } } diff --git a/packages/core-typings/package.json b/packages/core-typings/package.json index 2d0b0d734897..2b673be0f857 100644 --- a/packages/core-typings/package.json +++ b/packages/core-typings/package.json @@ -1,13 +1,7 @@ { + "$schema": "https://json.schemastore.org/package", "name": "@rocket.chat/core-typings", "version": "6.4.0-rc.4", - "devDependencies": { - "@rocket.chat/eslint-config": "workspace:^", - "eslint": "~8.45.0", - "mongodb": "^4.17.1", - "prettier": "~2.8.8", - "typescript": "~5.2.2" - }, "scripts": { "lint": "eslint --ext .js,.jsx,.ts,.tsx .", "lint:fix": "eslint --ext .js,.jsx,.ts,.tsx . --fix", @@ -26,6 +20,13 @@ "@rocket.chat/message-parser": "next", "@rocket.chat/ui-kit": "^0.32.1" }, + "devDependencies": { + "@rocket.chat/eslint-config": "workspace:^", + "eslint": "~8.45.0", + "mongodb": "^4.17.1", + "prettier": "~2.8.8", + "typescript": "~5.2.2" + }, "volta": { "extends": "../../package.json" } diff --git a/packages/core-typings/src/IStats.ts b/packages/core-typings/src/IStats.ts index 2ea8115a727c..cd8aeb9f1762 100644 --- a/packages/core-typings/src/IStats.ts +++ b/packages/core-typings/src/IStats.ts @@ -6,7 +6,15 @@ import type { ITeamStats } from './ITeam'; export interface IStats { _id: string; - wizard: Record; + wizard: { + organizationType?: string; + industry?: string; + size?: string; + country?: string; + language?: string; + serverType?: string; + registerServer?: boolean; + }; uniqueId: string; installedAt?: string; version?: string; diff --git a/packages/core-typings/src/cloud/Announcement.ts b/packages/core-typings/src/cloud/Announcement.ts new file mode 100644 index 000000000000..3d891daf132f --- /dev/null +++ b/packages/core-typings/src/cloud/Announcement.ts @@ -0,0 +1,28 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + +import type { IRocketChatRecord } from '../IRocketChatRecord'; +import { type UiKitPayload } from '../UIKit'; + +type TargetPlatform = 'web' | 'mobile'; + +type Dictionary = { + [lng: string]: { + [key: string]: string; + }; +}; + +type Creator = 'cloud' | 'system'; + +export interface Announcement extends IRocketChatRecord { + selector?: { + roles?: string[]; + }; + platform: TargetPlatform[]; + expireAt: Date; + startAt: Date; + createdBy: Creator; + createdAt: Date; + dictionary?: Dictionary; + view: UiKitPayload; + surface: 'banner' | 'modal'; +} diff --git a/packages/core-typings/src/cloud/NpsSurveyAnnouncement.ts b/packages/core-typings/src/cloud/NpsSurveyAnnouncement.ts new file mode 100644 index 000000000000..fff1db8f1b99 --- /dev/null +++ b/packages/core-typings/src/cloud/NpsSurveyAnnouncement.ts @@ -0,0 +1,7 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + +export interface NpsSurveyAnnouncement { + id: string; + startAt: Date; + expireAt: Date; +} diff --git a/packages/core-typings/src/cloud/WorkspaceLicensePayload.ts b/packages/core-typings/src/cloud/WorkspaceLicensePayload.ts new file mode 100644 index 000000000000..7e81e1b47599 --- /dev/null +++ b/packages/core-typings/src/cloud/WorkspaceLicensePayload.ts @@ -0,0 +1,10 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + +export interface WorkspaceLicensePayload { + version: number; + address: string; + license: string; + updatedAt: Date; + modules: string; + expireAt: Date; +} diff --git a/packages/core-typings/src/cloud/WorkspaceSyncPayload.ts b/packages/core-typings/src/cloud/WorkspaceSyncPayload.ts new file mode 100644 index 000000000000..964cb42571b2 --- /dev/null +++ b/packages/core-typings/src/cloud/WorkspaceSyncPayload.ts @@ -0,0 +1,33 @@ +/* eslint-disable @typescript-eslint/naming-convention */ + +import type { IBanner } from '../IBanner'; +import type { Announcement } from './Announcement'; +import type { NpsSurveyAnnouncement } from './NpsSurveyAnnouncement'; + +export interface WorkspaceSyncPayload { + workspaceId: string; + publicKey?: string; + announcements?: { + create: Announcement[]; + delete: Announcement['_id'][]; + }; + trial?: { + trialing: boolean; + trialID: string; + endDate: Date; + marketing: { + utmContent: string; + utmMedium: string; + utmSource: string; + utmCampaign: string; + }; + DowngradesToPlan: { + id: string; + }; + trialRequested: boolean; + }; + /** @deprecated */ + nps?: NpsSurveyAnnouncement; + /** @deprecated */ + banners?: IBanner[]; +} diff --git a/packages/core-typings/src/cloud/index.ts b/packages/core-typings/src/cloud/index.ts new file mode 100644 index 000000000000..b9c044b054e3 --- /dev/null +++ b/packages/core-typings/src/cloud/index.ts @@ -0,0 +1,4 @@ +export { Announcement } from './Announcement'; +export { NpsSurveyAnnouncement } from './NpsSurveyAnnouncement'; +export { WorkspaceLicensePayload } from './WorkspaceLicensePayload'; +export { WorkspaceSyncPayload } from './WorkspaceSyncPayload'; diff --git a/packages/core-typings/src/index.ts b/packages/core-typings/src/index.ts index 459e5680900b..de36606e7f90 100644 --- a/packages/core-typings/src/index.ts +++ b/packages/core-typings/src/index.ts @@ -134,3 +134,5 @@ export * from './ICustomOAuthConfig'; export * from './IModerationReport'; export * from './CustomFieldMetadata'; + +export * as Cloud from './cloud'; diff --git a/packages/model-typings/src/index.ts b/packages/model-typings/src/index.ts index 23e77ff1de29..a1874b144347 100644 --- a/packages/model-typings/src/index.ts +++ b/packages/model-typings/src/index.ts @@ -79,3 +79,4 @@ export * from './models/IAuditLogModel'; export * from './models/ICronHistoryModel'; export * from './models/IMigrationsModel'; export * from './models/IModerationReportsModel'; +export * from './models/ICloudAnnouncementsModel'; diff --git a/packages/model-typings/src/models/IBannersModel.ts b/packages/model-typings/src/models/IBannersModel.ts index 62f33ef5d3b2..4fe496bb954c 100644 --- a/packages/model-typings/src/models/IBannersModel.ts +++ b/packages/model-typings/src/models/IBannersModel.ts @@ -1,10 +1,10 @@ -import type { BannerPlatform, IBanner } from '@rocket.chat/core-typings'; +import type { BannerPlatform, IBanner, Optional } from '@rocket.chat/core-typings'; import type { Document, FindCursor, FindOptions, UpdateResult, InsertOneResult } from 'mongodb'; import type { IBaseModel } from './IBaseModel'; export interface IBannersModel extends IBaseModel { - create(doc: IBanner): Promise>; + create(doc: Optional): Promise>; findActiveByRoleOrId(roles: string[], platform: BannerPlatform, bannerId?: string, options?: FindOptions): FindCursor; diff --git a/packages/model-typings/src/models/ICloudAnnouncementsModel.ts b/packages/model-typings/src/models/ICloudAnnouncementsModel.ts new file mode 100644 index 000000000000..672ff8c316a0 --- /dev/null +++ b/packages/model-typings/src/models/ICloudAnnouncementsModel.ts @@ -0,0 +1,6 @@ +import type { Cloud } from '@rocket.chat/core-typings'; + +import type { IBaseModel } from './IBaseModel'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ICloudAnnouncementsModel extends IBaseModel {} diff --git a/packages/models/src/index.ts b/packages/models/src/index.ts index e1cf91f1b0ee..1e83fe72b93e 100644 --- a/packages/models/src/index.ts +++ b/packages/models/src/index.ts @@ -78,6 +78,7 @@ import type { ICronHistoryModel, IMigrationsModel, IModerationReportsModel, + ICloudAnnouncementsModel, } from '@rocket.chat/model-typings'; import { proxify } from './proxify'; @@ -170,3 +171,4 @@ export const AuditLog = proxify('IAuditLogModel'); export const CronHistory = proxify('ICronHistoryModel'); export const Migrations = proxify('IMigrationsModel'); export const ModerationReports = proxify('IModerationReportsModel'); +export const CloudAnnouncements = proxify('ICloudAnnouncementsModel'); diff --git a/packages/rest-typings/src/v1/autotranslate/AutotranslateSaveSettingsParamsPOST.ts b/packages/rest-typings/src/v1/autotranslate/AutotranslateSaveSettingsParamsPOST.ts index 3690d0672ce2..914739d000a0 100644 --- a/packages/rest-typings/src/v1/autotranslate/AutotranslateSaveSettingsParamsPOST.ts +++ b/packages/rest-typings/src/v1/autotranslate/AutotranslateSaveSettingsParamsPOST.ts @@ -21,7 +21,7 @@ const AutotranslateSaveSettingsParamsPostSchema = { enum: ['autoTranslate', 'autoTranslateLanguage'], }, value: { - type: ['boolean', 'string'], + anyOf: [{ type: 'boolean' }, { type: 'string' }], }, defaultLanguage: { type: 'string', diff --git a/packages/rest-typings/src/v1/federation/FederationVerifyMatrixIdProps.ts b/packages/rest-typings/src/v1/federation/FederationVerifyMatrixIdProps.ts index a63d37da07ba..a6009fe20d85 100644 --- a/packages/rest-typings/src/v1/federation/FederationVerifyMatrixIdProps.ts +++ b/packages/rest-typings/src/v1/federation/FederationVerifyMatrixIdProps.ts @@ -11,7 +11,7 @@ const FederationVerifyMatrixIdPropsSchema = { properties: { matrixIds: { type: 'array', - items: [{ type: 'string' }], + items: { type: 'string' }, uniqueItems: true, }, }, diff --git a/packages/rest-typings/src/v1/moderation/ReportHistoryProps.ts b/packages/rest-typings/src/v1/moderation/ReportHistoryProps.ts index 69b1d85f22a5..48b859a7899b 100644 --- a/packages/rest-typings/src/v1/moderation/ReportHistoryProps.ts +++ b/packages/rest-typings/src/v1/moderation/ReportHistoryProps.ts @@ -10,6 +10,7 @@ type ReportHistoryProps = { export type ReportHistoryPropsGET = PaginatedRequest; const reportHistoryPropsSchema = { + type: 'object', properties: { latest: { type: 'string', diff --git a/packages/rest-typings/src/v1/omnichannel.ts b/packages/rest-typings/src/v1/omnichannel.ts index 60f6ed7ace08..31d004ba39c4 100644 --- a/packages/rest-typings/src/v1/omnichannel.ts +++ b/packages/rest-typings/src/v1/omnichannel.ts @@ -2550,6 +2550,10 @@ const GETLivechatRoomsParamsSchema = { type: 'string', nullable: true, }, + query: { + type: 'string', + nullable: true, + }, fields: { type: 'string', nullable: true, @@ -2582,12 +2586,16 @@ const GETLivechatRoomsParamsSchema = { nullable: true, }, open: { - type: ['string', 'boolean'], - nullable: true, + anyOf: [ + { type: 'string', nullable: true }, + { type: 'boolean', nullable: true }, + ], }, onhold: { - type: ['string', 'boolean'], - nullable: true, + anyOf: [ + { type: 'string', nullable: true }, + { type: 'boolean', nullable: true }, + ], }, tags: { type: 'array', @@ -3112,7 +3120,7 @@ const POSTLivechatAppearanceParamsSchema = { type: 'string', }, value: { - type: ['string', 'boolean', 'number'], + anyOf: [{ type: 'string' }, { type: 'boolean' }, { type: 'number' }], }, }, required: ['_id', 'value'], diff --git a/yarn.lock b/yarn.lock index 368d87e88f05..265f9c7ab0d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8008,7 +8008,7 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/css-in-js@npm:next, @rocket.chat/css-in-js@npm:~0.31.26-dev.19": +"@rocket.chat/css-in-js@npm:next": version: 0.31.26-dev.19 resolution: "@rocket.chat/css-in-js@npm:0.31.26-dev.19" dependencies: @@ -8021,6 +8021,15 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/css-in-js@npm:~0.31.26-dev.19": + version: 0.31.26-dev.23 + resolution: "@rocket.chat/css-in-js@npm:0.31.26-dev.23" + dependencies: + "@rocket.chat/memo": ^0.31.25 + checksum: 6d71bd0f232c8ea3fc2711347064ddd14925b1c2b8713f6d7649b98679455029a53ee41d08b98d010da3ea4789afa21a15901a92efef61dee7b32d6965157445 + languageName: node + linkType: hard + "@rocket.chat/css-supports@npm:^0.31.25": version: 0.31.25 resolution: "@rocket.chat/css-supports@npm:0.31.25" @@ -8030,12 +8039,12 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/css-supports@npm:~0.31.26-dev.19": - version: 0.31.26-dev.19 - resolution: "@rocket.chat/css-supports@npm:0.31.26-dev.19" +"@rocket.chat/css-supports@npm:~0.31.26-dev.19, @rocket.chat/css-supports@npm:~0.31.26-dev.23": + version: 0.31.26-dev.23 + resolution: "@rocket.chat/css-supports@npm:0.31.26-dev.23" dependencies: - "@rocket.chat/memo": ~0.31.26-dev.19 - checksum: c689ccca04901b128c8993e7475d89ca1e49d01efac9bb9641a0a35bba4237d36da48204cd26b39e92b8d98f24ff85df40e516fd0e421beaaf7c10a8308536ea + "@rocket.chat/memo": ~0.31.26-dev.23 + checksum: a4f25562df67214b1c92c85a1cd16eb03fc2aea385f48cdde42ad0053b9e03a92ca9e3486d1387c7a31cf68f47fa888825f31acae8f4700ee2b9f03495286a12 languageName: node linkType: hard @@ -8632,13 +8641,20 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/memo@npm:next, @rocket.chat/memo@npm:~0.31.26-dev.19": +"@rocket.chat/memo@npm:next": version: 0.31.26-dev.19 resolution: "@rocket.chat/memo@npm:0.31.26-dev.19" checksum: 387c29643c0d725b2e2d3b79eeebf2ed3ac2fa518178d2836913dddf48f2aa72e80b277d54c77ac0498c144324cdfd3449bae883895c316fbb43c7dbbfcb3993 languageName: node linkType: hard +"@rocket.chat/memo@npm:~0.31.26-dev.19, @rocket.chat/memo@npm:~0.31.26-dev.23": + version: 0.31.26-dev.23 + resolution: "@rocket.chat/memo@npm:0.31.26-dev.23" + checksum: 68301161d87ba25347f1d2ab85c139ba86c5fdd1101f41678808c19ba461772814f4bff048a30e4aefd08978fe2feb952c541bddc0beb6bc3cd190bd7852393b + languageName: node + linkType: hard + "@rocket.chat/message-parser@npm:next": version: 0.32.0-dev.377 resolution: "@rocket.chat/message-parser@npm:0.32.0-dev.377" @@ -8826,7 +8842,7 @@ __metadata: "@xmldom/xmldom": ^0.8.8 adm-zip: 0.5.10 ajv: ^8.11.0 - ajv-formats: ^2.1.1 + ajv-formats: ~2.1.1 apn: 2.2.0 archiver: ^3.1.1 asterisk-manager: ^0.2.0 @@ -9001,6 +9017,7 @@ __metadata: stylelint: ^14.9.1 stylelint-order: ^5.0.0 supertest: ^6.2.3 + suretype: ~2.4.1 tar-stream: ^1.6.2 template-file: ^6.0.1 textarea-caret: ^3.1.0 @@ -9521,13 +9538,13 @@ __metadata: linkType: hard "@rocket.chat/stylis-logical-props-middleware@npm:~0.31.26-dev.19": - version: 0.31.26-dev.19 - resolution: "@rocket.chat/stylis-logical-props-middleware@npm:0.31.26-dev.19" + version: 0.31.26-dev.23 + resolution: "@rocket.chat/stylis-logical-props-middleware@npm:0.31.26-dev.23" dependencies: - "@rocket.chat/css-supports": ~0.31.26-dev.19 + "@rocket.chat/css-supports": ~0.31.26-dev.23 peerDependencies: stylis: 4.0.10 - checksum: 893bd48b6cc320ee7a970cda019e08b00c299c51562cf74f14e925bd4f613fc0c9448de876c3aa6b651bfc060a42097ccdd5a2dee0769a9a05cfe32eaff684f3 + checksum: b2fbfad3b2f4dedd9023b30d4cdc51e76ae76faeeca5819cf697e896c02fd4bb2dde5bbc428b377d77f32011fd8cc82c6d98a84d66b93056ef981c13aee1dc67 languageName: node linkType: hard @@ -14241,7 +14258,7 @@ __metadata: languageName: node linkType: hard -"ajv-formats@npm:^2.1.1": +"ajv-formats@npm:^2.1.1, ajv-formats@npm:~2.1.1": version: 2.1.1 resolution: "ajv-formats@npm:2.1.1" dependencies: @@ -14287,7 +14304,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^6.1.0, ajv@npm:^6.10.0, ajv@npm:^6.10.2, ajv@npm:^6.12.2, ajv@npm:^6.12.3, ajv@npm:^6.12.4, ajv@npm:^6.12.5": +"ajv@npm:^6.1.0, ajv@npm:^6.10.0, ajv@npm:^6.10.2, ajv@npm:^6.11.0, ajv@npm:^6.12.2, ajv@npm:^6.12.3, ajv@npm:^6.12.4, ajv@npm:^6.12.5": version: 6.12.6 resolution: "ajv@npm:6.12.6" dependencies: @@ -15130,6 +15147,21 @@ __metadata: languageName: node linkType: hard +"awesome-ajv-errors@npm:^1.0.1": + version: 1.0.1 + resolution: "awesome-ajv-errors@npm:1.0.1" + dependencies: + chalk: ^4.1.0 + jsonpointer: ^4.1.0 + jsonpos: ^1.1.0 + leven: ^3.1.0 + terminal-link: ^2.1.1 + peerDependencies: + ajv: ^6 || ^7 + checksum: 1653f6dcebaf4913341e9ad5722aaa772bc1eddd623c11c58434d958c11bddc8f06f470c8ce6f04f269b45e296c4328455151e90cd0bb6892c6f1629753730d8 + languageName: node + linkType: hard + "aws-sdk@npm:^2.1363.0": version: 2.1363.0 resolution: "aws-sdk@npm:2.1363.0" @@ -17280,6 +17312,13 @@ __metadata: languageName: node linkType: hard +"code-error-fragment@npm:0.0.230": + version: 0.0.230 + resolution: "code-error-fragment@npm:0.0.230" + checksum: 6c5e800d6d70b30938cc85a2fc2c6069f028eadb58bceb65716b995ce6228c99906302f2c438ba50115fd81a1ee15dd95dc7d317b16a6c590e311ac7e50613f3 + languageName: node + linkType: hard + "code-point-at@npm:^1.0.0": version: 1.1.0 resolution: "code-point-at@npm:1.1.0" @@ -26432,6 +26471,16 @@ __metadata: languageName: node linkType: hard +"json-to-ast@npm:^2.1.0": + version: 2.1.0 + resolution: "json-to-ast@npm:2.1.0" + dependencies: + code-error-fragment: 0.0.230 + grapheme-splitter: ^1.0.4 + checksum: 1e9b051505b218573b39f3fec9054d75772413aefc2fee3e763d9033276664faa7eec26b945a71f70b9ce29685b2f13259df7dd3243e15eacf4672c62d5ba7ce + languageName: node + linkType: hard + "json5@npm:^0.5.0": version: 0.5.1 resolution: "json5@npm:0.5.1" @@ -26505,6 +26554,13 @@ __metadata: languageName: node linkType: hard +"jsonpointer@npm:^4.1.0": + version: 4.1.0 + resolution: "jsonpointer@npm:4.1.0" + checksum: ffc3e8937380989934676b339718d3213ecf5f6b7ce637b1ce5669a22f45dc61a86463e28abbe8c743d62f87ae790253c50cce0f586cb8e7623a21a7f811a444 + languageName: node + linkType: hard + "jsonpointer@npm:^5.0.0": version: 5.0.0 resolution: "jsonpointer@npm:5.0.0" @@ -26512,6 +26568,15 @@ __metadata: languageName: node linkType: hard +"jsonpos@npm:^1.1.0": + version: 1.1.0 + resolution: "jsonpos@npm:1.1.0" + dependencies: + json-to-ast: ^2.1.0 + checksum: 00a11fff623e74e1b14d10dcda2846e25ccdf0a12ade911fcbc8a75b82f4d33429c22dd57b6f7d2fd8a8eb07bc6435f2c3e412cb7ec09f2c8f63f19381742483 + languageName: node + linkType: hard + "jsonwebtoken@npm:^8.1.0, jsonwebtoken@npm:^8.5.1": version: 8.5.1 resolution: "jsonwebtoken@npm:8.5.1" @@ -28002,6 +28067,13 @@ __metadata: languageName: node linkType: hard +"meta-types@npm:^1.1.0": + version: 1.1.1 + resolution: "meta-types@npm:1.1.1" + checksum: 4dc31cf2eca16529ea8fc317e7d21cf8e88d85a64bc7894c8a00cf7395c1ac2d56d6655767c0a8ec02c80b8916555cf2968fad14629b06c5fefd00cb9b731b40 + languageName: node + linkType: hard + "meteor-blaze-tools@npm:^1.2.0, meteor-blaze-tools@npm:^1.2.4": version: 1.5.0 resolution: "meteor-blaze-tools@npm:1.5.0" @@ -34244,7 +34316,7 @@ __metadata: "@types/chart.js": ^2.9.37 "@types/js-yaml": ^4.0.5 husky: ^7.0.4 - turbo: ^1.10.13 + turbo: ~1.10.14 languageName: unknown linkType: soft @@ -36523,13 +36595,13 @@ __metadata: languageName: node linkType: hard -"supports-hyperlinks@npm:^2.2.0": - version: 2.2.0 - resolution: "supports-hyperlinks@npm:2.2.0" +"supports-hyperlinks@npm:^2.0.0, supports-hyperlinks@npm:^2.2.0": + version: 2.3.0 + resolution: "supports-hyperlinks@npm:2.3.0" dependencies: has-flag: ^4.0.0 supports-color: ^7.0.0 - checksum: aef04fb41f4a67f1bc128f7c3e88a81b6cf2794c800fccf137006efe5bafde281da3e42e72bf9206c2fcf42e6438f37e3a820a389214d0a88613ca1f2d36076a + checksum: 9ee0de3c8ce919d453511b2b1588a8205bd429d98af94a01df87411391010fe22ca463f268c84b2ce2abad019dfff8452aa02806eeb5c905a8d7ad5c4f4c52b8 languageName: node linkType: hard @@ -36540,6 +36612,17 @@ __metadata: languageName: node linkType: hard +"suretype@npm:~2.4.1": + version: 2.4.1 + resolution: "suretype@npm:2.4.1" + dependencies: + ajv: ^6.11.0 + awesome-ajv-errors: ^1.0.1 + meta-types: ^1.1.0 + checksum: f7562a8c1faa68e8daa3969e4488948eb72397eb5f9b4dbaed28c5ce1eb58e0701cca6352166834cde947662b2e30aeefb6f24d31904f773ee9bf65dd98810d1 + languageName: node + linkType: hard + "svg-arc-to-cubic-bezier@npm:^3.0.0, svg-arc-to-cubic-bezier@npm:^3.2.0": version: 3.2.0 resolution: "svg-arc-to-cubic-bezier@npm:3.2.0" @@ -36826,6 +36909,16 @@ __metadata: languageName: node linkType: hard +"terminal-link@npm:^2.1.1": + version: 2.1.1 + resolution: "terminal-link@npm:2.1.1" + dependencies: + ansi-escapes: ^4.2.1 + supports-hyperlinks: ^2.0.0 + checksum: ce3d2cd3a438c4a9453947aa664581519173ea40e77e2534d08c088ee6dda449eabdbe0a76d2a516b8b73c33262fedd10d5270ccf7576ae316e3db170ce6562f + languageName: node + linkType: hard + "terser-webpack-plugin@npm:^1.4.3": version: 1.4.5 resolution: "terser-webpack-plugin@npm:1.4.5" @@ -37638,58 +37731,58 @@ __metadata: languageName: node linkType: hard -"turbo-darwin-64@npm:1.10.13": - version: 1.10.13 - resolution: "turbo-darwin-64@npm:1.10.13" +"turbo-darwin-64@npm:1.10.14": + version: 1.10.14 + resolution: "turbo-darwin-64@npm:1.10.14" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"turbo-darwin-arm64@npm:1.10.13": - version: 1.10.13 - resolution: "turbo-darwin-arm64@npm:1.10.13" +"turbo-darwin-arm64@npm:1.10.14": + version: 1.10.14 + resolution: "turbo-darwin-arm64@npm:1.10.14" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"turbo-linux-64@npm:1.10.13": - version: 1.10.13 - resolution: "turbo-linux-64@npm:1.10.13" +"turbo-linux-64@npm:1.10.14": + version: 1.10.14 + resolution: "turbo-linux-64@npm:1.10.14" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"turbo-linux-arm64@npm:1.10.13": - version: 1.10.13 - resolution: "turbo-linux-arm64@npm:1.10.13" +"turbo-linux-arm64@npm:1.10.14": + version: 1.10.14 + resolution: "turbo-linux-arm64@npm:1.10.14" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"turbo-windows-64@npm:1.10.13": - version: 1.10.13 - resolution: "turbo-windows-64@npm:1.10.13" +"turbo-windows-64@npm:1.10.14": + version: 1.10.14 + resolution: "turbo-windows-64@npm:1.10.14" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"turbo-windows-arm64@npm:1.10.13": - version: 1.10.13 - resolution: "turbo-windows-arm64@npm:1.10.13" +"turbo-windows-arm64@npm:1.10.14": + version: 1.10.14 + resolution: "turbo-windows-arm64@npm:1.10.14" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"turbo@npm:^1.10.13": - version: 1.10.13 - resolution: "turbo@npm:1.10.13" +"turbo@npm:~1.10.14": + version: 1.10.14 + resolution: "turbo@npm:1.10.14" dependencies: - turbo-darwin-64: 1.10.13 - turbo-darwin-arm64: 1.10.13 - turbo-linux-64: 1.10.13 - turbo-linux-arm64: 1.10.13 - turbo-windows-64: 1.10.13 - turbo-windows-arm64: 1.10.13 + turbo-darwin-64: 1.10.14 + turbo-darwin-arm64: 1.10.14 + turbo-linux-64: 1.10.14 + turbo-linux-arm64: 1.10.14 + turbo-windows-64: 1.10.14 + turbo-windows-arm64: 1.10.14 dependenciesMeta: turbo-darwin-64: optional: true @@ -37705,7 +37798,7 @@ __metadata: optional: true bin: turbo: bin/turbo - checksum: 0c000c671534c8c80270c6d1fc77646df0e44164c0db561a85b3fefadd4bda6d5920626d067abb09af38613024e3984fb8d8bc5be922dae6236eda6aab9447a2 + checksum: 219d245bb5cc32a9f76b136b81e86e179228d93a44cab4df3e3d487a55dd2688b5b85f4d585b66568ac53166145352399dd2d7ed0cd47f1aae63d08beb814ebb languageName: node linkType: hard