From dd9b2034a8bcb0792b214fe727b60e2b83430d43 Mon Sep 17 00:00:00 2001 From: anilb Date: Mon, 5 Dec 2022 13:44:27 +0100 Subject: [PATCH 01/27] tenant update migs start --- .../database/migrations/U1670239828__tenantPlanUpdates.sql | 0 .../database/migrations/V1670239828__tenantPlanUpdates.sql | 4 ++++ 2 files changed, 4 insertions(+) create mode 100644 backend/src/database/migrations/U1670239828__tenantPlanUpdates.sql create mode 100644 backend/src/database/migrations/V1670239828__tenantPlanUpdates.sql diff --git a/backend/src/database/migrations/U1670239828__tenantPlanUpdates.sql b/backend/src/database/migrations/U1670239828__tenantPlanUpdates.sql new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/src/database/migrations/V1670239828__tenantPlanUpdates.sql b/backend/src/database/migrations/V1670239828__tenantPlanUpdates.sql new file mode 100644 index 0000000000..bc1004af85 --- /dev/null +++ b/backend/src/database/migrations/V1670239828__tenantPlanUpdates.sql @@ -0,0 +1,4 @@ +CREATE TYPE tenant_plans AS ENUM ('Essential', 'Growth'); + +alter table tenants + alter column plan type tenant_plans default 'Essential' not null \ No newline at end of file From d287208289d3ab855dcb52745946a59478dc0a9a Mon Sep 17 00:00:00 2001 From: anilb Date: Mon, 5 Dec 2022 14:18:57 +0100 Subject: [PATCH 02/27] tenant plan up migrations --- .../migrations/V1670239828__tenantPlanUpdates.sql | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/backend/src/database/migrations/V1670239828__tenantPlanUpdates.sql b/backend/src/database/migrations/V1670239828__tenantPlanUpdates.sql index bc1004af85..8099274484 100644 --- a/backend/src/database/migrations/V1670239828__tenantPlanUpdates.sql +++ b/backend/src/database/migrations/V1670239828__tenantPlanUpdates.sql @@ -1,4 +1,12 @@ -CREATE TYPE tenant_plans AS ENUM ('Essential', 'Growth'); +CREATE TYPE tenant_plans_type AS ENUM ('Essential', 'Growth'); + +ALTER TABLE tenants ALTER plan DROP DEFAULT; +ALTER TABLE tenants + ALTER COLUMN plan TYPE public.tenant_plans_type USING plan::tenant_plans_type; + +ALTER TABLE tenants ALTER column plan SET DEFAULT 'Essential'; + +ALTER TABLE tenants ADD COLUMN "isTrialPlan" BOOLEAN NOT NULL DEFAULT FALSE; + +ALTER TABLE tenants ADD COLUMN "trialEndsAt" timestamp with time zone DEFAULT null; -alter table tenants - alter column plan type tenant_plans default 'Essential' not null \ No newline at end of file From b1440b47e053ebf7b87b537ddd5a5a0fd78e6a2a Mon Sep 17 00:00:00 2001 From: anilb Date: Mon, 5 Dec 2022 17:07:01 +0100 Subject: [PATCH 03/27] plans model updated --- backend/src/database/models/tenant.ts | 13 +- .../database/repositories/tenantRepository.ts | 4 + backend/src/security/permissions.ts | 182 +++++++++--------- backend/src/security/plans.ts | 23 +-- backend/src/services/tenantService.ts | 2 +- 5 files changed, 113 insertions(+), 111 deletions(-) diff --git a/backend/src/database/models/tenant.ts b/backend/src/database/models/tenant.ts index 7443f61306..b427f70817 100644 --- a/backend/src/database/models/tenant.ts +++ b/backend/src/database/models/tenant.ts @@ -44,9 +44,9 @@ export default (sequelize, DataTypes) => { allowNull: false, validate: { notEmpty: true, - isIn: [[plans.free, plans.beta, plans.premium, plans.enterprise]], + isIn: [[plans.essential, plans.growth]], }, - defaultValue: plans.free, + defaultValue: plans.essential, }, planStatus: { type: DataTypes.STRING(255), @@ -74,6 +74,15 @@ export default (sequelize, DataTypes) => { defaultValue: false, allowNull: false, }, + isTrialPlan: { + type: DataTypes.BOOLEAN, + defaultValue: false, + allowNull: false, + }, + trialEndsAt: { + type: DataTypes.DATE, + defaultValue: null, + }, }, { indexes: [ diff --git a/backend/src/database/repositories/tenantRepository.ts b/backend/src/database/repositories/tenantRepository.ts index 0d6fac324d..b9b75da45d 100644 --- a/backend/src/database/repositories/tenantRepository.ts +++ b/backend/src/database/repositories/tenantRepository.ts @@ -1,4 +1,5 @@ import lodash from 'lodash' +import moment from 'moment' import Sequelize, { QueryTypes } from 'sequelize' import SequelizeRepository from './sequelizeRepository' import AuditLogRepository from './auditLogRepository' @@ -47,6 +48,9 @@ class TenantRepository { 'integrationsRequired', 'importHash', ]), + plan: 'Growth', + isTrialPlan: true, + trialEndsAt: moment().add(7, 'days').toISOString(), createdById: currentUser.id, updatedById: currentUser.id, }, diff --git a/backend/src/security/permissions.ts b/backend/src/security/permissions.ts index 425937912b..43d9fd732e 100644 --- a/backend/src/security/permissions.ts +++ b/backend/src/security/permissions.ts @@ -12,316 +12,316 @@ class Permissions { tenantEdit: { id: 'tenantEdit', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, tenantDestroy: { id: 'tenantDestroy', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, planEdit: { id: 'planEdit', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, planRead: { id: 'planRead', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, userEdit: { id: 'userEdit', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, userDestroy: { id: 'userDestroy', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, userCreate: { id: 'userCreate', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, userImport: { id: 'userImport', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, userRead: { id: 'userRead', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, userAutocomplete: { id: 'userAutocomplete', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, auditLogRead: { id: 'auditLogRead', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, settingsRead: { id: 'settingsRead', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [storage.settingsBackgroundImages, storage.settingsLogos], }, settingsEdit: { id: 'settingsEdit', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [storage.settingsBackgroundImages, storage.settingsLogos], }, memberAttributesRead: { id: 'memberAttributesRead', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, memberAttributesEdit: { id: 'memberAttributesEdit', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, memberAttributesDestroy: { id: 'memberAttributesDestroy', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, memberAttributesCreate: { id: 'memberAttributesCreate', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, memberImport: { id: 'memberImport', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, memberCreate: { id: 'memberCreate', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, memberEdit: { id: 'memberEdit', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, memberDestroy: { id: 'memberDestroy', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, memberRead: { id: 'memberRead', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, memberAutocomplete: { id: 'memberAutocomplete', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, activityImport: { id: 'activityImport', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, activityCreate: { id: 'activityCreate', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, activityEdit: { id: 'activityEdit', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, activityDestroy: { id: 'activityDestroy', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, activityRead: { id: 'activityRead', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, activityAutocomplete: { id: 'activityAutocomplete', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, automationCreate: { id: 'automationCreate', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, automationUpdate: { id: 'automationUpdate', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, automationDestroy: { id: 'automationDestroy', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, automationRead: { id: 'automationRead', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, tagImport: { id: 'tagImport', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, tagCreate: { id: 'tagCreate', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, tagEdit: { id: 'tagEdit', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, tagDestroy: { id: 'tagDestroy', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, tagRead: { id: 'tagRead', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, tagAutocomplete: { id: 'tagAutocomplete', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, organizationImport: { id: 'tagImport', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, organizationCreate: { id: 'tagCreate', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, organizationEdit: { id: 'tagEdit', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, organizationDestroy: { id: 'tagDestroy', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, organizationRead: { id: 'tagRead', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, organizationAutocomplete: { id: 'tagAutocomplete', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, widgetImport: { id: 'widgetImport', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, widgetCreate: { id: 'widgetCreate', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, widgetEdit: { id: 'widgetEdit', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, widgetDestroy: { id: 'widgetDestroy', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, widgetRead: { id: 'widgetRead', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, widgetAutocomplete: { id: 'widgetAutocomplete', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, reportImport: { id: 'reportImport', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, reportCreate: { id: 'reportCreate', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, reportEdit: { id: 'reportEdit', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, reportDestroy: { id: 'reportDestroy', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, reportRead: { id: 'reportRead', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, reportAutocomplete: { id: 'reportAutocomplete', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, integrationImport: { id: 'integrationImport', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, integrationControlLimit: { id: 'integrationControlLimit', @@ -331,182 +331,182 @@ class Permissions { integrationCreate: { id: 'integrationCreate', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, integrationEdit: { id: 'integrationEdit', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, integrationDestroy: { id: 'integrationDestroy', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, integrationRead: { id: 'integrationRead', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, integrationAutocomplete: { id: 'integrationAutocomplete', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, microserviceImport: { id: 'microserviceImport', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, microserviceCreate: { id: 'microserviceCreate', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, microserviceEdit: { id: 'microserviceEdit', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, microserviceDestroy: { id: 'microserviceDestroy', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, microserviceRead: { id: 'microserviceRead', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, microserviceAutocomplete: { id: 'microserviceAutocomplete', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, microserviceVariantFree: { id: 'microserviceVariantFree', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, microserviceVariantPremium: { id: 'microserviceVariantPremium', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.growth], }, conversationCreate: { id: 'conversationCreate', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, conversationEdit: { id: 'conversationEdit', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, conversationDestroy: { id: 'conversationDestroy', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, conversationRead: { id: 'conversationRead', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, eagleEyeContentRead: { id: 'eagleEyeContentRead', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, eagleEyeContentSearch: { id: 'eagleEyeContentSearch', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, eagleEyeContentEdit: { id: 'eagleEyeContentEdit', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, taskImport: { id: 'taskImport', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, taskCreate: { id: 'taskCreate', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, taskEdit: { id: 'taskEdit', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, taskDestroy: { id: 'taskDestroy', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, taskRead: { id: 'taskRead', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, taskAutocomplete: { id: 'taskAutocomplete', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, taskBatch: { id: 'taskBatch', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, noteImport: { id: 'noteImport', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, noteCreate: { id: 'noteCreate', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, noteEdit: { id: 'noteEdit', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, noteDestroy: { id: 'noteDestroy', allowedRoles: [roles.admin], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], allowedStorage: [], }, noteRead: { id: 'noteRead', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, noteAutocomplete: { id: 'noteAutocomplete', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.free, plans.beta, plans.premium, plans.enterprise], + allowedPlans: [plans.essential, plans.growth], }, } } diff --git a/backend/src/security/plans.ts b/backend/src/security/plans.ts index 2f77d76be4..ed21ea7745 100644 --- a/backend/src/security/plans.ts +++ b/backend/src/security/plans.ts @@ -3,37 +3,26 @@ import { PLANS_CONFIG } from '../config' class Plans { static get values() { return { - free: 'free', - beta: 'beta', - premium: 'premium', - enterprise: 'enterprise', + essential: 'Essential', + growth: 'Growth', } } static selectPlanByStripePriceId(stripePriceId) { const premiumStripePriceId = PLANS_CONFIG.stripePricePremium - const enterpriseStripePriceId = PLANS_CONFIG.stripePriceEnterprise if (premiumStripePriceId === stripePriceId) { - return Plans.values.premium + return Plans.values.growth } - if (enterpriseStripePriceId === stripePriceId) { - return Plans.values.enterprise - } - - return Plans.values.free + return Plans.values.essential } static selectStripePriceIdByPlan(plan) { - if (plan === Plans.values.premium) { + if (plan === Plans.values.growth) { return PLANS_CONFIG.stripePricePremium } - if (plan === Plans.values.enterprise) { - return PLANS_CONFIG.stripePriceEnterprise - } - return null } @@ -72,7 +61,7 @@ class Plans { * because future charges might occur */ static allowTenantDestroy(plan, planStatus) { - if (plan === Plans.values.free || plan === Plans.values.beta) { + if (plan === Plans.values.essential || plan === Plans.values.growth) { return true } diff --git a/backend/src/services/tenantService.ts b/backend/src/services/tenantService.ts index 27fc2172aa..245fa572a2 100644 --- a/backend/src/services/tenantService.ts +++ b/backend/src/services/tenantService.ts @@ -321,7 +321,7 @@ export default class TenantService { } async updatePlanToFree(planStripeCustomerId) { - return this.updatePlanStatus(planStripeCustomerId, Plans.values.free, 'active') + return this.updatePlanStatus(planStripeCustomerId, Plans.values.essential, 'active') } async updatePlanStatus(planStripeCustomerId, plan, planStatus) { From 654f198d54d9fb9b56b2c22b07a00ee5872499ae Mon Sep 17 00:00:00 2001 From: anilb Date: Tue, 6 Dec 2022 16:45:58 +0100 Subject: [PATCH 04/27] feature flagging for eagle eye, chc pro and automations --- .../config/custom-environment-variables.json | 251 ++-- backend/config/default.json | 71 +- backend/package-lock.json | 20 + backend/package.json | 257 ++-- .../src/api/automation/automationCreate.ts | 12 + .../src/api/automation/automationDestroy.ts | 2 + .../conversationSettingsUpdate.ts | 7 + backend/src/api/index.ts | 10 + backend/src/api/tenant/tenantCreate.ts | 2 +- backend/src/api/tenant/tenantFind.ts | 2 +- backend/src/bin/scripts/change-tenant-plan.ts | 96 ++ backend/src/config/configTypes.ts | 4 + backend/src/config/index.ts | 7 + backend/src/feature-flags/isFeatureEnabled.ts | 13 + .../src/feature-flags/setTenantProperties.ts | 24 + backend/src/segment/identifyTenant.ts | 11 +- .../src/services/user/permissionChecker.ts | 2 +- frontend/src/security/permissions.js | 1218 ++++++++--------- frontend/src/security/plans.js | 14 +- 19 files changed, 1114 insertions(+), 909 deletions(-) create mode 100644 backend/src/bin/scripts/change-tenant-plan.ts create mode 100644 backend/src/feature-flags/isFeatureEnabled.ts create mode 100644 backend/src/feature-flags/setTenantProperties.ts diff --git a/backend/config/custom-environment-variables.json b/backend/config/custom-environment-variables.json index da025fc13a..49d721b5fe 100644 --- a/backend/config/custom-environment-variables.json +++ b/backend/config/custom-environment-variables.json @@ -1,126 +1,129 @@ { - "api": { - "url": "CROWD_API_URL", - "edition": "CROWD_EDITION", - "frontendUrl": "CROWD_API_FRONTEND_URL", - "frontendUrlWithSubdomain": "CROWD_API_FRONTEND_URL_WITH_SUBDOMAINS", - "jwtSecret": "CROWD_API_JWT_SECRET", - "jwtExpiresIn": "CROWD_API_JWT_EXPIRES_IN", - "premiumApiUrl": "CROWD_PREMIUM_API_URL" - }, - "redis": { - "username": "CROWD_REDIS_USERNAME", - "password": "CROWD_REDIS_PASSWORD", - "host": "CROWD_REDIS_HOST", - "port": "CROWD_REDIS_PORT" - }, - "sqs": { - "host": "CROWD_SQS_HOST", - "port": "CROWD_SQS_PORT", - "nodejsWorkerQueue": "CROWD_SQS_NODEJS_WORKER_QUEUE", - "nodejsWorkerDelayableQueue": "CROWD_SQS_NODEJS_WORKER_DELAYABLE_QUEUE", - "pythonWorkerQueue": "CROWD_SQS_PYTHON_WORKER_QUEUE", - "premiumPythonWorkerQueue": "CROWD_SQS_PREMIUM_PYTHON_WORKER_QUEUE", - "aws": { - "accountId": "CROWD_SQS_AWS_ACCOUNT_ID", - "accessKeyId": "CROWD_SQS_AWS_ACCESS_KEY_ID", - "secretAccessKey": "CROWD_SQS_AWS_SECRET_ACCESS_KEY", - "region": "CROWD_SQS_AWS_REGION" + "api": { + "url": "CROWD_API_URL", + "edition": "CROWD_EDITION", + "frontendUrl": "CROWD_API_FRONTEND_URL", + "frontendUrlWithSubdomain": "CROWD_API_FRONTEND_URL_WITH_SUBDOMAINS", + "jwtSecret": "CROWD_API_JWT_SECRET", + "jwtExpiresIn": "CROWD_API_JWT_EXPIRES_IN", + "premiumApiUrl": "CROWD_PREMIUM_API_URL" + }, + "redis": { + "username": "CROWD_REDIS_USERNAME", + "password": "CROWD_REDIS_PASSWORD", + "host": "CROWD_REDIS_HOST", + "port": "CROWD_REDIS_PORT" + }, + "sqs": { + "host": "CROWD_SQS_HOST", + "port": "CROWD_SQS_PORT", + "nodejsWorkerQueue": "CROWD_SQS_NODEJS_WORKER_QUEUE", + "nodejsWorkerDelayableQueue": "CROWD_SQS_NODEJS_WORKER_DELAYABLE_QUEUE", + "pythonWorkerQueue": "CROWD_SQS_PYTHON_WORKER_QUEUE", + "premiumPythonWorkerQueue": "CROWD_SQS_PREMIUM_PYTHON_WORKER_QUEUE", + "aws": { + "accountId": "CROWD_SQS_AWS_ACCOUNT_ID", + "accessKeyId": "CROWD_SQS_AWS_ACCESS_KEY_ID", + "secretAccessKey": "CROWD_SQS_AWS_SECRET_ACCESS_KEY", + "region": "CROWD_SQS_AWS_REGION" + } + }, + "s3": { + "host": "CROWD_S3_HOST", + "port": "CROWD_S3_PORT", + "integrationsAssetsBucket": "CROWD_S3_INTEGRATION_ASSETS_BUCKET", + "microservicesAssetsBucket": "CROWD_S3_MICROSERVICES_ASSETS_BUCKET", + "aws": { + "accountId": "CROWD_S3_AWS_ACCOUNT_ID", + "accessKeyId": "CROWD_S3_AWS_ACCESS_KEY_ID", + "secretAccessKey": "CROWD_S3_AWS_SECRET_ACCESS_KEY", + "region": "CROWD_S3_AWS_REGION" + } + }, + "db": { + "readHost": "CROWD_DB_READ_HOST", + "writeHost": "CROWD_DB_WRITE_HOST", + "port": "CROWD_DB_PORT", + "username": "CROWD_DB_USERNAME", + "password": "CROWD_DB_PASSWORD", + "apiUsername": "CROWD_DB_API_USERNAME", + "apiPassword": "CROWD_DB_API_PASSWORD", + "nodejsWorkerUsername": "CROWD_DB_NODEJS_WORKER_USERNAME", + "nodejsWorkerPassword": "CROWD_DB_NODEJS_WORKER_PASSWORD", + "jobGeneratorUsername": "CROWD_DB_JOB_GENERATOR_USERNAME", + "jobGeneratorPassword": "CROWD_DB_JOB_GENERATOR_PASSWORD", + "database": "CROWD_DB_DATABASE" + }, + "cubejs": { + "url": "CROWD_CUBEJS_URL", + "jwtSecret": "CROWD_CUBEJS_JWT_SECRET", + "jwtExpiry": "CROWD_CUBEJS_JWT_EXPIRY" + }, + "searchEngine": { + "host": "CROWD_SEARCH_ENGINE_HOST", + "apiKey": "CROWD_SEARCH_ENGINE_API_KEY" + }, + "segment": { + "writeKey": "CROWD_SEGMENT_WRITE_KEY" + }, + "posthog": { + "apiKey": "CROWD_POSTHOG_API_KEY" + }, + "comprehend": { + "aws": { + "accountId": "CROWD_COMPREHEND_AWS_ACCOUNT_ID", + "accessKeyId": "CROWD_COMPREHEND_AWS_ACCESS_KEY_ID", + "secretAccessKey": "CROWD_COMPREHEND_AWS_SECRET_ACCESS_KEY", + "region": "CROWD_COMPREHEND_AWS_REGION" + } + }, + "clearbit": { + "apiKey": "CROWD_CLEARBIT_API_KEY" + }, + "netlify": { + "apiKey": "CROWD_NETLIFY_API_KEY", + "siteDomain": "CROWD_NETLIFY_SITE_DOMAIN" + }, + "sendgrid": { + "key": "CROWD_SENDGRID_KEY", + "emailFrom": "CROWD_SENDGRID_EMAIL_FROM", + "nameFrom": "CROWD_SENDGRID_NAME_FROM", + "templateEmailAddressVerification": "CROWD_SENDGRID_TEMPLATE_EMAIL_ADDRESS_VERIFICATION", + "templateInvitation": "CROWD_SENDGRID_TEMPLATE_INVITATION", + "templatePasswordReset": "CROWD_SENDGRID_TEMPLATE_PASSWORD_RESET", + "templateWeeklyAnalytics": "CROWD_SENDGRID_TEMPLATE_WEEKLY_ANALYTICS", + "weeklyAnalyticsUnsubscribeGroupId": "CROWD_SENDGRID_WEEKLY_ANALYTICS_UNSUBSCRIBE_GROUP_ID" + }, + "plans": { + "stripePricePremium": "CROWD_STRIPE_PRICE_PREMIUM", + "stripePriceEnterprise": "CROWD_STRIPE_PRICE_ENTERPRISE", + "stripeSecretKey": "CROWD_STRIPE_SECRET_KEY", + "stripWebhookSigningSecret": "CROWD_STRIPE_WEBHOOK_SIGNING_SECRET" + }, + "twitter": { + "clientId": "CROWD_TWITTER_CLIENT_ID", + "clientSecret": "CROWD_TWITTER_CLIENT_SECRET" + }, + "slack": { + "clientId": "CROWD_SLACK_CLIENT_ID", + "clientSecret": "CROWD_SLACK_CLIENT_SECRET", + "reporterToken": "CROWD_SLACK_REPORTER_TOKEN", + "reporterChannel": "CROWD_SLACK_REPORTER_CHANNEL" + }, + "google": { + "clientId": "CROWD_GOOGLE_CLIENT_ID", + "clientSecret": "CROWD_GOOGLE_CLIENT_SECRET", + "callbackUrl": "CROWD_GOOGLE_CALLBACK_URL" + }, + "discord": { + "token": "CROWD_DISCORD_TOKEN", + "token2": "CROWD_DISCORD_TOKEN_2" + }, + "github": { + "appId": "CROWD_GITHUB_APP_ID", + "clientId": "CROWD_GITHUB_CLIENT_ID", + "clientSecret": "CROWD_GITHUB_CLIENT_SECRET", + "privateKey": "CROWD_GITHUB_PRIVATE_KEY", + "webhookSecret": "CROWD_GITHUB_WEBHOOK_SECRET" } - }, - "s3": { - "host": "CROWD_S3_HOST", - "port": "CROWD_S3_PORT", - "integrationsAssetsBucket": "CROWD_S3_INTEGRATION_ASSETS_BUCKET", - "microservicesAssetsBucket": "CROWD_S3_MICROSERVICES_ASSETS_BUCKET", - "aws": { - "accountId": "CROWD_S3_AWS_ACCOUNT_ID", - "accessKeyId": "CROWD_S3_AWS_ACCESS_KEY_ID", - "secretAccessKey": "CROWD_S3_AWS_SECRET_ACCESS_KEY", - "region": "CROWD_S3_AWS_REGION" - } - }, - "db": { - "readHost": "CROWD_DB_READ_HOST", - "writeHost": "CROWD_DB_WRITE_HOST", - "port": "CROWD_DB_PORT", - "username": "CROWD_DB_USERNAME", - "password": "CROWD_DB_PASSWORD", - "apiUsername": "CROWD_DB_API_USERNAME", - "apiPassword": "CROWD_DB_API_PASSWORD", - "nodejsWorkerUsername": "CROWD_DB_NODEJS_WORKER_USERNAME", - "nodejsWorkerPassword": "CROWD_DB_NODEJS_WORKER_PASSWORD", - "jobGeneratorUsername": "CROWD_DB_JOB_GENERATOR_USERNAME", - "jobGeneratorPassword": "CROWD_DB_JOB_GENERATOR_PASSWORD", - "database": "CROWD_DB_DATABASE" - }, - "cubejs": { - "url": "CROWD_CUBEJS_URL", - "jwtSecret": "CROWD_CUBEJS_JWT_SECRET", - "jwtExpiry": "CROWD_CUBEJS_JWT_EXPIRY" - }, - "searchEngine": { - "host": "CROWD_SEARCH_ENGINE_HOST", - "apiKey": "CROWD_SEARCH_ENGINE_API_KEY" - }, - "segment": { - "writeKey": "CROWD_SEGMENT_WRITE_KEY" - }, - "comprehend": { - "aws": { - "accountId": "CROWD_COMPREHEND_AWS_ACCOUNT_ID", - "accessKeyId": "CROWD_COMPREHEND_AWS_ACCESS_KEY_ID", - "secretAccessKey": "CROWD_COMPREHEND_AWS_SECRET_ACCESS_KEY", - "region": "CROWD_COMPREHEND_AWS_REGION" - } - }, - "clearbit": { - "apiKey": "CROWD_CLEARBIT_API_KEY" - }, - "netlify": { - "apiKey": "CROWD_NETLIFY_API_KEY", - "siteDomain": "CROWD_NETLIFY_SITE_DOMAIN" - }, - "sendgrid": { - "key": "CROWD_SENDGRID_KEY", - "emailFrom": "CROWD_SENDGRID_EMAIL_FROM", - "nameFrom": "CROWD_SENDGRID_NAME_FROM", - "templateEmailAddressVerification": "CROWD_SENDGRID_TEMPLATE_EMAIL_ADDRESS_VERIFICATION", - "templateInvitation": "CROWD_SENDGRID_TEMPLATE_INVITATION", - "templatePasswordReset": "CROWD_SENDGRID_TEMPLATE_PASSWORD_RESET", - "templateWeeklyAnalytics": "CROWD_SENDGRID_TEMPLATE_WEEKLY_ANALYTICS", - "weeklyAnalyticsUnsubscribeGroupId": "CROWD_SENDGRID_WEEKLY_ANALYTICS_UNSUBSCRIBE_GROUP_ID" - }, - "plans": { - "stripePricePremium": "CROWD_STRIPE_PRICE_PREMIUM", - "stripePriceEnterprise": "CROWD_STRIPE_PRICE_ENTERPRISE", - "stripeSecretKey": "CROWD_STRIPE_SECRET_KEY", - "stripWebhookSigningSecret": "CROWD_STRIPE_WEBHOOK_SIGNING_SECRET" - }, - "twitter": { - "clientId": "CROWD_TWITTER_CLIENT_ID", - "clientSecret": "CROWD_TWITTER_CLIENT_SECRET" - }, - "slack": { - "clientId": "CROWD_SLACK_CLIENT_ID", - "clientSecret": "CROWD_SLACK_CLIENT_SECRET", - "reporterToken": "CROWD_SLACK_REPORTER_TOKEN", - "reporterChannel": "CROWD_SLACK_REPORTER_CHANNEL" - }, - "google": { - "clientId": "CROWD_GOOGLE_CLIENT_ID", - "clientSecret": "CROWD_GOOGLE_CLIENT_SECRET", - "callbackUrl": "CROWD_GOOGLE_CALLBACK_URL" - }, - "discord": { - "token": "CROWD_DISCORD_TOKEN", - "token2": "CROWD_DISCORD_TOKEN_2" - }, - "github": { - "appId": "CROWD_GITHUB_APP_ID", - "clientId": "CROWD_GITHUB_CLIENT_ID", - "clientSecret": "CROWD_GITHUB_CLIENT_SECRET", - "privateKey": "CROWD_GITHUB_PRIVATE_KEY", - "webhookSecret": "CROWD_GITHUB_WEBHOOK_SECRET" - } -} +} \ No newline at end of file diff --git a/backend/config/default.json b/backend/config/default.json index 75ce0c192d..25e20c144d 100644 --- a/backend/config/default.json +++ b/backend/config/default.json @@ -1,36 +1,37 @@ { - "api": { - "port": 8080, - "documentation": false - }, - "sqs": {}, - "s3": {}, - "db": { - "dialect": "postgres", - "logging": false, - "transactions": false - }, - "cubejs": {}, - "searchEngine": {}, - "segment": {}, - "comprehend": { - "aws": {} - }, - "clearbit": {}, - "netlify": {}, - "sendgrid": {}, - "plans": {}, - "devto": {}, - "twitter": { - "maxRetrospectInSeconds": 7380, - "limitResetFrequencyDays": 30 - }, - "slack": { - "maxRetrospectInSeconds": 3600 - }, - "google": {}, - "discord": { - "maxRetrospectInSeconds": 3600 - }, - "github": {} -} + "api": { + "port": 8080, + "documentation": false + }, + "sqs": {}, + "s3": {}, + "db": { + "dialect": "postgres", + "logging": false, + "transactions": false + }, + "cubejs": {}, + "searchEngine": {}, + "segment": {}, + "posthog": {}, + "comprehend": { + "aws": {} + }, + "clearbit": {}, + "netlify": {}, + "sendgrid": {}, + "plans": {}, + "devto": {}, + "twitter": { + "maxRetrospectInSeconds": 7380, + "limitResetFrequencyDays": 30 + }, + "slack": { + "maxRetrospectInSeconds": 3600 + }, + "google": {}, + "discord": { + "maxRetrospectInSeconds": 3600 + }, + "github": {} +} \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index 9e50b94f99..f028f061cc 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -57,6 +57,7 @@ "passport-slack": "0.0.7", "pg": "^8.7.3", "pm2": "^5.2.0", + "posthog-node": "^2.2.3", "redis": "^4.5.0", "sanitize-html": "^2.7.1", "sequelize": "6.21.2", @@ -16406,6 +16407,17 @@ "node": ">=0.10.0" } }, + "node_modules/posthog-node": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-2.2.3.tgz", + "integrity": "sha512-dYlLZhrDus+uRov/Hh+EiRlMoMhRKchNjNa7mNE2iWmKg/ryOTipf0XYKS9UKdki7aU1NzWFhnLe11HF615XuA==", + "dependencies": { + "axios": "^0.27.0" + }, + "engines": { + "node": ">=14.17.0" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -33207,6 +33219,14 @@ "xtend": "^4.0.0" } }, + "posthog-node": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-2.2.3.tgz", + "integrity": "sha512-dYlLZhrDus+uRov/Hh+EiRlMoMhRKchNjNa7mNE2iWmKg/ryOTipf0XYKS9UKdki7aU1NzWFhnLe11HF615XuA==", + "requires": { + "axios": "^0.27.0" + } + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", diff --git a/backend/package.json b/backend/package.json index 0128335061..aa418e0c71 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,128 +1,131 @@ { - "name": "app-backend", - "description": "Backend", - "scripts": { - "start:api": "ts-node --transpile-only ./src/bin/api.ts", - "start:api:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/api.ts\"", - "start:api:dev:local": "set -a && . ./.env.dist.local && . ./.env.override.local && set +a && SERVICE=api nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/api.ts\"", - "start:job-generator": "ts-node --transpile-only ./src/bin/job-generator.ts", - "start:job-generator:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/job-generator.ts\"", - "start:job-generator:dev:local": "set -a && . ./.env.dist.local && . ./.env.override.local && set +a && SERVICE=job-generator nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/job-generator.ts\"", - "start:nodejs-worker": "ts-node --transpile-only ./src/bin/nodejs-worker.ts", - "start:nodejs-worker:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/nodejs-worker.ts\"", - "start:nodejs-worker:dev:local": "set -a && . ./.env.dist.local && . ./.env.override.local && set +a && SERVICE=nodejs-worker nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"node --inspect=0.0.0.0:9229 -r ts-node/register ./src/bin/nodejs-worker.ts --transpile-only\"", - "build": "tsc && npm run build:documentation && cp package*json dist/ && cp .sequelizerc dist/.sequelizerc ", - "test": "../scripts/cli scaffold up-test && jest --clearCache && set -a && . ./.env.dist.local && . ./.env.test && set +a && NODE_ENV=test SERVICE=test jest --runInBand --verbose --forceExit", - "build:documentation": "copyfiles --flat ./src/documentation/openapi.json ./dist/documentation/", - "db:create:test": "npx ts-node ./src/database/initializers/create test", - "db:create:dev:source": "ts-node ./src/database/initializers/create dev", - "db:seed:test": "npx ts-node ./src/database/initializers/seed test", - "db:seed:dev": "npx ts-node ./src/database/initializers/seed dev", - "db:publish": "bash ./util/publish-db.sh", - "docs": "bash ./util/publish-docs.sh", - "sequelize-cli:source": "npm run build && npx sequelize --config src/database/sequelize-cli-config.ts --migrations-source-path src/database/migrations", - "sequelize-cli:build": "npx sequelize --config database/sequelize-cli-config.js --migrations-compiled-path database/migrations", - "stripe:login": "stripe login", - "stripe:start": "stripe listen --forward-to localhost:8080/api/plan/stripe/webhook", - "lint": "npx eslint .", - "format": "npx prettier --write .", - "script:process-integration": "SERVICE=script ts-node ./src/bin/scripts/process-integration.ts" - }, - "dependencies": { - "@aws-sdk/client-comprehend": "^3.159.0", - "@cubejs-client/core": "^0.30.4", - "@google-cloud/storage": "5.3.0", - "@octokit/auth-app": "^3.6.1", - "@octokit/graphql": "^4.8.0", - "@octokit/request": "^5.6.3", - "@pm2/io": "^5.0.0", - "@sendgrid/mail": "7.2.6", - "@slack/web-api": "^6.7.2", - "@superfaceai/one-sdk": "^1.3.0", - "@superfaceai/passport-twitter-oauth2": "^1.0.0", - "analytics-node": "^6.2.0", - "aws-sdk": "2.814.0", - "axios": "^0.27.2", - "bcrypt": "5.0.0", - "bunyan": "^1.8.15", - "bunyan-format": "^0.2.1", - "bunyan-middleware": "^1.0.2", - "clearbit": "^1.3.5", - "cli-highlight": "2.1.6", - "command-line-args": "^5.2.1", - "command-line-usage": "^6.1.3", - "config": "^3.3.8", - "cors": "2.8.5", - "cron": "^2.1.0", - "cron-time-generator": "^1.3.0", - "crypto-js": "^4.1.1", - "dotenv": "8.2.0", - "dotenv-expand": "^8.0.3", - "emoji-dictionary": "^1.0.11", - "express": "4.17.1", - "express-rate-limit": "6.5.1", - "formidable-serverless": "1.1.1", - "helmet": "4.1.1", - "html-to-text": "^8.2.1", - "jsonwebtoken": "8.5.1", - "lodash": "4.17.21", - "meilisearch": "^0.26.0", - "moment": "2.29.4", - "moment-timezone": "^0.5.34", - "mv": "2.1.1", - "node-fetch": "^2.6.7", - "omit-deep-by-values": "^1.0.2", - "openapi-comment-parser": "^1.0.0", - "passport": "0.6.0", - "passport-facebook": "3.0.0", - "passport-google-oauth": "2.0.0", - "passport-google-oauth20": "^2.0.0", - "passport-slack": "0.0.7", - "pg": "^8.7.3", - "pm2": "^5.2.0", - "redis": "^4.5.0", - "sanitize-html": "^2.7.1", - "sequelize": "6.21.2", - "sequelize-cli-typescript": "^3.2.0-c", - "stripe": "10.0.0", - "superagent": "^8.0.0", - "swagger-ui-dist": "4.1.3", - "uuid": "^8.3.2", - "validator": "^13.7.0", - "verify-github-webhook": "^1.0.1" - }, - "private": true, - "devDependencies": { - "@babel/plugin-transform-runtime": "^7.18.10", - "@babel/preset-env": "^7.16.10", - "@types/bunyan": "^1.8.8", - "@types/bunyan-format": "^0.2.5", - "@types/config": "^3.3.0", - "@types/cron": "^2.0.0", - "@types/html-to-text": "^8.1.1", - "@types/jest": "^27.4.0", - "@types/node": "^17.0.21", - "@types/sanitize-html": "^2.6.2", - "@types/superagent": "^4.1.15", - "@typescript-eslint/eslint-plugin": "^5.17.0", - "@typescript-eslint/parser": "^5.17.0", - "copyfiles": "2.4.1", - "cross-env": "7.0.2", - "eslint": "^8.12.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-config-airbnb-typescript": "^16.1.4", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-import": "^2.25.4", - "eslint-plugin-openapi": "^0.0.4", - "jest": "^27.4.7", - "mocha": "8.1.3", - "node-mocks-http": "1.9.0", - "nodemon": "2.0.4", - "prettier": "^2.5.1", - "rdme": "^7.2.0", - "supertest": "^6.2.2", - "ts-jest": "^27.1.3", - "ts-node": "10.6.0", - "typescript": "^4.7.4" - } -} + "name": "app-backend", + "description": "Backend", + "scripts": { + "start:api": "ts-node --transpile-only ./src/bin/api.ts", + "start:api:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/api.ts\"", + "start:api:dev:local": "set -a && . ./.env.dist.local && . ./.env.override.local && set +a && SERVICE=api nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/api.ts\"", + "start:job-generator": "ts-node --transpile-only ./src/bin/job-generator.ts", + "start:job-generator:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/job-generator.ts\"", + "start:job-generator:dev:local": "set -a && . ./.env.dist.local && . ./.env.override.local && set +a && SERVICE=job-generator nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/job-generator.ts\"", + "start:nodejs-worker": "ts-node --transpile-only ./src/bin/nodejs-worker.ts", + "start:nodejs-worker:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/nodejs-worker.ts\"", + "start:nodejs-worker:dev:local": " && SERVICE=nodejs-worker nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"node --inspect=0.0.0.0:9229 -r ts-node/register ./src/bin/nodejs-worker.ts --transpile-only\"", + "build": "tsc && npm run build:documentation && cp package*json dist/ && cp .sequelizerc dist/.sequelizerc ", + "test": "../scripts/cli scaffold up-test && jest --clearCache && set -a && . ./.env.dist.local && . ./.env.test && set +a && NODE_ENV=test SERVICE=test jest --runInBand --verbose --forceExit", + "build:documentation": "copyfiles --flat ./src/documentation/openapi.json ./dist/documentation/", + "db:create:test": "npx ts-node ./src/database/initializers/create test", + "db:create:dev:source": "ts-node ./src/database/initializers/create dev", + "db:seed:test": "npx ts-node ./src/database/initializers/seed test", + "db:seed:dev": "npx ts-node ./src/database/initializers/seed dev", + "db:publish": "bash ./util/publish-db.sh", + "docs": "bash ./util/publish-docs.sh", + "sequelize-cli:source": "npm run build && npx sequelize --config src/database/sequelize-cli-config.ts --migrations-source-path src/database/migrations", + "sequelize-cli:build": "npx sequelize --config database/sequelize-cli-config.js --migrations-compiled-path database/migrations", + "stripe:login": "stripe login", + "stripe:start": "stripe listen --forward-to localhost:8080/api/plan/stripe/webhook", + "lint": "npx eslint .", + "format": "npx prettier --write .", + "script:process-integration": "SERVICE=script ts-node ./src/bin/scripts/process-integration.ts", + "script:change-tenant-plan": "SERVICE=script ts-node ./src/bin/scripts/change-tenant-plan.ts" + + }, + "dependencies": { + "@aws-sdk/client-comprehend": "^3.159.0", + "@cubejs-client/core": "^0.30.4", + "@google-cloud/storage": "5.3.0", + "@octokit/auth-app": "^3.6.1", + "@octokit/graphql": "^4.8.0", + "@octokit/request": "^5.6.3", + "@pm2/io": "^5.0.0", + "@sendgrid/mail": "7.2.6", + "@slack/web-api": "^6.7.2", + "@superfaceai/one-sdk": "^1.3.0", + "@superfaceai/passport-twitter-oauth2": "^1.0.0", + "analytics-node": "^6.2.0", + "aws-sdk": "2.814.0", + "axios": "^0.27.2", + "bcrypt": "5.0.0", + "bunyan": "^1.8.15", + "bunyan-format": "^0.2.1", + "bunyan-middleware": "^1.0.2", + "clearbit": "^1.3.5", + "cli-highlight": "2.1.6", + "command-line-args": "^5.2.1", + "command-line-usage": "^6.1.3", + "config": "^3.3.8", + "cors": "2.8.5", + "cron": "^2.1.0", + "cron-time-generator": "^1.3.0", + "crypto-js": "^4.1.1", + "dotenv": "8.2.0", + "dotenv-expand": "^8.0.3", + "emoji-dictionary": "^1.0.11", + "express": "4.17.1", + "express-rate-limit": "6.5.1", + "formidable-serverless": "1.1.1", + "helmet": "4.1.1", + "html-to-text": "^8.2.1", + "jsonwebtoken": "8.5.1", + "lodash": "4.17.21", + "meilisearch": "^0.26.0", + "moment": "2.29.4", + "moment-timezone": "^0.5.34", + "mv": "2.1.1", + "node-fetch": "^2.6.7", + "omit-deep-by-values": "^1.0.2", + "openapi-comment-parser": "^1.0.0", + "passport": "0.6.0", + "passport-facebook": "3.0.0", + "passport-google-oauth": "2.0.0", + "passport-google-oauth20": "^2.0.0", + "passport-slack": "0.0.7", + "pg": "^8.7.3", + "pm2": "^5.2.0", + "posthog-node": "^2.2.3", + "redis": "^4.5.0", + "sanitize-html": "^2.7.1", + "sequelize": "6.21.2", + "sequelize-cli-typescript": "^3.2.0-c", + "stripe": "10.0.0", + "superagent": "^8.0.0", + "swagger-ui-dist": "4.1.3", + "uuid": "^8.3.2", + "validator": "^13.7.0", + "verify-github-webhook": "^1.0.1" + }, + "private": true, + "devDependencies": { + "@babel/plugin-transform-runtime": "^7.18.10", + "@babel/preset-env": "^7.16.10", + "@types/bunyan": "^1.8.8", + "@types/bunyan-format": "^0.2.5", + "@types/config": "^3.3.0", + "@types/cron": "^2.0.0", + "@types/html-to-text": "^8.1.1", + "@types/jest": "^27.4.0", + "@types/node": "^17.0.21", + "@types/sanitize-html": "^2.6.2", + "@types/superagent": "^4.1.15", + "@typescript-eslint/eslint-plugin": "^5.17.0", + "@typescript-eslint/parser": "^5.17.0", + "copyfiles": "2.4.1", + "cross-env": "7.0.2", + "eslint": "^8.12.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^16.1.4", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-openapi": "^0.0.4", + "jest": "^27.4.7", + "mocha": "8.1.3", + "node-mocks-http": "1.9.0", + "nodemon": "2.0.4", + "prettier": "^2.5.1", + "rdme": "^7.2.0", + "supertest": "^6.2.2", + "ts-jest": "^27.1.3", + "ts-node": "10.6.0", + "typescript": "^4.7.4" + } +} \ No newline at end of file diff --git a/backend/src/api/automation/automationCreate.ts b/backend/src/api/automation/automationCreate.ts index 5557a0cffb..87d1f9f897 100644 --- a/backend/src/api/automation/automationCreate.ts +++ b/backend/src/api/automation/automationCreate.ts @@ -2,6 +2,8 @@ import PermissionChecker from '../../services/user/permissionChecker' import Permissions from '../../security/permissions' import AutomationService from '../../services/automationService' import track from '../../segment/track' +import identifyTenant from '../../segment/identifyTenant' +import isFeatureEnabled from '../../feature-flags/isFeatureEnabled' /** * POST /tenant/{tenantId}/automation @@ -19,9 +21,19 @@ import track from '../../segment/track' */ export default async (req, res) => { new PermissionChecker(req).validateHas(Permissions.values.automationCreate) + + if (!await isFeatureEnabled('automations', req.currentTenant.id, req.posthog)) { + await req.responseHandler.success(req, res, { + message: 'You have exceeded # of automations you can have in your plan' + }) + return + } + const payload = await new AutomationService(req).create(req.body.data) track('Automation Created', { ...payload }, { ...req }) + identifyTenant(req) + await req.responseHandler.success(req, res, payload) } diff --git a/backend/src/api/automation/automationDestroy.ts b/backend/src/api/automation/automationDestroy.ts index b7fbd2a683..16f9271cc4 100644 --- a/backend/src/api/automation/automationDestroy.ts +++ b/backend/src/api/automation/automationDestroy.ts @@ -2,6 +2,7 @@ import PermissionChecker from '../../services/user/permissionChecker' import Permissions from '../../security/permissions' import AutomationService from '../../services/automationService' import track from '../../segment/track' +import identifyTenant from '../../segment/identifyTenant' /** * DELETE /tenant/{tenantId}/automation/{automationId} @@ -20,6 +21,7 @@ export default async (req, res) => { await new AutomationService(req).destroy(req.params.automationId) track('Automation Destroyed', { id: req.params.automationId }, { ...req }) + identifyTenant(req) await req.responseHandler.success(req, res, true, 204) } diff --git a/backend/src/api/conversation/conversationSettingsUpdate.ts b/backend/src/api/conversation/conversationSettingsUpdate.ts index 9739f61512..9c5d2f777f 100644 --- a/backend/src/api/conversation/conversationSettingsUpdate.ts +++ b/backend/src/api/conversation/conversationSettingsUpdate.ts @@ -1,3 +1,4 @@ +import isFeatureEnabled from '../../feature-flags/isFeatureEnabled' import Permissions from '../../security/permissions' import ConversationService from '../../services/conversationService' import PermissionChecker from '../../services/user/permissionChecker' @@ -5,6 +6,12 @@ import PermissionChecker from '../../services/user/permissionChecker' export default async (req, res) => { new PermissionChecker(req).validateHas(Permissions.values.conversationEdit) + if(req.body.customUrl && !await isFeatureEnabled('community-help-center-pro', req.currentTenant.id, req.posthog)){ + await req.responseHandler.success(req, res, { + message: `Your plan (${req.currentTenant.plan}) doesn't include custom urls.` + }) + } + const payload = await new ConversationService(req).updateSettings(req.body) await req.responseHandler.success(req, res, payload) diff --git a/backend/src/api/index.ts b/backend/src/api/index.ts index 511cc9d4e8..9c9eac5f86 100644 --- a/backend/src/api/index.ts +++ b/backend/src/api/index.ts @@ -2,6 +2,7 @@ import express from 'express' import cors from 'cors' import helmet from 'helmet' import bunyanMiddleware from 'bunyan-middleware' +import { PostHog } from 'posthog-node' import { authMiddleware } from '../middlewares/authMiddleware' import { tenantMiddleware } from '../middlewares/tenantMiddleware' import { databaseMiddleware } from '../middlewares/databaseMiddleware' @@ -16,6 +17,7 @@ import { errorMiddleware } from '../middlewares/errorMiddleware' import { passportStrategyMiddleware } from '../middlewares/passportStrategyMiddleware' import { redisMiddleware } from '../middlewares/redisMiddleware' import { createRedisClient } from '../utils/redis' +import { POSTHOG_CONFIG } from '../config' const serviceLogger = createServiceLogger() @@ -24,6 +26,8 @@ const app = express() setImmediate(async () => { const redis = await createRedisClient(true) + const posthog = new PostHog(POSTHOG_CONFIG.apiKey) + // Enables CORS app.use(cors({ origin: true })) @@ -47,6 +51,12 @@ setImmediate(async () => { // Bind redis to request app.use(redisMiddleware(redis)) + // Bind posthog to request + app.use((req: any, res, next) => { + req.posthog = posthog + next() + }) + // initialize passport strategies app.use(passportStrategyMiddleware) diff --git a/backend/src/api/tenant/tenantCreate.ts b/backend/src/api/tenant/tenantCreate.ts index 248eb33946..6744fd11b8 100644 --- a/backend/src/api/tenant/tenantCreate.ts +++ b/backend/src/api/tenant/tenantCreate.ts @@ -21,7 +21,7 @@ export default async (req, res) => { }, { ...req }, ) - identifyTenant(req.currentUser, payload) + identifyTenant({...req, currentTenant: payload}) telemetryTrack('Tenant created', {}, { ...req }) diff --git a/backend/src/api/tenant/tenantFind.ts b/backend/src/api/tenant/tenantFind.ts index bb05e36c9b..018ac1b8e4 100644 --- a/backend/src/api/tenant/tenantFind.ts +++ b/backend/src/api/tenant/tenantFind.ts @@ -13,7 +13,7 @@ export default async (req, res) => { if (payload) { if (req.currentUser) { - identifyTenant(req.currentUser, payload) + identifyTenant({...req, currentTenant: payload}) } await req.responseHandler.success(req, res, payload) diff --git a/backend/src/bin/scripts/change-tenant-plan.ts b/backend/src/bin/scripts/change-tenant-plan.ts new file mode 100644 index 0000000000..33595d1769 --- /dev/null +++ b/backend/src/bin/scripts/change-tenant-plan.ts @@ -0,0 +1,96 @@ +import { PostHog } from 'posthog-node' +import commandLineArgs from 'command-line-args' +import commandLineUsage from 'command-line-usage' +import * as fs from 'fs' +import path from 'path' +import { createServiceLogger } from '../../utils/logging' +import SequelizeRepository from '../../database/repositories/sequelizeRepository' +import setPosthogTenantProperties from '../../feature-flags/setTenantProperties' +import { POSTHOG_CONFIG } from '../../config' +import { timeout } from '../../utils/timing' + +const banner = fs.readFileSync(path.join(__dirname, 'banner.txt'), 'utf8') + +const log = createServiceLogger() + +const options = [ + { + name: 'tenant', + alias: 't', + type: String, + description: 'The unique ID of tenant that you would like to update.', + }, + { + name: 'plan', + alias: 'p', + type: String, + description: `Plan that will be applied to the tenant. Accepted values are 'Growth' and 'Essential'.`, + }, + { + name: 'trial', + alias: 'x', + description: + 'YYYY-MM-dd format trial end date. If this value is ommited, isTrial will be set to false.', + type: String, + defaultValue: null, + }, + { + name: 'help', + alias: 'h', + type: Boolean, + description: 'Print this usage guide.', + }, +] +const sections = [ + { + content: banner, + raw: true, + }, + { + header: 'Update tenant plan', + content: 'Updates tenant plan and sends the data to posthog for feature flagging.', + }, + { + header: 'Options', + optionList: options, + }, +] + +const usage = commandLineUsage(sections) +const parameters = commandLineArgs(options) + +if (parameters.help || !parameters.tenant || !parameters.plan) { + console.log(usage) +} else if (parameters.plan !== 'Growth' && parameters.plan !== 'Essential') { + console.log(parameters.plan) +} else { + setImmediate(async () => { + const tenantId = parameters.tenant + const plan = parameters.plan + const isTrial = parameters.trial !== null + const trialEndsAt = parameters.trial + + const options = await SequelizeRepository.getDefaultIRepositoryOptions() + const tenant = await options.database.tenant.findByPk(tenantId) + + if (!tenant) { + log.error({ tenantId }, 'Tenant not found!') + process.exit(1) + } else { + log.info({ tenantId, isTrial }, `Tenant found - updating tenant plan to ${plan}!`) + const updated = await tenant.update({ + plan, + isTrialPlan: isTrial, + trialEndsAt, + }) + + setPosthogTenantProperties( + updated, + new PostHog(POSTHOG_CONFIG.apiKey, { flushAt: 1, flushInterval: 1 }), + options.database, + ) + await timeout(2000) + } + process.exit(0) + }) +} diff --git a/backend/src/config/configTypes.ts b/backend/src/config/configTypes.ts index eb567ba500..07e2610f56 100644 --- a/backend/src/config/configTypes.ts +++ b/backend/src/config/configTypes.ts @@ -79,6 +79,10 @@ export interface SegmentConfiguration { writeKey: string } +export interface PosthogConfiguration { + apiKey: string +} + export interface ApiConfiguration { port: number url: string diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 86d03fab29..5c13fe114c 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -21,6 +21,7 @@ import { ClearbitConfiguration, DevtoConfiguration, RedisConfiguration, + PosthogConfiguration, } from './configTypes' // TODO-kube @@ -91,6 +92,12 @@ export const SEGMENT_CONFIG: SegmentConfiguration = KUBE_MODE writeKey: process.env.SEGMENT_WRITE_KEY, } + export const POSTHOG_CONFIG: PosthogConfiguration = KUBE_MODE + ? config.get('posthog') + : { + apiKey: process.env.POSTHOG_API_KEY, + } + export const COMPREHEND_CONFIG: ComprehendConfiguration = KUBE_MODE ? config.get('comprehend') : { diff --git a/backend/src/feature-flags/isFeatureEnabled.ts b/backend/src/feature-flags/isFeatureEnabled.ts new file mode 100644 index 0000000000..eca17c1032 --- /dev/null +++ b/backend/src/feature-flags/isFeatureEnabled.ts @@ -0,0 +1,13 @@ +import { PostHog } from 'posthog-node' +import { API_CONFIG } from '../config' + +export default async (featureFlag: string, tenantId: string, posthog: PostHog): Promise => { + if (API_CONFIG.edition === 'community') { + return true + } + + const featureFlagEnabled = await posthog.isFeatureEnabled(featureFlag, '', { + groups: { tenant: tenantId }, + }) + return featureFlagEnabled +} diff --git a/backend/src/feature-flags/setTenantProperties.ts b/backend/src/feature-flags/setTenantProperties.ts new file mode 100644 index 0000000000..ebdc2973d0 --- /dev/null +++ b/backend/src/feature-flags/setTenantProperties.ts @@ -0,0 +1,24 @@ +import { PostHog } from 'posthog-node' +import { API_CONFIG, POSTHOG_CONFIG } from '../config' + +export default async function setPosthogTenantProperties(tenant:any, posthog:PostHog, database:any) { + if (POSTHOG_CONFIG.apiKey && API_CONFIG.edition === 'crowd-hosted') { + const automationCount = await database.automation.count({ + where: { + tenantId: tenant.id, + }, + }) + + const payload = { + groupType: 'tenant', + groupKey: tenant.id, + properties: { + name: tenant.name, + plan: tenant.plan, + automationCount, + }, + } + console.log(payload) + posthog.groupIdentify(payload) + } +} diff --git a/backend/src/segment/identifyTenant.ts b/backend/src/segment/identifyTenant.ts index a4d5877822..9b35dd0d3e 100644 --- a/backend/src/segment/identifyTenant.ts +++ b/backend/src/segment/identifyTenant.ts @@ -1,16 +1,17 @@ import { SEGMENT_CONFIG, API_CONFIG } from '../config' +import setPosthogTenantProperties from '../feature-flags/setTenantProperties' -export default function identifyTenant(user, tenant) { +export default async function identifyTenant(req) { if (SEGMENT_CONFIG.writeKey) { const Analytics = require('analytics-node') const analytics = new Analytics(SEGMENT_CONFIG.writeKey) if (API_CONFIG.edition === 'crowd-hosted') { analytics.group({ - userId: user.id, - groupId: tenant.id, + userId: req.currentUser.id, + groupId: req.currentTenant.id, traits: { - name: tenant.name, + name: req.currentTenant.name, }, }) } else if (API_CONFIG.edition === 'community') { @@ -25,4 +26,6 @@ export default function identifyTenant(user, tenant) { } } } + + setPosthogTenantProperties(req.currentTenant, req.posthog, req.database) } diff --git a/backend/src/services/user/permissionChecker.ts b/backend/src/services/user/permissionChecker.ts index a3288d0242..86eadff647 100644 --- a/backend/src/services/user/permissionChecker.ts +++ b/backend/src/services/user/permissionChecker.ts @@ -161,7 +161,7 @@ export default class PermissionChecker { */ get currentTenantPlan() { if (!this.currentTenant || !this.currentTenant.plan) { - return plans.free + return plans.essential } return this.currentTenant.plan diff --git a/frontend/src/security/permissions.js b/frontend/src/security/permissions.js index 42f02de840..793258f00f 100644 --- a/frontend/src/security/permissions.js +++ b/frontend/src/security/permissions.js @@ -11,622 +11,622 @@ const plans = Plans.values * actions within modules (ex: memberEdit, activityCreate, conversationCreate, etc) */ class Permissions { - static get values() { - return { - tenantEdit: { - id: 'tenantEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - tenantDestroy: { - id: 'tenantDestroy', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - planEdit: { - id: 'planEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - planRead: { - id: 'planRead', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - userEdit: { - id: 'userEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - userDestroy: { - id: 'userDestroy', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - userCreate: { - id: 'userCreate', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - userImport: { - id: 'userImport', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - userRead: { - id: 'userRead', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - userAutocomplete: { - id: 'userAutocomplete', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - auditLogRead: { - id: 'auditLogRead', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - settingsRead: { - id: 'settingsRead', - allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [ - storage.settingsBackgroundImages, - storage.settingsLogos - ] - }, - settingsEdit: { - id: 'settingsEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [ - storage.settingsBackgroundImages, - storage.settingsLogos - ] - }, - integrationImport: { - id: 'integrationImport', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - integrationCreate: { - id: 'integrationCreate', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - integrationEdit: { - id: 'integrationEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - integrationDestroy: { - id: 'integrationDestroy', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - integrationRead: { - id: 'integrationRead', - allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - integrationAutocomplete: { - id: 'integrationAutocomplete', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, + static get values() { + return { + tenantEdit: { + id: 'tenantEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + tenantDestroy: { + id: 'tenantDestroy', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + planEdit: { + id: 'planEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + planRead: { + id: 'planRead', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + userEdit: { + id: 'userEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + userDestroy: { + id: 'userDestroy', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + userCreate: { + id: 'userCreate', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + userImport: { + id: 'userImport', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + userRead: { + id: 'userRead', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + userAutocomplete: { + id: 'userAutocomplete', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + auditLogRead: { + id: 'auditLogRead', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + settingsRead: { + id: 'settingsRead', + allowedRoles: [roles.admin, roles.readonly], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [ + storage.settingsBackgroundImages, + storage.settingsLogos + ] + }, + settingsEdit: { + id: 'settingsEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [ + storage.settingsBackgroundImages, + storage.settingsLogos + ] + }, + integrationImport: { + id: 'integrationImport', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + integrationCreate: { + id: 'integrationCreate', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + integrationEdit: { + id: 'integrationEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + integrationDestroy: { + id: 'integrationDestroy', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + integrationRead: { + id: 'integrationRead', + allowedRoles: [roles.admin, roles.readonly], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + integrationAutocomplete: { + id: 'integrationAutocomplete', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, - reportImport: { - id: 'reportImport', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - reportCreate: { - id: 'reportCreate', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - reportEdit: { - id: 'reportEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - reportDestroy: { - id: 'reportDestroy', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - reportRead: { - id: 'reportRead', - allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - reportAutocomplete: { - id: 'reportAutocomplete', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, + reportImport: { + id: 'reportImport', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + reportCreate: { + id: 'reportCreate', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + reportEdit: { + id: 'reportEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + reportDestroy: { + id: 'reportDestroy', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + reportRead: { + id: 'reportRead', + allowedRoles: [roles.admin, roles.readonly], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + reportAutocomplete: { + id: 'reportAutocomplete', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, - memberImport: { - id: 'memberImport', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - memberCreate: { - id: 'memberCreate', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - memberEdit: { - id: 'memberEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - memberDestroy: { - id: 'memberDestroy', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - memberRead: { - id: 'memberRead', - allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - memberAutocomplete: { - id: 'memberAutocomplete', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, + memberImport: { + id: 'memberImport', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + memberCreate: { + id: 'memberCreate', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + memberEdit: { + id: 'memberEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + memberDestroy: { + id: 'memberDestroy', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + memberRead: { + id: 'memberRead', + allowedRoles: [roles.admin, roles.readonly], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + memberAutocomplete: { + id: 'memberAutocomplete', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, - organizationImport: { - id: 'organizationImport', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - organizationCreate: { - id: 'organizationCreate', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - organizationEdit: { - id: 'organizationEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - organizationDestroy: { - id: 'organizationDestroy', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - organizationRead: { - id: 'organizationRead', - allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - organizationAutocomplete: { - id: 'organizationAutocomplete', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, + organizationImport: { + id: 'organizationImport', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + organizationCreate: { + id: 'organizationCreate', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + organizationEdit: { + id: 'organizationEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + organizationDestroy: { + id: 'organizationDestroy', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + organizationRead: { + id: 'organizationRead', + allowedRoles: [roles.admin, roles.readonly], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + organizationAutocomplete: { + id: 'organizationAutocomplete', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, - activityImport: { - id: 'activityImport', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - activityCreate: { - id: 'activityCreate', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - activityEdit: { - id: 'activityEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - activityDestroy: { - id: 'activityDestroy', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - activityRead: { - id: 'activityRead', - allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - activityAutocomplete: { - id: 'activityAutocomplete', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, + activityImport: { + id: 'activityImport', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + activityCreate: { + id: 'activityCreate', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + activityEdit: { + id: 'activityEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + activityDestroy: { + id: 'activityDestroy', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + activityRead: { + id: 'activityRead', + allowedRoles: [roles.admin, roles.readonly], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + activityAutocomplete: { + id: 'activityAutocomplete', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, - taskImport: { - id: 'taskImport', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - taskCreate: { - id: 'taskCreate', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - taskEdit: { - id: 'taskEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - taskDestroy: { - id: 'taskDestroy', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - taskRead: { - id: 'taskRead', - allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - taskAutocomplete: { - id: 'taskAutocomplete', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, + taskImport: { + id: 'taskImport', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + taskCreate: { + id: 'taskCreate', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + taskEdit: { + id: 'taskEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + taskDestroy: { + id: 'taskDestroy', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + taskRead: { + id: 'taskRead', + allowedRoles: [roles.admin, roles.readonly], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + taskAutocomplete: { + id: 'taskAutocomplete', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, - conversationImport: { - id: 'conversationImport', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - conversationCreate: { - id: 'conversationCreate', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - conversationEdit: { - id: 'conversationEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - conversationDestroy: { - id: 'conversationDestroy', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - conversationRead: { - id: 'conversationRead', - allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - conversationCustomize: { - id: 'conversationCustomize', - allowedRoles: [roles.admin], - allowedPlans: [plans.premium, plans.enterprise] - }, - conversationAutocomplete: { - id: 'conversationAutocomplete', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - eagleEyeRead: { - id: 'eagleEyeRead', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - automationImport: { - id: 'automationImport', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - automationCreate: { - id: 'automationCreate', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - automationEdit: { - id: 'automationEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - automationDestroy: { - id: 'automationDestroy', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ], - allowedStorage: [] - }, - automationRead: { - id: 'automationRead', - allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - }, - automationCustomize: { - id: 'automationCustomize', - allowedRoles: [roles.admin], - allowedPlans: [plans.premium, plans.enterprise] - }, - automationAutocomplete: { - id: 'automationAutocomplete', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.free, - plans.premium, - plans.enterprise - ] - } + conversationImport: { + id: 'conversationImport', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + conversationCreate: { + id: 'conversationCreate', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + conversationEdit: { + id: 'conversationEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + conversationDestroy: { + id: 'conversationDestroy', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + conversationRead: { + id: 'conversationRead', + allowedRoles: [roles.admin, roles.readonly], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + conversationCustomize: { + id: 'conversationCustomize', + allowedRoles: [roles.admin], + allowedPlans: [plans.growth, plans.enterprise] + }, + conversationAutocomplete: { + id: 'conversationAutocomplete', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + eagleEyeRead: { + id: 'eagleEyeRead', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + automationImport: { + id: 'automationImport', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + automationCreate: { + id: 'automationCreate', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + automationEdit: { + id: 'automationEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + automationDestroy: { + id: 'automationDestroy', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + automationRead: { + id: 'automationRead', + allowedRoles: [roles.admin, roles.readonly], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + automationCustomize: { + id: 'automationCustomize', + allowedRoles: [roles.admin], + allowedPlans: [plans.growth, plans.enterprise] + }, + automationAutocomplete: { + id: 'automationAutocomplete', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + } + } } - } - static get asArray() { - return Object.keys(this.values).map((value) => { - return this.values[value] - }) - } + static get asArray() { + return Object.keys(this.values).map((value) => { + return this.values[value] + }) + } } -export default Permissions +export default Permissions \ No newline at end of file diff --git a/frontend/src/security/plans.js b/frontend/src/security/plans.js index c772d13256..a94dbb1af3 100644 --- a/frontend/src/security/plans.js +++ b/frontend/src/security/plans.js @@ -2,13 +2,13 @@ * List of Plans */ class Plans { - static get values() { - return { - free: 'free', - premium: 'premium', - enterprise: 'enterprise' + static get values() { + return { + essential: 'Essential', + growth: 'Growth', + enterprise: 'enterprise' + } } - } } -export default Plans +export default Plans \ No newline at end of file From 310ef1ca968cfb049708d2912bc584415b1b29d2 Mon Sep 17 00:00:00 2001 From: anilb Date: Tue, 6 Dec 2022 18:22:11 +0100 Subject: [PATCH 05/27] downgrade expired trial plans job --- .../config/custom-environment-variables.json | 254 ++++++++--------- backend/config/default.json | 72 ++--- backend/package.json | 259 +++++++++--------- .../src/api/automation/automationCreate.ts | 4 +- .../conversationSettingsUpdate.ts | 7 +- backend/src/api/tenant/tenantCreate.ts | 2 +- backend/src/api/tenant/tenantFind.ts | 2 +- .../bin/jobs/downgradeExpiredTrialPlans.ts | 38 +++ backend/src/bin/jobs/index.ts | 2 + backend/src/bin/scripts/change-tenant-plan.ts | 2 + backend/src/config/index.ts | 2 +- backend/src/feature-flags/isFeatureEnabled.ts | 6 +- .../src/feature-flags/setTenantProperties.ts | 6 +- 13 files changed, 354 insertions(+), 302 deletions(-) create mode 100644 backend/src/bin/jobs/downgradeExpiredTrialPlans.ts diff --git a/backend/config/custom-environment-variables.json b/backend/config/custom-environment-variables.json index 49d721b5fe..398508718b 100644 --- a/backend/config/custom-environment-variables.json +++ b/backend/config/custom-environment-variables.json @@ -1,129 +1,129 @@ { - "api": { - "url": "CROWD_API_URL", - "edition": "CROWD_EDITION", - "frontendUrl": "CROWD_API_FRONTEND_URL", - "frontendUrlWithSubdomain": "CROWD_API_FRONTEND_URL_WITH_SUBDOMAINS", - "jwtSecret": "CROWD_API_JWT_SECRET", - "jwtExpiresIn": "CROWD_API_JWT_EXPIRES_IN", - "premiumApiUrl": "CROWD_PREMIUM_API_URL" - }, - "redis": { - "username": "CROWD_REDIS_USERNAME", - "password": "CROWD_REDIS_PASSWORD", - "host": "CROWD_REDIS_HOST", - "port": "CROWD_REDIS_PORT" - }, - "sqs": { - "host": "CROWD_SQS_HOST", - "port": "CROWD_SQS_PORT", - "nodejsWorkerQueue": "CROWD_SQS_NODEJS_WORKER_QUEUE", - "nodejsWorkerDelayableQueue": "CROWD_SQS_NODEJS_WORKER_DELAYABLE_QUEUE", - "pythonWorkerQueue": "CROWD_SQS_PYTHON_WORKER_QUEUE", - "premiumPythonWorkerQueue": "CROWD_SQS_PREMIUM_PYTHON_WORKER_QUEUE", - "aws": { - "accountId": "CROWD_SQS_AWS_ACCOUNT_ID", - "accessKeyId": "CROWD_SQS_AWS_ACCESS_KEY_ID", - "secretAccessKey": "CROWD_SQS_AWS_SECRET_ACCESS_KEY", - "region": "CROWD_SQS_AWS_REGION" - } - }, - "s3": { - "host": "CROWD_S3_HOST", - "port": "CROWD_S3_PORT", - "integrationsAssetsBucket": "CROWD_S3_INTEGRATION_ASSETS_BUCKET", - "microservicesAssetsBucket": "CROWD_S3_MICROSERVICES_ASSETS_BUCKET", - "aws": { - "accountId": "CROWD_S3_AWS_ACCOUNT_ID", - "accessKeyId": "CROWD_S3_AWS_ACCESS_KEY_ID", - "secretAccessKey": "CROWD_S3_AWS_SECRET_ACCESS_KEY", - "region": "CROWD_S3_AWS_REGION" - } - }, - "db": { - "readHost": "CROWD_DB_READ_HOST", - "writeHost": "CROWD_DB_WRITE_HOST", - "port": "CROWD_DB_PORT", - "username": "CROWD_DB_USERNAME", - "password": "CROWD_DB_PASSWORD", - "apiUsername": "CROWD_DB_API_USERNAME", - "apiPassword": "CROWD_DB_API_PASSWORD", - "nodejsWorkerUsername": "CROWD_DB_NODEJS_WORKER_USERNAME", - "nodejsWorkerPassword": "CROWD_DB_NODEJS_WORKER_PASSWORD", - "jobGeneratorUsername": "CROWD_DB_JOB_GENERATOR_USERNAME", - "jobGeneratorPassword": "CROWD_DB_JOB_GENERATOR_PASSWORD", - "database": "CROWD_DB_DATABASE" - }, - "cubejs": { - "url": "CROWD_CUBEJS_URL", - "jwtSecret": "CROWD_CUBEJS_JWT_SECRET", - "jwtExpiry": "CROWD_CUBEJS_JWT_EXPIRY" - }, - "searchEngine": { - "host": "CROWD_SEARCH_ENGINE_HOST", - "apiKey": "CROWD_SEARCH_ENGINE_API_KEY" - }, - "segment": { - "writeKey": "CROWD_SEGMENT_WRITE_KEY" - }, - "posthog": { - "apiKey": "CROWD_POSTHOG_API_KEY" - }, - "comprehend": { - "aws": { - "accountId": "CROWD_COMPREHEND_AWS_ACCOUNT_ID", - "accessKeyId": "CROWD_COMPREHEND_AWS_ACCESS_KEY_ID", - "secretAccessKey": "CROWD_COMPREHEND_AWS_SECRET_ACCESS_KEY", - "region": "CROWD_COMPREHEND_AWS_REGION" - } - }, - "clearbit": { - "apiKey": "CROWD_CLEARBIT_API_KEY" - }, - "netlify": { - "apiKey": "CROWD_NETLIFY_API_KEY", - "siteDomain": "CROWD_NETLIFY_SITE_DOMAIN" - }, - "sendgrid": { - "key": "CROWD_SENDGRID_KEY", - "emailFrom": "CROWD_SENDGRID_EMAIL_FROM", - "nameFrom": "CROWD_SENDGRID_NAME_FROM", - "templateEmailAddressVerification": "CROWD_SENDGRID_TEMPLATE_EMAIL_ADDRESS_VERIFICATION", - "templateInvitation": "CROWD_SENDGRID_TEMPLATE_INVITATION", - "templatePasswordReset": "CROWD_SENDGRID_TEMPLATE_PASSWORD_RESET", - "templateWeeklyAnalytics": "CROWD_SENDGRID_TEMPLATE_WEEKLY_ANALYTICS", - "weeklyAnalyticsUnsubscribeGroupId": "CROWD_SENDGRID_WEEKLY_ANALYTICS_UNSUBSCRIBE_GROUP_ID" - }, - "plans": { - "stripePricePremium": "CROWD_STRIPE_PRICE_PREMIUM", - "stripePriceEnterprise": "CROWD_STRIPE_PRICE_ENTERPRISE", - "stripeSecretKey": "CROWD_STRIPE_SECRET_KEY", - "stripWebhookSigningSecret": "CROWD_STRIPE_WEBHOOK_SIGNING_SECRET" - }, - "twitter": { - "clientId": "CROWD_TWITTER_CLIENT_ID", - "clientSecret": "CROWD_TWITTER_CLIENT_SECRET" - }, - "slack": { - "clientId": "CROWD_SLACK_CLIENT_ID", - "clientSecret": "CROWD_SLACK_CLIENT_SECRET", - "reporterToken": "CROWD_SLACK_REPORTER_TOKEN", - "reporterChannel": "CROWD_SLACK_REPORTER_CHANNEL" - }, - "google": { - "clientId": "CROWD_GOOGLE_CLIENT_ID", - "clientSecret": "CROWD_GOOGLE_CLIENT_SECRET", - "callbackUrl": "CROWD_GOOGLE_CALLBACK_URL" - }, - "discord": { - "token": "CROWD_DISCORD_TOKEN", - "token2": "CROWD_DISCORD_TOKEN_2" - }, - "github": { - "appId": "CROWD_GITHUB_APP_ID", - "clientId": "CROWD_GITHUB_CLIENT_ID", - "clientSecret": "CROWD_GITHUB_CLIENT_SECRET", - "privateKey": "CROWD_GITHUB_PRIVATE_KEY", - "webhookSecret": "CROWD_GITHUB_WEBHOOK_SECRET" + "api": { + "url": "CROWD_API_URL", + "edition": "CROWD_EDITION", + "frontendUrl": "CROWD_API_FRONTEND_URL", + "frontendUrlWithSubdomain": "CROWD_API_FRONTEND_URL_WITH_SUBDOMAINS", + "jwtSecret": "CROWD_API_JWT_SECRET", + "jwtExpiresIn": "CROWD_API_JWT_EXPIRES_IN", + "premiumApiUrl": "CROWD_PREMIUM_API_URL" + }, + "redis": { + "username": "CROWD_REDIS_USERNAME", + "password": "CROWD_REDIS_PASSWORD", + "host": "CROWD_REDIS_HOST", + "port": "CROWD_REDIS_PORT" + }, + "sqs": { + "host": "CROWD_SQS_HOST", + "port": "CROWD_SQS_PORT", + "nodejsWorkerQueue": "CROWD_SQS_NODEJS_WORKER_QUEUE", + "nodejsWorkerDelayableQueue": "CROWD_SQS_NODEJS_WORKER_DELAYABLE_QUEUE", + "pythonWorkerQueue": "CROWD_SQS_PYTHON_WORKER_QUEUE", + "premiumPythonWorkerQueue": "CROWD_SQS_PREMIUM_PYTHON_WORKER_QUEUE", + "aws": { + "accountId": "CROWD_SQS_AWS_ACCOUNT_ID", + "accessKeyId": "CROWD_SQS_AWS_ACCESS_KEY_ID", + "secretAccessKey": "CROWD_SQS_AWS_SECRET_ACCESS_KEY", + "region": "CROWD_SQS_AWS_REGION" } -} \ No newline at end of file + }, + "s3": { + "host": "CROWD_S3_HOST", + "port": "CROWD_S3_PORT", + "integrationsAssetsBucket": "CROWD_S3_INTEGRATION_ASSETS_BUCKET", + "microservicesAssetsBucket": "CROWD_S3_MICROSERVICES_ASSETS_BUCKET", + "aws": { + "accountId": "CROWD_S3_AWS_ACCOUNT_ID", + "accessKeyId": "CROWD_S3_AWS_ACCESS_KEY_ID", + "secretAccessKey": "CROWD_S3_AWS_SECRET_ACCESS_KEY", + "region": "CROWD_S3_AWS_REGION" + } + }, + "db": { + "readHost": "CROWD_DB_READ_HOST", + "writeHost": "CROWD_DB_WRITE_HOST", + "port": "CROWD_DB_PORT", + "username": "CROWD_DB_USERNAME", + "password": "CROWD_DB_PASSWORD", + "apiUsername": "CROWD_DB_API_USERNAME", + "apiPassword": "CROWD_DB_API_PASSWORD", + "nodejsWorkerUsername": "CROWD_DB_NODEJS_WORKER_USERNAME", + "nodejsWorkerPassword": "CROWD_DB_NODEJS_WORKER_PASSWORD", + "jobGeneratorUsername": "CROWD_DB_JOB_GENERATOR_USERNAME", + "jobGeneratorPassword": "CROWD_DB_JOB_GENERATOR_PASSWORD", + "database": "CROWD_DB_DATABASE" + }, + "cubejs": { + "url": "CROWD_CUBEJS_URL", + "jwtSecret": "CROWD_CUBEJS_JWT_SECRET", + "jwtExpiry": "CROWD_CUBEJS_JWT_EXPIRY" + }, + "searchEngine": { + "host": "CROWD_SEARCH_ENGINE_HOST", + "apiKey": "CROWD_SEARCH_ENGINE_API_KEY" + }, + "segment": { + "writeKey": "CROWD_SEGMENT_WRITE_KEY" + }, + "posthog": { + "apiKey": "CROWD_POSTHOG_API_KEY" + }, + "comprehend": { + "aws": { + "accountId": "CROWD_COMPREHEND_AWS_ACCOUNT_ID", + "accessKeyId": "CROWD_COMPREHEND_AWS_ACCESS_KEY_ID", + "secretAccessKey": "CROWD_COMPREHEND_AWS_SECRET_ACCESS_KEY", + "region": "CROWD_COMPREHEND_AWS_REGION" + } + }, + "clearbit": { + "apiKey": "CROWD_CLEARBIT_API_KEY" + }, + "netlify": { + "apiKey": "CROWD_NETLIFY_API_KEY", + "siteDomain": "CROWD_NETLIFY_SITE_DOMAIN" + }, + "sendgrid": { + "key": "CROWD_SENDGRID_KEY", + "emailFrom": "CROWD_SENDGRID_EMAIL_FROM", + "nameFrom": "CROWD_SENDGRID_NAME_FROM", + "templateEmailAddressVerification": "CROWD_SENDGRID_TEMPLATE_EMAIL_ADDRESS_VERIFICATION", + "templateInvitation": "CROWD_SENDGRID_TEMPLATE_INVITATION", + "templatePasswordReset": "CROWD_SENDGRID_TEMPLATE_PASSWORD_RESET", + "templateWeeklyAnalytics": "CROWD_SENDGRID_TEMPLATE_WEEKLY_ANALYTICS", + "weeklyAnalyticsUnsubscribeGroupId": "CROWD_SENDGRID_WEEKLY_ANALYTICS_UNSUBSCRIBE_GROUP_ID" + }, + "plans": { + "stripePricePremium": "CROWD_STRIPE_PRICE_PREMIUM", + "stripePriceEnterprise": "CROWD_STRIPE_PRICE_ENTERPRISE", + "stripeSecretKey": "CROWD_STRIPE_SECRET_KEY", + "stripWebhookSigningSecret": "CROWD_STRIPE_WEBHOOK_SIGNING_SECRET" + }, + "twitter": { + "clientId": "CROWD_TWITTER_CLIENT_ID", + "clientSecret": "CROWD_TWITTER_CLIENT_SECRET" + }, + "slack": { + "clientId": "CROWD_SLACK_CLIENT_ID", + "clientSecret": "CROWD_SLACK_CLIENT_SECRET", + "reporterToken": "CROWD_SLACK_REPORTER_TOKEN", + "reporterChannel": "CROWD_SLACK_REPORTER_CHANNEL" + }, + "google": { + "clientId": "CROWD_GOOGLE_CLIENT_ID", + "clientSecret": "CROWD_GOOGLE_CLIENT_SECRET", + "callbackUrl": "CROWD_GOOGLE_CALLBACK_URL" + }, + "discord": { + "token": "CROWD_DISCORD_TOKEN", + "token2": "CROWD_DISCORD_TOKEN_2" + }, + "github": { + "appId": "CROWD_GITHUB_APP_ID", + "clientId": "CROWD_GITHUB_CLIENT_ID", + "clientSecret": "CROWD_GITHUB_CLIENT_SECRET", + "privateKey": "CROWD_GITHUB_PRIVATE_KEY", + "webhookSecret": "CROWD_GITHUB_WEBHOOK_SECRET" + } +} diff --git a/backend/config/default.json b/backend/config/default.json index 25e20c144d..a1e368ba99 100644 --- a/backend/config/default.json +++ b/backend/config/default.json @@ -1,37 +1,37 @@ { - "api": { - "port": 8080, - "documentation": false - }, - "sqs": {}, - "s3": {}, - "db": { - "dialect": "postgres", - "logging": false, - "transactions": false - }, - "cubejs": {}, - "searchEngine": {}, - "segment": {}, - "posthog": {}, - "comprehend": { - "aws": {} - }, - "clearbit": {}, - "netlify": {}, - "sendgrid": {}, - "plans": {}, - "devto": {}, - "twitter": { - "maxRetrospectInSeconds": 7380, - "limitResetFrequencyDays": 30 - }, - "slack": { - "maxRetrospectInSeconds": 3600 - }, - "google": {}, - "discord": { - "maxRetrospectInSeconds": 3600 - }, - "github": {} -} \ No newline at end of file + "api": { + "port": 8080, + "documentation": false + }, + "sqs": {}, + "s3": {}, + "db": { + "dialect": "postgres", + "logging": false, + "transactions": false + }, + "cubejs": {}, + "searchEngine": {}, + "segment": {}, + "posthog": {}, + "comprehend": { + "aws": {} + }, + "clearbit": {}, + "netlify": {}, + "sendgrid": {}, + "plans": {}, + "devto": {}, + "twitter": { + "maxRetrospectInSeconds": 7380, + "limitResetFrequencyDays": 30 + }, + "slack": { + "maxRetrospectInSeconds": 3600 + }, + "google": {}, + "discord": { + "maxRetrospectInSeconds": 3600 + }, + "github": {} +} diff --git a/backend/package.json b/backend/package.json index aa418e0c71..fc46fc6f37 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,131 +1,130 @@ { - "name": "app-backend", - "description": "Backend", - "scripts": { - "start:api": "ts-node --transpile-only ./src/bin/api.ts", - "start:api:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/api.ts\"", - "start:api:dev:local": "set -a && . ./.env.dist.local && . ./.env.override.local && set +a && SERVICE=api nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/api.ts\"", - "start:job-generator": "ts-node --transpile-only ./src/bin/job-generator.ts", - "start:job-generator:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/job-generator.ts\"", - "start:job-generator:dev:local": "set -a && . ./.env.dist.local && . ./.env.override.local && set +a && SERVICE=job-generator nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/job-generator.ts\"", - "start:nodejs-worker": "ts-node --transpile-only ./src/bin/nodejs-worker.ts", - "start:nodejs-worker:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/nodejs-worker.ts\"", - "start:nodejs-worker:dev:local": " && SERVICE=nodejs-worker nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"node --inspect=0.0.0.0:9229 -r ts-node/register ./src/bin/nodejs-worker.ts --transpile-only\"", - "build": "tsc && npm run build:documentation && cp package*json dist/ && cp .sequelizerc dist/.sequelizerc ", - "test": "../scripts/cli scaffold up-test && jest --clearCache && set -a && . ./.env.dist.local && . ./.env.test && set +a && NODE_ENV=test SERVICE=test jest --runInBand --verbose --forceExit", - "build:documentation": "copyfiles --flat ./src/documentation/openapi.json ./dist/documentation/", - "db:create:test": "npx ts-node ./src/database/initializers/create test", - "db:create:dev:source": "ts-node ./src/database/initializers/create dev", - "db:seed:test": "npx ts-node ./src/database/initializers/seed test", - "db:seed:dev": "npx ts-node ./src/database/initializers/seed dev", - "db:publish": "bash ./util/publish-db.sh", - "docs": "bash ./util/publish-docs.sh", - "sequelize-cli:source": "npm run build && npx sequelize --config src/database/sequelize-cli-config.ts --migrations-source-path src/database/migrations", - "sequelize-cli:build": "npx sequelize --config database/sequelize-cli-config.js --migrations-compiled-path database/migrations", - "stripe:login": "stripe login", - "stripe:start": "stripe listen --forward-to localhost:8080/api/plan/stripe/webhook", - "lint": "npx eslint .", - "format": "npx prettier --write .", - "script:process-integration": "SERVICE=script ts-node ./src/bin/scripts/process-integration.ts", - "script:change-tenant-plan": "SERVICE=script ts-node ./src/bin/scripts/change-tenant-plan.ts" - - }, - "dependencies": { - "@aws-sdk/client-comprehend": "^3.159.0", - "@cubejs-client/core": "^0.30.4", - "@google-cloud/storage": "5.3.0", - "@octokit/auth-app": "^3.6.1", - "@octokit/graphql": "^4.8.0", - "@octokit/request": "^5.6.3", - "@pm2/io": "^5.0.0", - "@sendgrid/mail": "7.2.6", - "@slack/web-api": "^6.7.2", - "@superfaceai/one-sdk": "^1.3.0", - "@superfaceai/passport-twitter-oauth2": "^1.0.0", - "analytics-node": "^6.2.0", - "aws-sdk": "2.814.0", - "axios": "^0.27.2", - "bcrypt": "5.0.0", - "bunyan": "^1.8.15", - "bunyan-format": "^0.2.1", - "bunyan-middleware": "^1.0.2", - "clearbit": "^1.3.5", - "cli-highlight": "2.1.6", - "command-line-args": "^5.2.1", - "command-line-usage": "^6.1.3", - "config": "^3.3.8", - "cors": "2.8.5", - "cron": "^2.1.0", - "cron-time-generator": "^1.3.0", - "crypto-js": "^4.1.1", - "dotenv": "8.2.0", - "dotenv-expand": "^8.0.3", - "emoji-dictionary": "^1.0.11", - "express": "4.17.1", - "express-rate-limit": "6.5.1", - "formidable-serverless": "1.1.1", - "helmet": "4.1.1", - "html-to-text": "^8.2.1", - "jsonwebtoken": "8.5.1", - "lodash": "4.17.21", - "meilisearch": "^0.26.0", - "moment": "2.29.4", - "moment-timezone": "^0.5.34", - "mv": "2.1.1", - "node-fetch": "^2.6.7", - "omit-deep-by-values": "^1.0.2", - "openapi-comment-parser": "^1.0.0", - "passport": "0.6.0", - "passport-facebook": "3.0.0", - "passport-google-oauth": "2.0.0", - "passport-google-oauth20": "^2.0.0", - "passport-slack": "0.0.7", - "pg": "^8.7.3", - "pm2": "^5.2.0", - "posthog-node": "^2.2.3", - "redis": "^4.5.0", - "sanitize-html": "^2.7.1", - "sequelize": "6.21.2", - "sequelize-cli-typescript": "^3.2.0-c", - "stripe": "10.0.0", - "superagent": "^8.0.0", - "swagger-ui-dist": "4.1.3", - "uuid": "^8.3.2", - "validator": "^13.7.0", - "verify-github-webhook": "^1.0.1" - }, - "private": true, - "devDependencies": { - "@babel/plugin-transform-runtime": "^7.18.10", - "@babel/preset-env": "^7.16.10", - "@types/bunyan": "^1.8.8", - "@types/bunyan-format": "^0.2.5", - "@types/config": "^3.3.0", - "@types/cron": "^2.0.0", - "@types/html-to-text": "^8.1.1", - "@types/jest": "^27.4.0", - "@types/node": "^17.0.21", - "@types/sanitize-html": "^2.6.2", - "@types/superagent": "^4.1.15", - "@typescript-eslint/eslint-plugin": "^5.17.0", - "@typescript-eslint/parser": "^5.17.0", - "copyfiles": "2.4.1", - "cross-env": "7.0.2", - "eslint": "^8.12.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-config-airbnb-typescript": "^16.1.4", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-import": "^2.25.4", - "eslint-plugin-openapi": "^0.0.4", - "jest": "^27.4.7", - "mocha": "8.1.3", - "node-mocks-http": "1.9.0", - "nodemon": "2.0.4", - "prettier": "^2.5.1", - "rdme": "^7.2.0", - "supertest": "^6.2.2", - "ts-jest": "^27.1.3", - "ts-node": "10.6.0", - "typescript": "^4.7.4" - } -} \ No newline at end of file + "name": "app-backend", + "description": "Backend", + "scripts": { + "start:api": "ts-node --transpile-only ./src/bin/api.ts", + "start:api:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/api.ts\"", + "start:api:dev:local": "set -a && . ./.env.dist.local && . ./.env.override.local && set +a && SERVICE=api nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/api.ts\"", + "start:job-generator": "ts-node --transpile-only ./src/bin/job-generator.ts", + "start:job-generator:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/job-generator.ts\"", + "start:job-generator:dev:local": "set -a && . ./.env.dist.local && . ./.env.override.local && set +a && SERVICE=job-generator nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/job-generator.ts\"", + "start:nodejs-worker": "ts-node --transpile-only ./src/bin/nodejs-worker.ts", + "start:nodejs-worker:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/nodejs-worker.ts\"", + "start:nodejs-worker:dev:local": " && SERVICE=nodejs-worker nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"node --inspect=0.0.0.0:9229 -r ts-node/register ./src/bin/nodejs-worker.ts --transpile-only\"", + "build": "tsc && npm run build:documentation && cp package*json dist/ && cp .sequelizerc dist/.sequelizerc ", + "test": "../scripts/cli scaffold up-test && jest --clearCache && set -a && . ./.env.dist.local && . ./.env.test && set +a && NODE_ENV=test SERVICE=test jest --runInBand --verbose --forceExit", + "build:documentation": "copyfiles --flat ./src/documentation/openapi.json ./dist/documentation/", + "db:create:test": "npx ts-node ./src/database/initializers/create test", + "db:create:dev:source": "ts-node ./src/database/initializers/create dev", + "db:seed:test": "npx ts-node ./src/database/initializers/seed test", + "db:seed:dev": "npx ts-node ./src/database/initializers/seed dev", + "db:publish": "bash ./util/publish-db.sh", + "docs": "bash ./util/publish-docs.sh", + "sequelize-cli:source": "npm run build && npx sequelize --config src/database/sequelize-cli-config.ts --migrations-source-path src/database/migrations", + "sequelize-cli:build": "npx sequelize --config database/sequelize-cli-config.js --migrations-compiled-path database/migrations", + "stripe:login": "stripe login", + "stripe:start": "stripe listen --forward-to localhost:8080/api/plan/stripe/webhook", + "lint": "npx eslint .", + "format": "npx prettier --write .", + "script:process-integration": "SERVICE=script ts-node ./src/bin/scripts/process-integration.ts", + "script:change-tenant-plan": "SERVICE=script ts-node ./src/bin/scripts/change-tenant-plan.ts" + }, + "dependencies": { + "@aws-sdk/client-comprehend": "^3.159.0", + "@cubejs-client/core": "^0.30.4", + "@google-cloud/storage": "5.3.0", + "@octokit/auth-app": "^3.6.1", + "@octokit/graphql": "^4.8.0", + "@octokit/request": "^5.6.3", + "@pm2/io": "^5.0.0", + "@sendgrid/mail": "7.2.6", + "@slack/web-api": "^6.7.2", + "@superfaceai/one-sdk": "^1.3.0", + "@superfaceai/passport-twitter-oauth2": "^1.0.0", + "analytics-node": "^6.2.0", + "aws-sdk": "2.814.0", + "axios": "^0.27.2", + "bcrypt": "5.0.0", + "bunyan": "^1.8.15", + "bunyan-format": "^0.2.1", + "bunyan-middleware": "^1.0.2", + "clearbit": "^1.3.5", + "cli-highlight": "2.1.6", + "command-line-args": "^5.2.1", + "command-line-usage": "^6.1.3", + "config": "^3.3.8", + "cors": "2.8.5", + "cron": "^2.1.0", + "cron-time-generator": "^1.3.0", + "crypto-js": "^4.1.1", + "dotenv": "8.2.0", + "dotenv-expand": "^8.0.3", + "emoji-dictionary": "^1.0.11", + "express": "4.17.1", + "express-rate-limit": "6.5.1", + "formidable-serverless": "1.1.1", + "helmet": "4.1.1", + "html-to-text": "^8.2.1", + "jsonwebtoken": "8.5.1", + "lodash": "4.17.21", + "meilisearch": "^0.26.0", + "moment": "2.29.4", + "moment-timezone": "^0.5.34", + "mv": "2.1.1", + "node-fetch": "^2.6.7", + "omit-deep-by-values": "^1.0.2", + "openapi-comment-parser": "^1.0.0", + "passport": "0.6.0", + "passport-facebook": "3.0.0", + "passport-google-oauth": "2.0.0", + "passport-google-oauth20": "^2.0.0", + "passport-slack": "0.0.7", + "pg": "^8.7.3", + "pm2": "^5.2.0", + "posthog-node": "^2.2.3", + "redis": "^4.5.0", + "sanitize-html": "^2.7.1", + "sequelize": "6.21.2", + "sequelize-cli-typescript": "^3.2.0-c", + "stripe": "10.0.0", + "superagent": "^8.0.0", + "swagger-ui-dist": "4.1.3", + "uuid": "^8.3.2", + "validator": "^13.7.0", + "verify-github-webhook": "^1.0.1" + }, + "private": true, + "devDependencies": { + "@babel/plugin-transform-runtime": "^7.18.10", + "@babel/preset-env": "^7.16.10", + "@types/bunyan": "^1.8.8", + "@types/bunyan-format": "^0.2.5", + "@types/config": "^3.3.0", + "@types/cron": "^2.0.0", + "@types/html-to-text": "^8.1.1", + "@types/jest": "^27.4.0", + "@types/node": "^17.0.21", + "@types/sanitize-html": "^2.6.2", + "@types/superagent": "^4.1.15", + "@typescript-eslint/eslint-plugin": "^5.17.0", + "@typescript-eslint/parser": "^5.17.0", + "copyfiles": "2.4.1", + "cross-env": "7.0.2", + "eslint": "^8.12.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^16.1.4", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-openapi": "^0.0.4", + "jest": "^27.4.7", + "mocha": "8.1.3", + "node-mocks-http": "1.9.0", + "nodemon": "2.0.4", + "prettier": "^2.5.1", + "rdme": "^7.2.0", + "supertest": "^6.2.2", + "ts-jest": "^27.1.3", + "ts-node": "10.6.0", + "typescript": "^4.7.4" + } +} diff --git a/backend/src/api/automation/automationCreate.ts b/backend/src/api/automation/automationCreate.ts index 87d1f9f897..beac84fac6 100644 --- a/backend/src/api/automation/automationCreate.ts +++ b/backend/src/api/automation/automationCreate.ts @@ -22,9 +22,9 @@ import isFeatureEnabled from '../../feature-flags/isFeatureEnabled' export default async (req, res) => { new PermissionChecker(req).validateHas(Permissions.values.automationCreate) - if (!await isFeatureEnabled('automations', req.currentTenant.id, req.posthog)) { + if (!(await isFeatureEnabled('automations', req.currentTenant.id, req.posthog))) { await req.responseHandler.success(req, res, { - message: 'You have exceeded # of automations you can have in your plan' + message: 'You have exceeded # of automations you can have in your plan', }) return } diff --git a/backend/src/api/conversation/conversationSettingsUpdate.ts b/backend/src/api/conversation/conversationSettingsUpdate.ts index 9c5d2f777f..3c8d6bf0cc 100644 --- a/backend/src/api/conversation/conversationSettingsUpdate.ts +++ b/backend/src/api/conversation/conversationSettingsUpdate.ts @@ -6,9 +6,12 @@ import PermissionChecker from '../../services/user/permissionChecker' export default async (req, res) => { new PermissionChecker(req).validateHas(Permissions.values.conversationEdit) - if(req.body.customUrl && !await isFeatureEnabled('community-help-center-pro', req.currentTenant.id, req.posthog)){ + if ( + req.body.customUrl && + !(await isFeatureEnabled('community-help-center-pro', req.currentTenant.id, req.posthog)) + ) { await req.responseHandler.success(req, res, { - message: `Your plan (${req.currentTenant.plan}) doesn't include custom urls.` + message: `Your plan (${req.currentTenant.plan}) doesn't include custom urls.`, }) } diff --git a/backend/src/api/tenant/tenantCreate.ts b/backend/src/api/tenant/tenantCreate.ts index 6744fd11b8..669e3ee20e 100644 --- a/backend/src/api/tenant/tenantCreate.ts +++ b/backend/src/api/tenant/tenantCreate.ts @@ -21,7 +21,7 @@ export default async (req, res) => { }, { ...req }, ) - identifyTenant({...req, currentTenant: payload}) + identifyTenant({ ...req, currentTenant: payload }) telemetryTrack('Tenant created', {}, { ...req }) diff --git a/backend/src/api/tenant/tenantFind.ts b/backend/src/api/tenant/tenantFind.ts index 018ac1b8e4..6f27d6c1b2 100644 --- a/backend/src/api/tenant/tenantFind.ts +++ b/backend/src/api/tenant/tenantFind.ts @@ -13,7 +13,7 @@ export default async (req, res) => { if (payload) { if (req.currentUser) { - identifyTenant({...req, currentTenant: payload}) + identifyTenant({ ...req, currentTenant: payload }) } await req.responseHandler.success(req, res, payload) diff --git a/backend/src/bin/jobs/downgradeExpiredTrialPlans.ts b/backend/src/bin/jobs/downgradeExpiredTrialPlans.ts new file mode 100644 index 0000000000..9ff8433b78 --- /dev/null +++ b/backend/src/bin/jobs/downgradeExpiredTrialPlans.ts @@ -0,0 +1,38 @@ +import cronGenerator from 'cron-time-generator' +import { PostHog } from 'posthog-node' +import { POSTHOG_CONFIG } from '../../config' +import SequelizeRepository from '../../database/repositories/sequelizeRepository' +import setPosthogTenantProperties from '../../feature-flags/setTenantProperties' +import Plans from '../../security/plans' +import { CrowdJob } from '../../types/jobTypes' +import { timeout } from '../../utils/timing' + +const job: CrowdJob = { + name: 'Downgrade Expired Trial Plans', + // every day + cronTime: cronGenerator.every(1).days(), + onTrigger: async () => { + console.log('in Downgrade Expired Trial Plans') + const dbOptions = await SequelizeRepository.getDefaultIRepositoryOptions() + const posthog = new PostHog(POSTHOG_CONFIG.apiKey, { flushAt: 1, flushInterval: 1 }) + + const expiredTenants = await dbOptions.database.sequelize.query( + `select t.id, t.name from tenants t + where t."isTrialPlan" and t."trialEndsAt" < now()`, + ) + + for (const tenant of expiredTenants[0]) { + const updatedTenant = await dbOptions.database.tenant.update( + { isTrialPlan: false, trialEndsAt: null, plan: Plans.values.essential }, + { returning: true, raw: true, where: { id: tenant.id } }, + ) + + setPosthogTenantProperties(updatedTenant[1][0], posthog, dbOptions.database) + } + + // give time to posthog to process queue messages + await timeout(2000) + }, +} + +export default job diff --git a/backend/src/bin/jobs/index.ts b/backend/src/bin/jobs/index.ts index e6f82ef8b2..4f4e53a433 100644 --- a/backend/src/bin/jobs/index.ts +++ b/backend/src/bin/jobs/index.ts @@ -4,6 +4,7 @@ import weeklyAnalyticsEmailsCoordinator from './weeklyAnalyticsEmailsCoordinator import memberScoreCoordinator from './memberScoreCoordinator' import checkSqsQueues from './checkSqsQueues' import refreshMaterializedViews from './refreshMaterializedViews' +import downgradeExpiredTrialPlans from './downgradeExpiredTrialPlans' const jobs: CrowdJob[] = [ weeklyAnalyticsEmailsCoordinator, @@ -11,6 +12,7 @@ const jobs: CrowdJob[] = [ memberScoreCoordinator, checkSqsQueues, refreshMaterializedViews, + downgradeExpiredTrialPlans, ] export default jobs diff --git a/backend/src/bin/scripts/change-tenant-plan.ts b/backend/src/bin/scripts/change-tenant-plan.ts index 33595d1769..8b2d68760b 100644 --- a/backend/src/bin/scripts/change-tenant-plan.ts +++ b/backend/src/bin/scripts/change-tenant-plan.ts @@ -89,6 +89,8 @@ if (parameters.help || !parameters.tenant || !parameters.plan) { new PostHog(POSTHOG_CONFIG.apiKey, { flushAt: 1, flushInterval: 1 }), options.database, ) + + // give time to posthog to process queue messages await timeout(2000) } process.exit(0) diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 5c13fe114c..50aebe9d3e 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -92,7 +92,7 @@ export const SEGMENT_CONFIG: SegmentConfiguration = KUBE_MODE writeKey: process.env.SEGMENT_WRITE_KEY, } - export const POSTHOG_CONFIG: PosthogConfiguration = KUBE_MODE +export const POSTHOG_CONFIG: PosthogConfiguration = KUBE_MODE ? config.get('posthog') : { apiKey: process.env.POSTHOG_API_KEY, diff --git a/backend/src/feature-flags/isFeatureEnabled.ts b/backend/src/feature-flags/isFeatureEnabled.ts index eca17c1032..a13c1de6a8 100644 --- a/backend/src/feature-flags/isFeatureEnabled.ts +++ b/backend/src/feature-flags/isFeatureEnabled.ts @@ -1,7 +1,11 @@ import { PostHog } from 'posthog-node' import { API_CONFIG } from '../config' -export default async (featureFlag: string, tenantId: string, posthog: PostHog): Promise => { +export default async ( + featureFlag: string, + tenantId: string, + posthog: PostHog, +): Promise => { if (API_CONFIG.edition === 'community') { return true } diff --git a/backend/src/feature-flags/setTenantProperties.ts b/backend/src/feature-flags/setTenantProperties.ts index ebdc2973d0..c77d9b5579 100644 --- a/backend/src/feature-flags/setTenantProperties.ts +++ b/backend/src/feature-flags/setTenantProperties.ts @@ -1,7 +1,11 @@ import { PostHog } from 'posthog-node' import { API_CONFIG, POSTHOG_CONFIG } from '../config' -export default async function setPosthogTenantProperties(tenant:any, posthog:PostHog, database:any) { +export default async function setPosthogTenantProperties( + tenant: any, + posthog: PostHog, + database: any, +) { if (POSTHOG_CONFIG.apiKey && API_CONFIG.edition === 'crowd-hosted') { const automationCount = await database.automation.count({ where: { From aec125016a7f3975d885a6d1d242391ffd953780 Mon Sep 17 00:00:00 2001 From: anilb Date: Tue, 6 Dec 2022 18:37:41 +0100 Subject: [PATCH 06/27] trial ends at date check --- backend/src/database/repositories/tenantRepository.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/database/repositories/tenantRepository.ts b/backend/src/database/repositories/tenantRepository.ts index b9b75da45d..e7ecdf4217 100644 --- a/backend/src/database/repositories/tenantRepository.ts +++ b/backend/src/database/repositories/tenantRepository.ts @@ -50,7 +50,9 @@ class TenantRepository { ]), plan: 'Growth', isTrialPlan: true, - trialEndsAt: moment().add(7, 'days').toISOString(), + trialEndsAt: moment().add(14, 'days').isAfter('2023-01-01') + ? moment().add(14, 'days').toISOString() + : '2023-01-15', createdById: currentUser.id, updatedById: currentUser.id, }, From b28306271a36baf79ca3421a643d98f05cb1ace2 Mon Sep 17 00:00:00 2001 From: anilb Date: Tue, 6 Dec 2022 18:54:12 +0100 Subject: [PATCH 07/27] formatting --- frontend/src/security/permissions.js | 1218 +++++++++++++------------- 1 file changed, 609 insertions(+), 609 deletions(-) diff --git a/frontend/src/security/permissions.js b/frontend/src/security/permissions.js index 793258f00f..579ba71171 100644 --- a/frontend/src/security/permissions.js +++ b/frontend/src/security/permissions.js @@ -11,622 +11,622 @@ const plans = Plans.values * actions within modules (ex: memberEdit, activityCreate, conversationCreate, etc) */ class Permissions { - static get values() { - return { - tenantEdit: { - id: 'tenantEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - tenantDestroy: { - id: 'tenantDestroy', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - planEdit: { - id: 'planEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - planRead: { - id: 'planRead', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - userEdit: { - id: 'userEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - userDestroy: { - id: 'userDestroy', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - userCreate: { - id: 'userCreate', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - userImport: { - id: 'userImport', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - userRead: { - id: 'userRead', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - userAutocomplete: { - id: 'userAutocomplete', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - auditLogRead: { - id: 'auditLogRead', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - settingsRead: { - id: 'settingsRead', - allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [ - storage.settingsBackgroundImages, - storage.settingsLogos - ] - }, - settingsEdit: { - id: 'settingsEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [ - storage.settingsBackgroundImages, - storage.settingsLogos - ] - }, - integrationImport: { - id: 'integrationImport', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - integrationCreate: { - id: 'integrationCreate', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - integrationEdit: { - id: 'integrationEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - integrationDestroy: { - id: 'integrationDestroy', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - integrationRead: { - id: 'integrationRead', - allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - integrationAutocomplete: { - id: 'integrationAutocomplete', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, + static get values() { + return { + tenantEdit: { + id: 'tenantEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + tenantDestroy: { + id: 'tenantDestroy', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + planEdit: { + id: 'planEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + planRead: { + id: 'planRead', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + userEdit: { + id: 'userEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + userDestroy: { + id: 'userDestroy', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + userCreate: { + id: 'userCreate', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + userImport: { + id: 'userImport', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + userRead: { + id: 'userRead', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + userAutocomplete: { + id: 'userAutocomplete', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + auditLogRead: { + id: 'auditLogRead', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + settingsRead: { + id: 'settingsRead', + allowedRoles: [roles.admin, roles.readonly], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [ + storage.settingsBackgroundImages, + storage.settingsLogos + ] + }, + settingsEdit: { + id: 'settingsEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [ + storage.settingsBackgroundImages, + storage.settingsLogos + ] + }, + integrationImport: { + id: 'integrationImport', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + integrationCreate: { + id: 'integrationCreate', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + integrationEdit: { + id: 'integrationEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + integrationDestroy: { + id: 'integrationDestroy', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + integrationRead: { + id: 'integrationRead', + allowedRoles: [roles.admin, roles.readonly], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + integrationAutocomplete: { + id: 'integrationAutocomplete', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, - reportImport: { - id: 'reportImport', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - reportCreate: { - id: 'reportCreate', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - reportEdit: { - id: 'reportEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - reportDestroy: { - id: 'reportDestroy', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - reportRead: { - id: 'reportRead', - allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - reportAutocomplete: { - id: 'reportAutocomplete', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, + reportImport: { + id: 'reportImport', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + reportCreate: { + id: 'reportCreate', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + reportEdit: { + id: 'reportEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + reportDestroy: { + id: 'reportDestroy', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + reportRead: { + id: 'reportRead', + allowedRoles: [roles.admin, roles.readonly], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + reportAutocomplete: { + id: 'reportAutocomplete', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, - memberImport: { - id: 'memberImport', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - memberCreate: { - id: 'memberCreate', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - memberEdit: { - id: 'memberEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - memberDestroy: { - id: 'memberDestroy', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - memberRead: { - id: 'memberRead', - allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - memberAutocomplete: { - id: 'memberAutocomplete', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, + memberImport: { + id: 'memberImport', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + memberCreate: { + id: 'memberCreate', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + memberEdit: { + id: 'memberEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + memberDestroy: { + id: 'memberDestroy', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + memberRead: { + id: 'memberRead', + allowedRoles: [roles.admin, roles.readonly], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + memberAutocomplete: { + id: 'memberAutocomplete', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, - organizationImport: { - id: 'organizationImport', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - organizationCreate: { - id: 'organizationCreate', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - organizationEdit: { - id: 'organizationEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - organizationDestroy: { - id: 'organizationDestroy', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - organizationRead: { - id: 'organizationRead', - allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - organizationAutocomplete: { - id: 'organizationAutocomplete', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, + organizationImport: { + id: 'organizationImport', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + organizationCreate: { + id: 'organizationCreate', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + organizationEdit: { + id: 'organizationEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + organizationDestroy: { + id: 'organizationDestroy', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + organizationRead: { + id: 'organizationRead', + allowedRoles: [roles.admin, roles.readonly], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + organizationAutocomplete: { + id: 'organizationAutocomplete', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, - activityImport: { - id: 'activityImport', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - activityCreate: { - id: 'activityCreate', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - activityEdit: { - id: 'activityEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - activityDestroy: { - id: 'activityDestroy', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - activityRead: { - id: 'activityRead', - allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - activityAutocomplete: { - id: 'activityAutocomplete', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, + activityImport: { + id: 'activityImport', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + activityCreate: { + id: 'activityCreate', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + activityEdit: { + id: 'activityEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + activityDestroy: { + id: 'activityDestroy', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + activityRead: { + id: 'activityRead', + allowedRoles: [roles.admin, roles.readonly], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + activityAutocomplete: { + id: 'activityAutocomplete', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, - taskImport: { - id: 'taskImport', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - taskCreate: { - id: 'taskCreate', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - taskEdit: { - id: 'taskEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - taskDestroy: { - id: 'taskDestroy', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - taskRead: { - id: 'taskRead', - allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - taskAutocomplete: { - id: 'taskAutocomplete', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, + taskImport: { + id: 'taskImport', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + taskCreate: { + id: 'taskCreate', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + taskEdit: { + id: 'taskEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + taskDestroy: { + id: 'taskDestroy', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + taskRead: { + id: 'taskRead', + allowedRoles: [roles.admin, roles.readonly], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + taskAutocomplete: { + id: 'taskAutocomplete', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, - conversationImport: { - id: 'conversationImport', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - conversationCreate: { - id: 'conversationCreate', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - conversationEdit: { - id: 'conversationEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - conversationDestroy: { - id: 'conversationDestroy', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - conversationRead: { - id: 'conversationRead', - allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - conversationCustomize: { - id: 'conversationCustomize', - allowedRoles: [roles.admin], - allowedPlans: [plans.growth, plans.enterprise] - }, - conversationAutocomplete: { - id: 'conversationAutocomplete', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - eagleEyeRead: { - id: 'eagleEyeRead', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - automationImport: { - id: 'automationImport', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - automationCreate: { - id: 'automationCreate', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - automationEdit: { - id: 'automationEdit', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - automationDestroy: { - id: 'automationDestroy', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ], - allowedStorage: [] - }, - automationRead: { - id: 'automationRead', - allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - }, - automationCustomize: { - id: 'automationCustomize', - allowedRoles: [roles.admin], - allowedPlans: [plans.growth, plans.enterprise] - }, - automationAutocomplete: { - id: 'automationAutocomplete', - allowedRoles: [roles.admin], - allowedPlans: [ - plans.essential, - plans.growth, - plans.enterprise - ] - } - } + conversationImport: { + id: 'conversationImport', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + conversationCreate: { + id: 'conversationCreate', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + conversationEdit: { + id: 'conversationEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + conversationDestroy: { + id: 'conversationDestroy', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + conversationRead: { + id: 'conversationRead', + allowedRoles: [roles.admin, roles.readonly], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + conversationCustomize: { + id: 'conversationCustomize', + allowedRoles: [roles.admin], + allowedPlans: [plans.growth, plans.enterprise] + }, + conversationAutocomplete: { + id: 'conversationAutocomplete', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + eagleEyeRead: { + id: 'eagleEyeRead', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + automationImport: { + id: 'automationImport', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + automationCreate: { + id: 'automationCreate', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + automationEdit: { + id: 'automationEdit', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + automationDestroy: { + id: 'automationDestroy', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ], + allowedStorage: [] + }, + automationRead: { + id: 'automationRead', + allowedRoles: [roles.admin, roles.readonly], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + }, + automationCustomize: { + id: 'automationCustomize', + allowedRoles: [roles.admin], + allowedPlans: [plans.growth, plans.enterprise] + }, + automationAutocomplete: { + id: 'automationAutocomplete', + allowedRoles: [roles.admin], + allowedPlans: [ + plans.essential, + plans.growth, + plans.enterprise + ] + } } + } - static get asArray() { - return Object.keys(this.values).map((value) => { - return this.values[value] - }) - } + static get asArray() { + return Object.keys(this.values).map((value) => { + return this.values[value] + }) + } } -export default Permissions \ No newline at end of file +export default Permissions From 0295cdcc2c58b13d97244b32cb27ba3093dcfa92 Mon Sep 17 00:00:00 2001 From: anilb Date: Tue, 6 Dec 2022 18:56:32 +0100 Subject: [PATCH 08/27] formatting --- frontend/src/security/plans.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/src/security/plans.js b/frontend/src/security/plans.js index a94dbb1af3..68e4094988 100644 --- a/frontend/src/security/plans.js +++ b/frontend/src/security/plans.js @@ -2,13 +2,13 @@ * List of Plans */ class Plans { - static get values() { - return { - essential: 'Essential', - growth: 'Growth', - enterprise: 'enterprise' - } + static get values() { + return { + essential: 'Essential', + growth: 'Growth', + enterprise: 'enterprise' } + } } -export default Plans \ No newline at end of file +export default Plans From 39d679bc08822fa254a503ac7bc6660b9fc1efe2 Mon Sep 17 00:00:00 2001 From: anilb Date: Tue, 6 Dec 2022 19:05:28 +0100 Subject: [PATCH 09/27] plans updated in all project --- .../integrationProtectedFields.test.ts | 8 +-- .../microserviceProtectedFields.test.ts | 61 +++---------------- backend/src/api/plan/stripe/checkout.ts | 2 +- backend/src/api/plan/stripe/portal.ts | 2 +- .../__tests__/tenantRepository.test.ts | 2 +- .../src/database/utils/sequelizeTestUtils.ts | 6 +- .../__tests__/conversationService.test.ts | 8 +-- .../services/premium/user/userDestroyer.ts | 2 +- .../src/services/premium/user/userEditor.ts | 2 +- .../user/__tests__/permissionChecker.test.ts | 17 ++---- 10 files changed, 28 insertions(+), 82 deletions(-) diff --git a/backend/src/api/integration/__tests__/integrationProtectedFields.test.ts b/backend/src/api/integration/__tests__/integrationProtectedFields.test.ts index 0e6d7d761b..4d44f320fe 100644 --- a/backend/src/api/integration/__tests__/integrationProtectedFields.test.ts +++ b/backend/src/api/integration/__tests__/integrationProtectedFields.test.ts @@ -19,7 +19,7 @@ describe('Integration protected fields tests', () => { describe('Integration create protected fields', () => { it('Should create an integratio without protected variables', async () => { - const plan = Plans.values.free + const plan = Plans.values.essential const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, plan) const tenantId = mockIServiceOptions.currentTenant.get({ plain: true }).id const token = SequelizeTestUtils.getUserToken(mockIServiceOptions) @@ -37,7 +37,7 @@ describe('Integration protected fields tests', () => { }) }) it('Should throw an error when limitCount is passed for a free plan', async () => { - const plan = Plans.values.free + const plan = Plans.values.essential const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, plan) const tenantId = mockIServiceOptions.currentTenant.get({ plain: true }).id const token = SequelizeTestUtils.getUserToken(mockIServiceOptions) @@ -59,7 +59,7 @@ describe('Integration protected fields tests', () => { describe('Integration update protected fields', () => { it('Should update an integratiom without protected variables', async () => { - const plan = Plans.values.free + const plan = Plans.values.essential const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, plan) const integrationId = ( await new IntegrationService(mockIServiceOptions).create({ @@ -83,7 +83,7 @@ describe('Integration protected fields tests', () => { }) }) it('Should throw an error when limitCount is passed', async () => { - const plan = Plans.values.free + const plan = Plans.values.essential const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, plan) const integrationId = ( await new IntegrationService(mockIServiceOptions).create({ diff --git a/backend/src/api/microservice/__tests__/microserviceProtectedFields.test.ts b/backend/src/api/microservice/__tests__/microserviceProtectedFields.test.ts index 670842b2e8..804239f545 100644 --- a/backend/src/api/microservice/__tests__/microserviceProtectedFields.test.ts +++ b/backend/src/api/microservice/__tests__/microserviceProtectedFields.test.ts @@ -18,7 +18,7 @@ describe('Microservice protected fields tests', () => { describe('Microservice create protected fields', () => { it('Should return 200 with free variant', async () => { - const plan = Plans.values.free + const plan = Plans.values.essential const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, plan) const tenantId = mockIServiceOptions.currentTenant.get({ plain: true }).id const token = SequelizeTestUtils.getUserToken(mockIServiceOptions) @@ -38,7 +38,7 @@ describe('Microservice protected fields tests', () => { }) it('Should return 403 with a premium microservice in a free tenant', async () => { - const plan = Plans.values.free + const plan = Plans.values.essential const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, plan) const tenantId = mockIServiceOptions.currentTenant.get({ plain: true }).id const token = SequelizeTestUtils.getUserToken(mockIServiceOptions) @@ -58,7 +58,7 @@ describe('Microservice protected fields tests', () => { }) it('Should return 200 with a premium microservice in a premium tenant', async () => { - const plan = Plans.values.premium + const plan = Plans.values.growth const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, plan) const tenantId = mockIServiceOptions.currentTenant.get({ plain: true }).id const token = SequelizeTestUtils.getUserToken(mockIServiceOptions) @@ -77,30 +77,11 @@ describe('Microservice protected fields tests', () => { }) }) - it('Should return 200 with a premium microservice in a beta tenant', async () => { - const plan = Plans.values.beta - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, plan) - const tenantId = mockIServiceOptions.currentTenant.get({ plain: true }).id - const token = SequelizeTestUtils.getUserToken(mockIServiceOptions) - const data = { - type: 'check_merge', - variant: 'premium', - } - return request(app) - .post(`/tenant/${tenantId}/microservice`) - .set({ - Authorization: `Bearer ${token}`, - }) - .send({ ...data }) - .then((response) => { - expect(response.statusCode).toBe(200) - }) - }) }) describe('Microservice update protected fields', () => { it('Should return 200 without protected variables', async () => { - const plan = Plans.values.free + const plan = Plans.values.essential const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, plan) const microServiceId = ( await new MicroserviceService(mockIServiceOptions).create({ @@ -126,7 +107,7 @@ describe('Microservice protected fields tests', () => { }) it('Should return 200 when updating with default variant', async () => { - const plan = Plans.values.free + const plan = Plans.values.essential const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, plan) const microServiceId = ( await new MicroserviceService(mockIServiceOptions).create({ @@ -153,7 +134,7 @@ describe('Microservice protected fields tests', () => { }) it('Should return 403 when updating with to premium variant in free tenant', async () => { - const plan = Plans.values.free + const plan = Plans.values.essential const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, plan) const microServiceId = ( await new MicroserviceService(mockIServiceOptions).create({ @@ -179,7 +160,7 @@ describe('Microservice protected fields tests', () => { }) it('Should return 200 when updating with premium variant in premium tenant', async () => { - const plan = Plans.values.premium + const plan = Plans.values.growth const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, plan) const microServiceId = ( await new MicroserviceService(mockIServiceOptions).create({ @@ -205,34 +186,8 @@ describe('Microservice protected fields tests', () => { }) }) - it('Should return 200 when updating with premium variant in beta tenant', async () => { - const plan = Plans.values.beta - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, plan) - const microServiceId = ( - await new MicroserviceService(mockIServiceOptions).create({ - type: 'check_merge', - running: false, - variant: 'default', - }) - ).id - const tenantId = mockIServiceOptions.currentTenant.get({ plain: true }).id - const token = SequelizeTestUtils.getUserToken(mockIServiceOptions) - const data = { - running: true, - variant: 'premium', - } - return request(app) - .put(`/tenant/${tenantId}/microservice/${microServiceId}`) - .set({ - Authorization: `Bearer ${token}`, - }) - .send({ ...data }) - .then((response) => { - expect(response.statusCode).toBe(200) - }) - }) it('Should return 200 when updating from premium to default in premium tenant', async () => { - const plan = Plans.values.premium + const plan = Plans.values.growth const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, plan) const microServiceId = ( await new MicroserviceService(mockIServiceOptions).create({ diff --git a/backend/src/api/plan/stripe/checkout.ts b/backend/src/api/plan/stripe/checkout.ts index ae5350c494..032a53b475 100644 --- a/backend/src/api/plan/stripe/checkout.ts +++ b/backend/src/api/plan/stripe/checkout.ts @@ -20,7 +20,7 @@ export default async (req, res) => { } if ( - currentTenant.plan !== Plans.values.free && + currentTenant.plan !== Plans.values.essential && currentTenant.planStatus !== 'cancel_at_period_end' && currentTenant.planUserId !== currentUser.id ) { diff --git a/backend/src/api/plan/stripe/portal.ts b/backend/src/api/plan/stripe/portal.ts index 7dc14cf6d9..8154775d10 100644 --- a/backend/src/api/plan/stripe/portal.ts +++ b/backend/src/api/plan/stripe/portal.ts @@ -20,7 +20,7 @@ export default async (req, res) => { } if ( - currentTenant.plan !== Plans.values.free && + currentTenant.plan !== Plans.values.essential && currentTenant.planStatus !== 'cancel_at_period_end' && currentTenant.planUserId !== currentUser.id ) { diff --git a/backend/src/database/repositories/__tests__/tenantRepository.test.ts b/backend/src/database/repositories/__tests__/tenantRepository.test.ts index c25c2b83c5..650b25f3b7 100644 --- a/backend/src/database/repositories/__tests__/tenantRepository.test.ts +++ b/backend/src/database/repositories/__tests__/tenantRepository.test.ts @@ -34,7 +34,7 @@ describe('TenantRepository tests', () => { await options.database.tenant.create({ name: tenantName, url: 'a-tenant-name', - plan: Plans.values.free, + plan: Plans.values.essential, }) // now generate function should return 'a-tenant-name-1' because it already exists diff --git a/backend/src/database/utils/sequelizeTestUtils.ts b/backend/src/database/utils/sequelizeTestUtils.ts index 0d1aa8890c..6ce6719494 100644 --- a/backend/src/database/utils/sequelizeTestUtils.ts +++ b/backend/src/database/utils/sequelizeTestUtils.ts @@ -53,7 +53,7 @@ export default class SequelizeTestUtils { return db } - static async getTestIServiceOptions(db, plan = Plans.values.free, tenantName?, tenantUrl?) { + static async getTestIServiceOptions(db, plan = Plans.values.essential, tenantName?, tenantUrl?) { db = await this.getDatabase(db) const randomTenant = @@ -134,11 +134,11 @@ export default class SequelizeTestUtils { } as IRepositoryOptions } - static getRandomTestTenant(plan = Plans.values.free) { + static getRandomTestTenant(plan = Plans.values.essential) { return this.getTenant(this.getRandomString('test-tenant'), this.getRandomString('url#'), plan) } - static getTenant(name, url, plan = Plans.values.free) { + static getTenant(name, url, plan = Plans.values.essential) { return { name, url, diff --git a/backend/src/services/__tests__/conversationService.test.ts b/backend/src/services/__tests__/conversationService.test.ts index 0ee96e7845..a1ef5c8ae9 100644 --- a/backend/src/services/__tests__/conversationService.test.ts +++ b/backend/src/services/__tests__/conversationService.test.ts @@ -183,7 +183,7 @@ describe('ConversationService tests', () => { it('Should create a document representation of a conversation in the search engine', async () => { let mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions( db, - Plans.values.free, + Plans.values.essential, 'a tenant name', '#tenantUrl', ) @@ -623,7 +623,7 @@ describe('ConversationService tests', () => { it('Should remove a conversation document from the search engine', async () => { let mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions( db, - Plans.values.free, + Plans.values.essential, 'a tenant name', '#tenantUrl', ) @@ -737,7 +737,7 @@ describe('ConversationService tests', () => { it('Should publish/unpublish a conversation to search engine when published=true/false is sent', async () => { let mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions( db, - Plans.values.free, + Plans.values.essential, 'a tenant name', '#tenantUrl', ) @@ -847,7 +847,7 @@ describe('ConversationService tests', () => { it('Should update settings in various entities and push these settings to search engine', async () => { let mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions( db, - Plans.values.free, + Plans.values.essential, 'a tenant name', '#tenantUrl', ) diff --git a/backend/src/services/premium/user/userDestroyer.ts b/backend/src/services/premium/user/userDestroyer.ts index 7ffe24675b..289dc549d1 100644 --- a/backend/src/services/premium/user/userDestroyer.ts +++ b/backend/src/services/premium/user/userDestroyer.ts @@ -65,7 +65,7 @@ export default class UserDestroyer { async _isRemovingPlanUser() { const { currentTenant } = this.options - if (currentTenant.plan === Plans.values.free) { + if (currentTenant.plan === Plans.values.essential) { return false } diff --git a/backend/src/services/premium/user/userEditor.ts b/backend/src/services/premium/user/userEditor.ts index c8e18c620d..2f63c69d0b 100644 --- a/backend/src/services/premium/user/userEditor.ts +++ b/backend/src/services/premium/user/userEditor.ts @@ -86,7 +86,7 @@ export default class UserEditor { const { currentTenant } = this.options - if (currentTenant.plan === Plans.values.free) { + if (currentTenant.plan === Plans.values.essential) { return false } diff --git a/backend/src/services/user/__tests__/permissionChecker.test.ts b/backend/src/services/user/__tests__/permissionChecker.test.ts index 4fdfeab407..2e3231005b 100644 --- a/backend/src/services/user/__tests__/permissionChecker.test.ts +++ b/backend/src/services/user/__tests__/permissionChecker.test.ts @@ -51,7 +51,7 @@ describe('IntegrationService tests', () => { describe('Microservice protected fields', () => { it('Should throw an error when premium is passed for a free tenant', async () => { - const plan = Plans.values.free + const plan = Plans.values.essential const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, plan) const permissionChecker = new PermissionChecker(mockIServiceOptions) const data = { @@ -64,17 +64,7 @@ describe('IntegrationService tests', () => { }) it('Should pass when premium is passed for a premium tenant', async () => { - const plan = Plans.values.premium - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, plan) - const permissionChecker = new PermissionChecker(mockIServiceOptions) - const data = { - name: 'check_merge', - variant: 'premium', - } - expect(permissionChecker.validateMicroservicesProtectedFields(data)).toBeUndefined() - }) - it('Should pass when premium is passed for a beta tenant', async () => { - const plan = Plans.values.beta + const plan = Plans.values.growth const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, plan) const permissionChecker = new PermissionChecker(mockIServiceOptions) const data = { @@ -83,6 +73,7 @@ describe('IntegrationService tests', () => { } expect(permissionChecker.validateMicroservicesProtectedFields(data)).toBeUndefined() }) + it('Should always pass when free variant is passed', async () => { for (const plan of Object.values(Plans.values)) { const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, plan) @@ -95,7 +86,7 @@ describe('IntegrationService tests', () => { } }) it('Should throw an error for a wrong variant', async () => { - const plan = Plans.values.free + const plan = Plans.values.essential const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, plan) const permissionChecker = new PermissionChecker(mockIServiceOptions) const data = { From 074471dd25a1352c1066e9c4cfb7990ab76dd98d Mon Sep 17 00:00:00 2001 From: anilb Date: Tue, 6 Dec 2022 19:05:54 +0100 Subject: [PATCH 10/27] formatting --- .../microservice/__tests__/microserviceProtectedFields.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/api/microservice/__tests__/microserviceProtectedFields.test.ts b/backend/src/api/microservice/__tests__/microserviceProtectedFields.test.ts index 804239f545..1da65c16a5 100644 --- a/backend/src/api/microservice/__tests__/microserviceProtectedFields.test.ts +++ b/backend/src/api/microservice/__tests__/microserviceProtectedFields.test.ts @@ -76,7 +76,6 @@ describe('Microservice protected fields tests', () => { expect(response.statusCode).toBe(200) }) }) - }) describe('Microservice update protected fields', () => { From 3083a7d962de3a37562c55fdc046daa8a2f727aa Mon Sep 17 00:00:00 2001 From: anilb Date: Tue, 6 Dec 2022 20:44:04 +0100 Subject: [PATCH 11/27] posthog not initialized if key is not present --- backend/src/api/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/src/api/index.ts b/backend/src/api/index.ts index 9c9eac5f86..3a6da92e39 100644 --- a/backend/src/api/index.ts +++ b/backend/src/api/index.ts @@ -26,7 +26,11 @@ const app = express() setImmediate(async () => { const redis = await createRedisClient(true) - const posthog = new PostHog(POSTHOG_CONFIG.apiKey) + let posthog = null + + if (POSTHOG_CONFIG.apiKey){ + posthog = new PostHog(POSTHOG_CONFIG.apiKey) + } // Enables CORS app.use(cors({ origin: true })) From 07009d0d0063e56eca2f23e438e7f04c8710b1dd Mon Sep 17 00:00:00 2001 From: anilb Date: Wed, 7 Dec 2022 10:11:21 +0100 Subject: [PATCH 12/27] formatting --- backend/src/api/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/api/index.ts b/backend/src/api/index.ts index 3a6da92e39..5931fb168d 100644 --- a/backend/src/api/index.ts +++ b/backend/src/api/index.ts @@ -28,7 +28,7 @@ setImmediate(async () => { let posthog = null - if (POSTHOG_CONFIG.apiKey){ + if (POSTHOG_CONFIG.apiKey) { posthog = new PostHog(POSTHOG_CONFIG.apiKey) } From d93a71661d60779aebb8c0fef373ab96f9746974 Mon Sep 17 00:00:00 2001 From: anilb Date: Wed, 7 Dec 2022 11:09:07 +0100 Subject: [PATCH 13/27] tests are passing --- .../services/__tests__/memberService.test.ts | 6 +++- .../__tests__/organizationService.test.ts | 28 +++++++++++++++---- .../services/__tests__/tenantService.test.ts | 5 +++- backend/src/services/organizationService.ts | 3 +- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/backend/src/services/__tests__/memberService.test.ts b/backend/src/services/__tests__/memberService.test.ts index ae9e70f46c..aa658b32b2 100644 --- a/backend/src/services/__tests__/memberService.test.ts +++ b/backend/src/services/__tests__/memberService.test.ts @@ -20,6 +20,7 @@ import { AttributeType } from '../../database/attributes/types' import { SlackMemberAttributes } from '../../database/attributes/member/slack' import SettingsRepository from '../../database/repositories/settingsRepository' import OrganizationService from '../organizationService' +import Plans from '../../security/plans' const db = null @@ -652,7 +653,10 @@ describe('MemberService tests', () => { }) it('Should create non existent member - organization with enrichment', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, 'premium') + const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions( + db, + Plans.values.growth, + ) const member1 = { username: 'anil', diff --git a/backend/src/services/__tests__/organizationService.test.ts b/backend/src/services/__tests__/organizationService.test.ts index 65437c39ac..1d546c384f 100644 --- a/backend/src/services/__tests__/organizationService.test.ts +++ b/backend/src/services/__tests__/organizationService.test.ts @@ -1,5 +1,6 @@ import organizationCacheRepository from '../../database/repositories/organizationCacheRepository' import SequelizeTestUtils from '../../database/utils/sequelizeTestUtils' +import Plans from '../../security/plans' import OrganizationService from '../organizationService' const db = null @@ -49,7 +50,10 @@ describe('OrganizationService tests', () => { describe('Create method', () => { it('Should add without enriching when enrichP is false', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, 'premium') + const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions( + db, + Plans.values.growth, + ) const service = new OrganizationService(mockIServiceOptions) const toAdd = { @@ -60,7 +64,7 @@ describe('OrganizationService tests', () => { expect(added.url).toEqual(null) }) - it('Should add without enriching when tenant is not premium', async () => { + it('Should add without enriching when tenant is not growth', async () => { const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db) const service = new OrganizationService(mockIServiceOptions) @@ -73,7 +77,10 @@ describe('OrganizationService tests', () => { }) it('Should enrich and add an organization by name', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, 'premium') + const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions( + db, + Plans.values.growth, + ) const service = new OrganizationService(mockIServiceOptions) const toAdd = { @@ -117,8 +124,14 @@ describe('OrganizationService tests', () => { }) it('Should not re-enrich when the record is already in the cache table. By Name', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, 'premium') - const mockIServiceOptions2 = await SequelizeTestUtils.getTestIServiceOptions(db, 'premium') + const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions( + db, + Plans.values.growth, + ) + const mockIServiceOptions2 = await SequelizeTestUtils.getTestIServiceOptions( + db, + Plans.values.growth, + ) const service = new OrganizationService(mockIServiceOptions) const service2 = new OrganizationService(mockIServiceOptions2) @@ -185,7 +198,10 @@ describe('OrganizationService tests', () => { }) it('Should throw an error when name is not sent', async () => { - const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions(db, 'premium') + const mockIServiceOptions = await SequelizeTestUtils.getTestIServiceOptions( + db, + Plans.values.growth, + ) const service = new OrganizationService(mockIServiceOptions) const toAdd = {} diff --git a/backend/src/services/__tests__/tenantService.test.ts b/backend/src/services/__tests__/tenantService.test.ts index 50e0a930ed..176c618c13 100644 --- a/backend/src/services/__tests__/tenantService.test.ts +++ b/backend/src/services/__tests__/tenantService.test.ts @@ -7,6 +7,7 @@ import { PlatformType } from '../../types/integrationEnums' import MemberAttributeSettingsService from '../memberAttributeSettingsService' import { MemberAttributeName } from '../../database/attributes/member/enums' import TaskService from '../taskService' +import Plans from '../../security/plans' const db = null @@ -149,7 +150,9 @@ describe('TenantService tests', () => { id: tenantCreatedPlain.id, name: 'testName', url: 'testUrl', - plan: 'free', + plan: Plans.values.growth, + isTrialPlan: true, + trialEndsAt: new Date('2023-01-15T00:00:00.000Z'), planStatus: 'active', planStripeCustomerId: null, planUserId: null, diff --git a/backend/src/services/organizationService.ts b/backend/src/services/organizationService.ts index e29b60dfbe..a834b94417 100644 --- a/backend/src/services/organizationService.ts +++ b/backend/src/services/organizationService.ts @@ -8,6 +8,7 @@ import telemetryTrack from '../segment/telemetryTrack' import organizationCacheRepository from '../database/repositories/organizationCacheRepository' import { enrichOrganization } from './helpers/enrichment' import { LoggingBase } from './loggingBase' +import Plans from '../security/plans' export default class OrganizationService extends LoggingBase { options: IServiceOptions @@ -18,7 +19,7 @@ export default class OrganizationService extends LoggingBase { } async shouldEnrich(enrichP) { - const isPremium = this.options.currentTenant.plan === 'premium' + const isPremium = this.options.currentTenant.plan === Plans.values.growth if (!isPremium) { return false } From 2c77407d35f1b4c64ed5bf8ad2ab36d30d427859 Mon Sep 17 00:00:00 2001 From: anilb Date: Wed, 7 Dec 2022 11:12:02 +0100 Subject: [PATCH 14/27] fix package json typo --- backend/package.json | 258 +++++++++++++++++++++---------------------- 1 file changed, 129 insertions(+), 129 deletions(-) diff --git a/backend/package.json b/backend/package.json index fc46fc6f37..c81ce516f2 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,130 +1,130 @@ { - "name": "app-backend", - "description": "Backend", - "scripts": { - "start:api": "ts-node --transpile-only ./src/bin/api.ts", - "start:api:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/api.ts\"", - "start:api:dev:local": "set -a && . ./.env.dist.local && . ./.env.override.local && set +a && SERVICE=api nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/api.ts\"", - "start:job-generator": "ts-node --transpile-only ./src/bin/job-generator.ts", - "start:job-generator:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/job-generator.ts\"", - "start:job-generator:dev:local": "set -a && . ./.env.dist.local && . ./.env.override.local && set +a && SERVICE=job-generator nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/job-generator.ts\"", - "start:nodejs-worker": "ts-node --transpile-only ./src/bin/nodejs-worker.ts", - "start:nodejs-worker:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/nodejs-worker.ts\"", - "start:nodejs-worker:dev:local": " && SERVICE=nodejs-worker nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"node --inspect=0.0.0.0:9229 -r ts-node/register ./src/bin/nodejs-worker.ts --transpile-only\"", - "build": "tsc && npm run build:documentation && cp package*json dist/ && cp .sequelizerc dist/.sequelizerc ", - "test": "../scripts/cli scaffold up-test && jest --clearCache && set -a && . ./.env.dist.local && . ./.env.test && set +a && NODE_ENV=test SERVICE=test jest --runInBand --verbose --forceExit", - "build:documentation": "copyfiles --flat ./src/documentation/openapi.json ./dist/documentation/", - "db:create:test": "npx ts-node ./src/database/initializers/create test", - "db:create:dev:source": "ts-node ./src/database/initializers/create dev", - "db:seed:test": "npx ts-node ./src/database/initializers/seed test", - "db:seed:dev": "npx ts-node ./src/database/initializers/seed dev", - "db:publish": "bash ./util/publish-db.sh", - "docs": "bash ./util/publish-docs.sh", - "sequelize-cli:source": "npm run build && npx sequelize --config src/database/sequelize-cli-config.ts --migrations-source-path src/database/migrations", - "sequelize-cli:build": "npx sequelize --config database/sequelize-cli-config.js --migrations-compiled-path database/migrations", - "stripe:login": "stripe login", - "stripe:start": "stripe listen --forward-to localhost:8080/api/plan/stripe/webhook", - "lint": "npx eslint .", - "format": "npx prettier --write .", - "script:process-integration": "SERVICE=script ts-node ./src/bin/scripts/process-integration.ts", - "script:change-tenant-plan": "SERVICE=script ts-node ./src/bin/scripts/change-tenant-plan.ts" - }, - "dependencies": { - "@aws-sdk/client-comprehend": "^3.159.0", - "@cubejs-client/core": "^0.30.4", - "@google-cloud/storage": "5.3.0", - "@octokit/auth-app": "^3.6.1", - "@octokit/graphql": "^4.8.0", - "@octokit/request": "^5.6.3", - "@pm2/io": "^5.0.0", - "@sendgrid/mail": "7.2.6", - "@slack/web-api": "^6.7.2", - "@superfaceai/one-sdk": "^1.3.0", - "@superfaceai/passport-twitter-oauth2": "^1.0.0", - "analytics-node": "^6.2.0", - "aws-sdk": "2.814.0", - "axios": "^0.27.2", - "bcrypt": "5.0.0", - "bunyan": "^1.8.15", - "bunyan-format": "^0.2.1", - "bunyan-middleware": "^1.0.2", - "clearbit": "^1.3.5", - "cli-highlight": "2.1.6", - "command-line-args": "^5.2.1", - "command-line-usage": "^6.1.3", - "config": "^3.3.8", - "cors": "2.8.5", - "cron": "^2.1.0", - "cron-time-generator": "^1.3.0", - "crypto-js": "^4.1.1", - "dotenv": "8.2.0", - "dotenv-expand": "^8.0.3", - "emoji-dictionary": "^1.0.11", - "express": "4.17.1", - "express-rate-limit": "6.5.1", - "formidable-serverless": "1.1.1", - "helmet": "4.1.1", - "html-to-text": "^8.2.1", - "jsonwebtoken": "8.5.1", - "lodash": "4.17.21", - "meilisearch": "^0.26.0", - "moment": "2.29.4", - "moment-timezone": "^0.5.34", - "mv": "2.1.1", - "node-fetch": "^2.6.7", - "omit-deep-by-values": "^1.0.2", - "openapi-comment-parser": "^1.0.0", - "passport": "0.6.0", - "passport-facebook": "3.0.0", - "passport-google-oauth": "2.0.0", - "passport-google-oauth20": "^2.0.0", - "passport-slack": "0.0.7", - "pg": "^8.7.3", - "pm2": "^5.2.0", - "posthog-node": "^2.2.3", - "redis": "^4.5.0", - "sanitize-html": "^2.7.1", - "sequelize": "6.21.2", - "sequelize-cli-typescript": "^3.2.0-c", - "stripe": "10.0.0", - "superagent": "^8.0.0", - "swagger-ui-dist": "4.1.3", - "uuid": "^8.3.2", - "validator": "^13.7.0", - "verify-github-webhook": "^1.0.1" - }, - "private": true, - "devDependencies": { - "@babel/plugin-transform-runtime": "^7.18.10", - "@babel/preset-env": "^7.16.10", - "@types/bunyan": "^1.8.8", - "@types/bunyan-format": "^0.2.5", - "@types/config": "^3.3.0", - "@types/cron": "^2.0.0", - "@types/html-to-text": "^8.1.1", - "@types/jest": "^27.4.0", - "@types/node": "^17.0.21", - "@types/sanitize-html": "^2.6.2", - "@types/superagent": "^4.1.15", - "@typescript-eslint/eslint-plugin": "^5.17.0", - "@typescript-eslint/parser": "^5.17.0", - "copyfiles": "2.4.1", - "cross-env": "7.0.2", - "eslint": "^8.12.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-config-airbnb-typescript": "^16.1.4", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-import": "^2.25.4", - "eslint-plugin-openapi": "^0.0.4", - "jest": "^27.4.7", - "mocha": "8.1.3", - "node-mocks-http": "1.9.0", - "nodemon": "2.0.4", - "prettier": "^2.5.1", - "rdme": "^7.2.0", - "supertest": "^6.2.2", - "ts-jest": "^27.1.3", - "ts-node": "10.6.0", - "typescript": "^4.7.4" - } -} + "name": "app-backend", + "description": "Backend", + "scripts": { + "start:api": "ts-node --transpile-only ./src/bin/api.ts", + "start:api:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/api.ts\"", + "start:api:dev:local": "set -a && . ./.env.dist.local && . ./.env.override.local && set +a && SERVICE=api nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/api.ts\"", + "start:job-generator": "ts-node --transpile-only ./src/bin/job-generator.ts", + "start:job-generator:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/job-generator.ts\"", + "start:job-generator:dev:local": "set -a && . ./.env.dist.local && . ./.env.override.local && set +a && SERVICE=job-generator nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/job-generator.ts\"", + "start:nodejs-worker": "ts-node --transpile-only ./src/bin/nodejs-worker.ts", + "start:nodejs-worker:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/nodejs-worker.ts\"", + "start:nodejs-worker:dev:local": "set -a && . ./.env.dist.local && . ./.env.override.local && set +a && SERVICE=nodejs-worker nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"node --inspect=0.0.0.0:9229 -r ts-node/register ./src/bin/nodejs-worker.ts --transpile-only\"", + "build": "tsc && npm run build:documentation && cp package*json dist/ && cp .sequelizerc dist/.sequelizerc ", + "test": "../scripts/cli scaffold up-test && jest --clearCache && set -a && . ./.env.dist.local && . ./.env.test && set +a && NODE_ENV=test SERVICE=test jest --runInBand --verbose --forceExit", + "build:documentation": "copyfiles --flat ./src/documentation/openapi.json ./dist/documentation/", + "db:create:test": "npx ts-node ./src/database/initializers/create test", + "db:create:dev:source": "ts-node ./src/database/initializers/create dev", + "db:seed:test": "npx ts-node ./src/database/initializers/seed test", + "db:seed:dev": "npx ts-node ./src/database/initializers/seed dev", + "db:publish": "bash ./util/publish-db.sh", + "docs": "bash ./util/publish-docs.sh", + "sequelize-cli:source": "npm run build && npx sequelize --config src/database/sequelize-cli-config.ts --migrations-source-path src/database/migrations", + "sequelize-cli:build": "npx sequelize --config database/sequelize-cli-config.js --migrations-compiled-path database/migrations", + "stripe:login": "stripe login", + "stripe:start": "stripe listen --forward-to localhost:8080/api/plan/stripe/webhook", + "lint": "npx eslint .", + "format": "npx prettier --write .", + "script:process-integration": "SERVICE=script ts-node ./src/bin/scripts/process-integration.ts", + "script:change-tenant-plan": "SERVICE=script ts-node ./src/bin/scripts/change-tenant-plan.ts" + }, + "dependencies": { + "@aws-sdk/client-comprehend": "^3.159.0", + "@cubejs-client/core": "^0.30.4", + "@google-cloud/storage": "5.3.0", + "@octokit/auth-app": "^3.6.1", + "@octokit/graphql": "^4.8.0", + "@octokit/request": "^5.6.3", + "@pm2/io": "^5.0.0", + "@sendgrid/mail": "7.2.6", + "@slack/web-api": "^6.7.2", + "@superfaceai/one-sdk": "^1.3.0", + "@superfaceai/passport-twitter-oauth2": "^1.0.0", + "analytics-node": "^6.2.0", + "aws-sdk": "2.814.0", + "axios": "^0.27.2", + "bcrypt": "5.0.0", + "bunyan": "^1.8.15", + "bunyan-format": "^0.2.1", + "bunyan-middleware": "^1.0.2", + "clearbit": "^1.3.5", + "cli-highlight": "2.1.6", + "command-line-args": "^5.2.1", + "command-line-usage": "^6.1.3", + "config": "^3.3.8", + "cors": "2.8.5", + "cron": "^2.1.0", + "cron-time-generator": "^1.3.0", + "crypto-js": "^4.1.1", + "dotenv": "8.2.0", + "dotenv-expand": "^8.0.3", + "emoji-dictionary": "^1.0.11", + "express": "4.17.1", + "express-rate-limit": "6.5.1", + "formidable-serverless": "1.1.1", + "helmet": "4.1.1", + "html-to-text": "^8.2.1", + "jsonwebtoken": "8.5.1", + "lodash": "4.17.21", + "meilisearch": "^0.26.0", + "moment": "2.29.4", + "moment-timezone": "^0.5.34", + "mv": "2.1.1", + "node-fetch": "^2.6.7", + "omit-deep-by-values": "^1.0.2", + "openapi-comment-parser": "^1.0.0", + "passport": "0.6.0", + "passport-facebook": "3.0.0", + "passport-google-oauth": "2.0.0", + "passport-google-oauth20": "^2.0.0", + "passport-slack": "0.0.7", + "pg": "^8.7.3", + "pm2": "^5.2.0", + "posthog-node": "^2.2.3", + "redis": "^4.5.0", + "sanitize-html": "^2.7.1", + "sequelize": "6.21.2", + "sequelize-cli-typescript": "^3.2.0-c", + "stripe": "10.0.0", + "superagent": "^8.0.0", + "swagger-ui-dist": "4.1.3", + "uuid": "^8.3.2", + "validator": "^13.7.0", + "verify-github-webhook": "^1.0.1" + }, + "private": true, + "devDependencies": { + "@babel/plugin-transform-runtime": "^7.18.10", + "@babel/preset-env": "^7.16.10", + "@types/bunyan": "^1.8.8", + "@types/bunyan-format": "^0.2.5", + "@types/config": "^3.3.0", + "@types/cron": "^2.0.0", + "@types/html-to-text": "^8.1.1", + "@types/jest": "^27.4.0", + "@types/node": "^17.0.21", + "@types/sanitize-html": "^2.6.2", + "@types/superagent": "^4.1.15", + "@typescript-eslint/eslint-plugin": "^5.17.0", + "@typescript-eslint/parser": "^5.17.0", + "copyfiles": "2.4.1", + "cross-env": "7.0.2", + "eslint": "^8.12.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^16.1.4", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-openapi": "^0.0.4", + "jest": "^27.4.7", + "mocha": "8.1.3", + "node-mocks-http": "1.9.0", + "nodemon": "2.0.4", + "prettier": "^2.5.1", + "rdme": "^7.2.0", + "supertest": "^6.2.2", + "ts-jest": "^27.1.3", + "ts-node": "10.6.0", + "typescript": "^4.7.4" + } +} \ No newline at end of file From 268f13c3352a4d8d933f2808eb334bcbc9824a60 Mon Sep 17 00:00:00 2001 From: anilb Date: Wed, 7 Dec 2022 11:25:31 +0100 Subject: [PATCH 15/27] formatting --- backend/package.json | 258 +++++++++++++++++++++---------------------- 1 file changed, 129 insertions(+), 129 deletions(-) diff --git a/backend/package.json b/backend/package.json index c81ce516f2..e7462fab75 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,130 +1,130 @@ { - "name": "app-backend", - "description": "Backend", - "scripts": { - "start:api": "ts-node --transpile-only ./src/bin/api.ts", - "start:api:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/api.ts\"", - "start:api:dev:local": "set -a && . ./.env.dist.local && . ./.env.override.local && set +a && SERVICE=api nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/api.ts\"", - "start:job-generator": "ts-node --transpile-only ./src/bin/job-generator.ts", - "start:job-generator:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/job-generator.ts\"", - "start:job-generator:dev:local": "set -a && . ./.env.dist.local && . ./.env.override.local && set +a && SERVICE=job-generator nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/job-generator.ts\"", - "start:nodejs-worker": "ts-node --transpile-only ./src/bin/nodejs-worker.ts", - "start:nodejs-worker:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/nodejs-worker.ts\"", - "start:nodejs-worker:dev:local": "set -a && . ./.env.dist.local && . ./.env.override.local && set +a && SERVICE=nodejs-worker nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"node --inspect=0.0.0.0:9229 -r ts-node/register ./src/bin/nodejs-worker.ts --transpile-only\"", - "build": "tsc && npm run build:documentation && cp package*json dist/ && cp .sequelizerc dist/.sequelizerc ", - "test": "../scripts/cli scaffold up-test && jest --clearCache && set -a && . ./.env.dist.local && . ./.env.test && set +a && NODE_ENV=test SERVICE=test jest --runInBand --verbose --forceExit", - "build:documentation": "copyfiles --flat ./src/documentation/openapi.json ./dist/documentation/", - "db:create:test": "npx ts-node ./src/database/initializers/create test", - "db:create:dev:source": "ts-node ./src/database/initializers/create dev", - "db:seed:test": "npx ts-node ./src/database/initializers/seed test", - "db:seed:dev": "npx ts-node ./src/database/initializers/seed dev", - "db:publish": "bash ./util/publish-db.sh", - "docs": "bash ./util/publish-docs.sh", - "sequelize-cli:source": "npm run build && npx sequelize --config src/database/sequelize-cli-config.ts --migrations-source-path src/database/migrations", - "sequelize-cli:build": "npx sequelize --config database/sequelize-cli-config.js --migrations-compiled-path database/migrations", - "stripe:login": "stripe login", - "stripe:start": "stripe listen --forward-to localhost:8080/api/plan/stripe/webhook", - "lint": "npx eslint .", - "format": "npx prettier --write .", - "script:process-integration": "SERVICE=script ts-node ./src/bin/scripts/process-integration.ts", - "script:change-tenant-plan": "SERVICE=script ts-node ./src/bin/scripts/change-tenant-plan.ts" - }, - "dependencies": { - "@aws-sdk/client-comprehend": "^3.159.0", - "@cubejs-client/core": "^0.30.4", - "@google-cloud/storage": "5.3.0", - "@octokit/auth-app": "^3.6.1", - "@octokit/graphql": "^4.8.0", - "@octokit/request": "^5.6.3", - "@pm2/io": "^5.0.0", - "@sendgrid/mail": "7.2.6", - "@slack/web-api": "^6.7.2", - "@superfaceai/one-sdk": "^1.3.0", - "@superfaceai/passport-twitter-oauth2": "^1.0.0", - "analytics-node": "^6.2.0", - "aws-sdk": "2.814.0", - "axios": "^0.27.2", - "bcrypt": "5.0.0", - "bunyan": "^1.8.15", - "bunyan-format": "^0.2.1", - "bunyan-middleware": "^1.0.2", - "clearbit": "^1.3.5", - "cli-highlight": "2.1.6", - "command-line-args": "^5.2.1", - "command-line-usage": "^6.1.3", - "config": "^3.3.8", - "cors": "2.8.5", - "cron": "^2.1.0", - "cron-time-generator": "^1.3.0", - "crypto-js": "^4.1.1", - "dotenv": "8.2.0", - "dotenv-expand": "^8.0.3", - "emoji-dictionary": "^1.0.11", - "express": "4.17.1", - "express-rate-limit": "6.5.1", - "formidable-serverless": "1.1.1", - "helmet": "4.1.1", - "html-to-text": "^8.2.1", - "jsonwebtoken": "8.5.1", - "lodash": "4.17.21", - "meilisearch": "^0.26.0", - "moment": "2.29.4", - "moment-timezone": "^0.5.34", - "mv": "2.1.1", - "node-fetch": "^2.6.7", - "omit-deep-by-values": "^1.0.2", - "openapi-comment-parser": "^1.0.0", - "passport": "0.6.0", - "passport-facebook": "3.0.0", - "passport-google-oauth": "2.0.0", - "passport-google-oauth20": "^2.0.0", - "passport-slack": "0.0.7", - "pg": "^8.7.3", - "pm2": "^5.2.0", - "posthog-node": "^2.2.3", - "redis": "^4.5.0", - "sanitize-html": "^2.7.1", - "sequelize": "6.21.2", - "sequelize-cli-typescript": "^3.2.0-c", - "stripe": "10.0.0", - "superagent": "^8.0.0", - "swagger-ui-dist": "4.1.3", - "uuid": "^8.3.2", - "validator": "^13.7.0", - "verify-github-webhook": "^1.0.1" - }, - "private": true, - "devDependencies": { - "@babel/plugin-transform-runtime": "^7.18.10", - "@babel/preset-env": "^7.16.10", - "@types/bunyan": "^1.8.8", - "@types/bunyan-format": "^0.2.5", - "@types/config": "^3.3.0", - "@types/cron": "^2.0.0", - "@types/html-to-text": "^8.1.1", - "@types/jest": "^27.4.0", - "@types/node": "^17.0.21", - "@types/sanitize-html": "^2.6.2", - "@types/superagent": "^4.1.15", - "@typescript-eslint/eslint-plugin": "^5.17.0", - "@typescript-eslint/parser": "^5.17.0", - "copyfiles": "2.4.1", - "cross-env": "7.0.2", - "eslint": "^8.12.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-config-airbnb-typescript": "^16.1.4", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-import": "^2.25.4", - "eslint-plugin-openapi": "^0.0.4", - "jest": "^27.4.7", - "mocha": "8.1.3", - "node-mocks-http": "1.9.0", - "nodemon": "2.0.4", - "prettier": "^2.5.1", - "rdme": "^7.2.0", - "supertest": "^6.2.2", - "ts-jest": "^27.1.3", - "ts-node": "10.6.0", - "typescript": "^4.7.4" - } -} \ No newline at end of file + "name": "app-backend", + "description": "Backend", + "scripts": { + "start:api": "ts-node --transpile-only ./src/bin/api.ts", + "start:api:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/api.ts\"", + "start:api:dev:local": "set -a && . ./.env.dist.local && . ./.env.override.local && set +a && SERVICE=api nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/api.ts\"", + "start:job-generator": "ts-node --transpile-only ./src/bin/job-generator.ts", + "start:job-generator:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/job-generator.ts\"", + "start:job-generator:dev:local": "set -a && . ./.env.dist.local && . ./.env.override.local && set +a && SERVICE=job-generator nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/job-generator.ts\"", + "start:nodejs-worker": "ts-node --transpile-only ./src/bin/nodejs-worker.ts", + "start:nodejs-worker:dev": "nodemon --watch \"src/**/*.ts\" -e ts,json --exec \"ts-node --transpile-only ./src/bin/nodejs-worker.ts\"", + "start:nodejs-worker:dev:local": "set -a && . ./.env.dist.local && . ./.env.override.local && set +a && SERVICE=nodejs-worker nodemon --signal SIGKILL --watch \"src/**/*.ts\" -e ts,json --exec \"node --inspect=0.0.0.0:9229 -r ts-node/register ./src/bin/nodejs-worker.ts --transpile-only\"", + "build": "tsc && npm run build:documentation && cp package*json dist/ && cp .sequelizerc dist/.sequelizerc ", + "test": "../scripts/cli scaffold up-test && jest --clearCache && set -a && . ./.env.dist.local && . ./.env.test && set +a && NODE_ENV=test SERVICE=test jest --runInBand --verbose --forceExit", + "build:documentation": "copyfiles --flat ./src/documentation/openapi.json ./dist/documentation/", + "db:create:test": "npx ts-node ./src/database/initializers/create test", + "db:create:dev:source": "ts-node ./src/database/initializers/create dev", + "db:seed:test": "npx ts-node ./src/database/initializers/seed test", + "db:seed:dev": "npx ts-node ./src/database/initializers/seed dev", + "db:publish": "bash ./util/publish-db.sh", + "docs": "bash ./util/publish-docs.sh", + "sequelize-cli:source": "npm run build && npx sequelize --config src/database/sequelize-cli-config.ts --migrations-source-path src/database/migrations", + "sequelize-cli:build": "npx sequelize --config database/sequelize-cli-config.js --migrations-compiled-path database/migrations", + "stripe:login": "stripe login", + "stripe:start": "stripe listen --forward-to localhost:8080/api/plan/stripe/webhook", + "lint": "npx eslint .", + "format": "npx prettier --write .", + "script:process-integration": "SERVICE=script ts-node ./src/bin/scripts/process-integration.ts", + "script:change-tenant-plan": "SERVICE=script ts-node ./src/bin/scripts/change-tenant-plan.ts" + }, + "dependencies": { + "@aws-sdk/client-comprehend": "^3.159.0", + "@cubejs-client/core": "^0.30.4", + "@google-cloud/storage": "5.3.0", + "@octokit/auth-app": "^3.6.1", + "@octokit/graphql": "^4.8.0", + "@octokit/request": "^5.6.3", + "@pm2/io": "^5.0.0", + "@sendgrid/mail": "7.2.6", + "@slack/web-api": "^6.7.2", + "@superfaceai/one-sdk": "^1.3.0", + "@superfaceai/passport-twitter-oauth2": "^1.0.0", + "analytics-node": "^6.2.0", + "aws-sdk": "2.814.0", + "axios": "^0.27.2", + "bcrypt": "5.0.0", + "bunyan": "^1.8.15", + "bunyan-format": "^0.2.1", + "bunyan-middleware": "^1.0.2", + "clearbit": "^1.3.5", + "cli-highlight": "2.1.6", + "command-line-args": "^5.2.1", + "command-line-usage": "^6.1.3", + "config": "^3.3.8", + "cors": "2.8.5", + "cron": "^2.1.0", + "cron-time-generator": "^1.3.0", + "crypto-js": "^4.1.1", + "dotenv": "8.2.0", + "dotenv-expand": "^8.0.3", + "emoji-dictionary": "^1.0.11", + "express": "4.17.1", + "express-rate-limit": "6.5.1", + "formidable-serverless": "1.1.1", + "helmet": "4.1.1", + "html-to-text": "^8.2.1", + "jsonwebtoken": "8.5.1", + "lodash": "4.17.21", + "meilisearch": "^0.26.0", + "moment": "2.29.4", + "moment-timezone": "^0.5.34", + "mv": "2.1.1", + "node-fetch": "^2.6.7", + "omit-deep-by-values": "^1.0.2", + "openapi-comment-parser": "^1.0.0", + "passport": "0.6.0", + "passport-facebook": "3.0.0", + "passport-google-oauth": "2.0.0", + "passport-google-oauth20": "^2.0.0", + "passport-slack": "0.0.7", + "pg": "^8.7.3", + "pm2": "^5.2.0", + "posthog-node": "^2.2.3", + "redis": "^4.5.0", + "sanitize-html": "^2.7.1", + "sequelize": "6.21.2", + "sequelize-cli-typescript": "^3.2.0-c", + "stripe": "10.0.0", + "superagent": "^8.0.0", + "swagger-ui-dist": "4.1.3", + "uuid": "^8.3.2", + "validator": "^13.7.0", + "verify-github-webhook": "^1.0.1" + }, + "private": true, + "devDependencies": { + "@babel/plugin-transform-runtime": "^7.18.10", + "@babel/preset-env": "^7.16.10", + "@types/bunyan": "^1.8.8", + "@types/bunyan-format": "^0.2.5", + "@types/config": "^3.3.0", + "@types/cron": "^2.0.0", + "@types/html-to-text": "^8.1.1", + "@types/jest": "^27.4.0", + "@types/node": "^17.0.21", + "@types/sanitize-html": "^2.6.2", + "@types/superagent": "^4.1.15", + "@typescript-eslint/eslint-plugin": "^5.17.0", + "@typescript-eslint/parser": "^5.17.0", + "copyfiles": "2.4.1", + "cross-env": "7.0.2", + "eslint": "^8.12.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^16.1.4", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-openapi": "^0.0.4", + "jest": "^27.4.7", + "mocha": "8.1.3", + "node-mocks-http": "1.9.0", + "nodemon": "2.0.4", + "prettier": "^2.5.1", + "rdme": "^7.2.0", + "supertest": "^6.2.2", + "ts-jest": "^27.1.3", + "ts-node": "10.6.0", + "typescript": "^4.7.4" + } +} From 6537a6f21e910f83a539f847a5686c5d530cf574 Mon Sep 17 00:00:00 2001 From: anilb Date: Wed, 7 Dec 2022 11:41:51 +0100 Subject: [PATCH 16/27] posthog env key added to dist local --- backend/.env.dist.local | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/.env.dist.local b/backend/.env.dist.local index c80a89d684..b5c9c30389 100755 --- a/backend/.env.dist.local +++ b/backend/.env.dist.local @@ -68,6 +68,9 @@ CROWD_COMPREHEND_AWS_REGION= # Segment settings CROWD_SEGMENT_WRITE_KEY=TdX3BLaZuHpHyzN2lcDiNiRHDSH9Piyl +# Posthog settings +CROWD_POSTHOG_API_KEY= + SUPERFACE_SDK_TOKEN=sfs_29f8c03402d48b583f9b35bbb5fbb423cf638e73bde38a7cce6a5fb7bb352720193471189882b6d82494203bc998be47a52ab9d4250f90b16d2bd86b4c7ace2b_1d8a819b # Netlify settings From f5c008907b55eebacd37553dd471ab799d42fedd Mon Sep 17 00:00:00 2001 From: anilb Date: Wed, 7 Dec 2022 16:30:56 +0100 Subject: [PATCH 17/27] permissions and 403 errors, plan script also allows multiple tenants now --- .../src/api/automation/automationCreate.ts | 9 ++-- .../conversationSettingsUpdate.ts | 14 ++++-- backend/src/bin/scripts/change-tenant-plan.ts | 50 ++++++++++--------- backend/src/i18n/en.ts | 11 ++++ backend/src/security/permissions.ts | 30 +++++------ 5 files changed, 70 insertions(+), 44 deletions(-) diff --git a/backend/src/api/automation/automationCreate.ts b/backend/src/api/automation/automationCreate.ts index beac84fac6..d365f49271 100644 --- a/backend/src/api/automation/automationCreate.ts +++ b/backend/src/api/automation/automationCreate.ts @@ -4,6 +4,7 @@ import AutomationService from '../../services/automationService' import track from '../../segment/track' import identifyTenant from '../../segment/identifyTenant' import isFeatureEnabled from '../../feature-flags/isFeatureEnabled' +import Error403 from '../../errors/Error403' /** * POST /tenant/{tenantId}/automation @@ -23,9 +24,11 @@ export default async (req, res) => { new PermissionChecker(req).validateHas(Permissions.values.automationCreate) if (!(await isFeatureEnabled('automations', req.currentTenant.id, req.posthog))) { - await req.responseHandler.success(req, res, { - message: 'You have exceeded # of automations you can have in your plan', - }) + await req.responseHandler.error( + req, + res, + new Error403(req.language, 'entities.automation.errors.planLimitExceeded'), + ) return } diff --git a/backend/src/api/conversation/conversationSettingsUpdate.ts b/backend/src/api/conversation/conversationSettingsUpdate.ts index 3c8d6bf0cc..2c70166dde 100644 --- a/backend/src/api/conversation/conversationSettingsUpdate.ts +++ b/backend/src/api/conversation/conversationSettingsUpdate.ts @@ -1,3 +1,4 @@ +import Error403 from '../../errors/Error403' import isFeatureEnabled from '../../feature-flags/isFeatureEnabled' import Permissions from '../../security/permissions' import ConversationService from '../../services/conversationService' @@ -10,9 +11,16 @@ export default async (req, res) => { req.body.customUrl && !(await isFeatureEnabled('community-help-center-pro', req.currentTenant.id, req.posthog)) ) { - await req.responseHandler.success(req, res, { - message: `Your plan (${req.currentTenant.plan}) doesn't include custom urls.`, - }) + await req.responseHandler.error( + req, + res, + new Error403( + req.language, + 'communityHelpCenter.errors.planNotSupportingCustomUrls', + req.currentTenant.plan, + ), + ) + return } const payload = await new ConversationService(req).updateSettings(req.body) diff --git a/backend/src/bin/scripts/change-tenant-plan.ts b/backend/src/bin/scripts/change-tenant-plan.ts index 8b2d68760b..3dfaad86b3 100644 --- a/backend/src/bin/scripts/change-tenant-plan.ts +++ b/backend/src/bin/scripts/change-tenant-plan.ts @@ -27,7 +27,7 @@ const options = [ description: `Plan that will be applied to the tenant. Accepted values are 'Growth' and 'Essential'.`, }, { - name: 'trial', + name: 'trialEndsAt', alias: 'x', description: 'YYYY-MM-dd format trial end date. If this value is ommited, isTrial will be set to false.', @@ -62,37 +62,41 @@ const parameters = commandLineArgs(options) if (parameters.help || !parameters.tenant || !parameters.plan) { console.log(usage) } else if (parameters.plan !== 'Growth' && parameters.plan !== 'Essential') { - console.log(parameters.plan) + console.log(usage) + console.log(`Invalid plan ${parameters.plan}`) } else { setImmediate(async () => { - const tenantId = parameters.tenant const plan = parameters.plan - const isTrial = parameters.trial !== null - const trialEndsAt = parameters.trial + const isTrial = parameters.trialEndsAt !== null + const trialEndsAt = parameters.trialEndsAt const options = await SequelizeRepository.getDefaultIRepositoryOptions() - const tenant = await options.database.tenant.findByPk(tenantId) + const tenantIds = parameters.tenant.split(',') - if (!tenant) { - log.error({ tenantId }, 'Tenant not found!') - process.exit(1) - } else { - log.info({ tenantId, isTrial }, `Tenant found - updating tenant plan to ${plan}!`) - const updated = await tenant.update({ - plan, - isTrialPlan: isTrial, - trialEndsAt, - }) + for (const tenantId of tenantIds) { + const tenant = await options.database.tenant.findByPk(tenantId) - setPosthogTenantProperties( - updated, - new PostHog(POSTHOG_CONFIG.apiKey, { flushAt: 1, flushInterval: 1 }), - options.database, - ) + if (!tenant) { + log.error({ tenantId }, 'Tenant not found!') + process.exit(1) + } else { + log.info({ tenantId, isTrial }, `Tenant found - updating tenant plan to ${plan}!`) + const updated = await tenant.update({ + plan, + isTrialPlan: isTrial, + trialEndsAt, + }) - // give time to posthog to process queue messages - await timeout(2000) + setPosthogTenantProperties( + updated, + new PostHog(POSTHOG_CONFIG.apiKey, { flushAt: 1, flushInterval: 1 }), + options.database, + ) + } } + + // give time to posthog to process queue messages + await timeout(2000) process.exit(0) }) } diff --git a/backend/src/i18n/en.ts b/backend/src/i18n/en.ts index 9cf380aa6f..9488827112 100644 --- a/backend/src/i18n/en.ts +++ b/backend/src/i18n/en.ts @@ -156,6 +156,17 @@ const en = { unique: {}, }, }, + automation: { + errors: { + planLimitExceeded: 'You have exceeded # of automations you can have in your plan.', + }, + }, + }, + + communityHelpCenter: { + errors: { + planNotSupportingCustomUrls: "Your plan {0} doesn't include custom urls.", + }, }, } diff --git a/backend/src/security/permissions.ts b/backend/src/security/permissions.ts index 43d9fd732e..3c75df4c2b 100644 --- a/backend/src/security/permissions.ts +++ b/backend/src/security/permissions.ts @@ -220,37 +220,37 @@ class Permissions { allowedPlans: [plans.essential, plans.growth], }, organizationImport: { - id: 'tagImport', + id: 'organizationImport', allowedRoles: [roles.admin], - allowedPlans: [plans.essential, plans.growth], + allowedPlans: [plans.growth], }, organizationCreate: { - id: 'tagCreate', + id: 'organizationCreate', allowedRoles: [roles.admin], - allowedPlans: [plans.essential, plans.growth], + allowedPlans: [plans.growth], allowedStorage: [], }, organizationEdit: { - id: 'tagEdit', + id: 'organizationEdit', allowedRoles: [roles.admin], - allowedPlans: [plans.essential, plans.growth], + allowedPlans: [plans.growth], allowedStorage: [], }, organizationDestroy: { - id: 'tagDestroy', + id: 'organizationDestroy', allowedRoles: [roles.admin], - allowedPlans: [plans.essential, plans.growth], + allowedPlans: [plans.growth], allowedStorage: [], }, organizationRead: { - id: 'tagRead', + id: 'organizationRead', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.essential, plans.growth], + allowedPlans: [plans.growth], }, organizationAutocomplete: { - id: 'tagAutocomplete', + id: 'organizationAutocomplete', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.essential, plans.growth], + allowedPlans: [plans.growth], }, widgetImport: { id: 'widgetImport', @@ -425,17 +425,17 @@ class Permissions { eagleEyeContentRead: { id: 'eagleEyeContentRead', allowedRoles: [roles.admin, roles.readonly], - allowedPlans: [plans.essential, plans.growth], + allowedPlans: [plans.growth], }, eagleEyeContentSearch: { id: 'eagleEyeContentSearch', allowedRoles: [roles.admin], - allowedPlans: [plans.essential, plans.growth], + allowedPlans: [plans.growth], }, eagleEyeContentEdit: { id: 'eagleEyeContentEdit', allowedRoles: [roles.admin], - allowedPlans: [plans.essential, plans.growth], + allowedPlans: [plans.growth], }, taskImport: { id: 'taskImport', From 33e61a4d7041493e499ef7c1dccacc1cf13ae8b8 Mon Sep 17 00:00:00 2001 From: anilb Date: Wed, 7 Dec 2022 16:40:42 +0100 Subject: [PATCH 18/27] fixes for identifyTenant after rebasing --- backend/src/segment/identifyTenant.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/segment/identifyTenant.ts b/backend/src/segment/identifyTenant.ts index 9b35dd0d3e..27d117d655 100644 --- a/backend/src/segment/identifyTenant.ts +++ b/backend/src/segment/identifyTenant.ts @@ -15,12 +15,12 @@ export default async function identifyTenant(req) { }, }) } else if (API_CONFIG.edition === 'community') { - if (!user.email.includes('crowd.dev')) { + if (!req.currentUser.email.includes('crowd.dev')) { analytics.group({ - userId: user.id, - groupId: tenant.id, + userId: req.currentUser.id, + groupId: req.currentTenant.id, traits: { - createdAt: tenant.createdAt, + createdAt: req.currentTenant.createdAt, }, }) } From 8d0f4ad223ea7b35cf7d8fc46498957e0663af9a Mon Sep 17 00:00:00 2001 From: anilb Date: Wed, 7 Dec 2022 16:49:53 +0100 Subject: [PATCH 19/27] enums for feature flags --- backend/src/api/automation/automationCreate.ts | 3 ++- backend/src/api/conversation/conversationSettingsUpdate.ts | 7 ++++++- backend/src/feature-flags/isFeatureEnabled.ts | 3 ++- backend/src/types/featureFlag.ts | 6 ++++++ 4 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 backend/src/types/featureFlag.ts diff --git a/backend/src/api/automation/automationCreate.ts b/backend/src/api/automation/automationCreate.ts index d365f49271..2493855bde 100644 --- a/backend/src/api/automation/automationCreate.ts +++ b/backend/src/api/automation/automationCreate.ts @@ -5,6 +5,7 @@ import track from '../../segment/track' import identifyTenant from '../../segment/identifyTenant' import isFeatureEnabled from '../../feature-flags/isFeatureEnabled' import Error403 from '../../errors/Error403' +import { FeatureFlag } from '../../types/featureFlag' /** * POST /tenant/{tenantId}/automation @@ -23,7 +24,7 @@ import Error403 from '../../errors/Error403' export default async (req, res) => { new PermissionChecker(req).validateHas(Permissions.values.automationCreate) - if (!(await isFeatureEnabled('automations', req.currentTenant.id, req.posthog))) { + if (!(await isFeatureEnabled(FeatureFlag.AUTOMATIONS, req.currentTenant.id, req.posthog))) { await req.responseHandler.error( req, res, diff --git a/backend/src/api/conversation/conversationSettingsUpdate.ts b/backend/src/api/conversation/conversationSettingsUpdate.ts index 2c70166dde..8d1b78c494 100644 --- a/backend/src/api/conversation/conversationSettingsUpdate.ts +++ b/backend/src/api/conversation/conversationSettingsUpdate.ts @@ -3,13 +3,18 @@ import isFeatureEnabled from '../../feature-flags/isFeatureEnabled' import Permissions from '../../security/permissions' import ConversationService from '../../services/conversationService' import PermissionChecker from '../../services/user/permissionChecker' +import { FeatureFlag } from '../../types/featureFlag' export default async (req, res) => { new PermissionChecker(req).validateHas(Permissions.values.conversationEdit) if ( req.body.customUrl && - !(await isFeatureEnabled('community-help-center-pro', req.currentTenant.id, req.posthog)) + !(await isFeatureEnabled( + FeatureFlag.COMMUNITY_HELP_CENTER_PRO, + req.currentTenant.id, + req.posthog, + )) ) { await req.responseHandler.error( req, diff --git a/backend/src/feature-flags/isFeatureEnabled.ts b/backend/src/feature-flags/isFeatureEnabled.ts index a13c1de6a8..901343e858 100644 --- a/backend/src/feature-flags/isFeatureEnabled.ts +++ b/backend/src/feature-flags/isFeatureEnabled.ts @@ -1,8 +1,9 @@ import { PostHog } from 'posthog-node' import { API_CONFIG } from '../config' +import { FeatureFlag } from '../types/featureFlag' export default async ( - featureFlag: string, + featureFlag: FeatureFlag, tenantId: string, posthog: PostHog, ): Promise => { diff --git a/backend/src/types/featureFlag.ts b/backend/src/types/featureFlag.ts new file mode 100644 index 0000000000..be3c1a47cf --- /dev/null +++ b/backend/src/types/featureFlag.ts @@ -0,0 +1,6 @@ +export enum FeatureFlag { + AUTOMATIONS = 'automations', + COMMUNITY_HELP_CENTER_PRO = 'community-help-center-pro', + EAGLE_EYE = 'eagle-eye', + ORGANIZATIONS = 'organizations', +} From 9f1e7669ea347f0bfd9e49da933e2b96e7b70640 Mon Sep 17 00:00:00 2001 From: anilb Date: Wed, 7 Dec 2022 17:15:00 +0100 Subject: [PATCH 20/27] edition enums --- backend/src/api/automation/automationCreate.ts | 2 +- .../api/conversation/conversationSettingsUpdate.ts | 2 +- backend/src/feature-flags/isFeatureEnabled.ts | 4 ++-- backend/src/feature-flags/setTenantProperties.ts | 3 ++- backend/src/segment/identify.ts | 5 +++-- backend/src/segment/identifyTenant.ts | 5 +++-- backend/src/segment/telemetryTrack.ts | 3 ++- backend/src/segment/track.ts | 3 ++- backend/src/types/common.ts | 12 ++++++++++++ backend/src/types/featureFlag.ts | 6 ------ 10 files changed, 28 insertions(+), 17 deletions(-) delete mode 100644 backend/src/types/featureFlag.ts diff --git a/backend/src/api/automation/automationCreate.ts b/backend/src/api/automation/automationCreate.ts index 2493855bde..072743ac35 100644 --- a/backend/src/api/automation/automationCreate.ts +++ b/backend/src/api/automation/automationCreate.ts @@ -5,7 +5,7 @@ import track from '../../segment/track' import identifyTenant from '../../segment/identifyTenant' import isFeatureEnabled from '../../feature-flags/isFeatureEnabled' import Error403 from '../../errors/Error403' -import { FeatureFlag } from '../../types/featureFlag' +import { FeatureFlag } from '../../types/common' /** * POST /tenant/{tenantId}/automation diff --git a/backend/src/api/conversation/conversationSettingsUpdate.ts b/backend/src/api/conversation/conversationSettingsUpdate.ts index 8d1b78c494..9eb29f8152 100644 --- a/backend/src/api/conversation/conversationSettingsUpdate.ts +++ b/backend/src/api/conversation/conversationSettingsUpdate.ts @@ -3,7 +3,7 @@ import isFeatureEnabled from '../../feature-flags/isFeatureEnabled' import Permissions from '../../security/permissions' import ConversationService from '../../services/conversationService' import PermissionChecker from '../../services/user/permissionChecker' -import { FeatureFlag } from '../../types/featureFlag' +import { FeatureFlag } from '../../types/common' export default async (req, res) => { new PermissionChecker(req).validateHas(Permissions.values.conversationEdit) diff --git a/backend/src/feature-flags/isFeatureEnabled.ts b/backend/src/feature-flags/isFeatureEnabled.ts index 901343e858..662df623dd 100644 --- a/backend/src/feature-flags/isFeatureEnabled.ts +++ b/backend/src/feature-flags/isFeatureEnabled.ts @@ -1,13 +1,13 @@ import { PostHog } from 'posthog-node' import { API_CONFIG } from '../config' -import { FeatureFlag } from '../types/featureFlag' +import { FeatureFlag, Edition } from '../types/common' export default async ( featureFlag: FeatureFlag, tenantId: string, posthog: PostHog, ): Promise => { - if (API_CONFIG.edition === 'community') { + if (API_CONFIG.edition === Edition.COMMUNITY) { return true } diff --git a/backend/src/feature-flags/setTenantProperties.ts b/backend/src/feature-flags/setTenantProperties.ts index c77d9b5579..d7174efbb3 100644 --- a/backend/src/feature-flags/setTenantProperties.ts +++ b/backend/src/feature-flags/setTenantProperties.ts @@ -1,12 +1,13 @@ import { PostHog } from 'posthog-node' import { API_CONFIG, POSTHOG_CONFIG } from '../config' +import { Edition } from '../types/common' export default async function setPosthogTenantProperties( tenant: any, posthog: PostHog, database: any, ) { - if (POSTHOG_CONFIG.apiKey && API_CONFIG.edition === 'crowd-hosted') { + if (POSTHOG_CONFIG.apiKey && API_CONFIG.edition === Edition.CROWD_HOSTED) { const automationCount = await database.automation.count({ where: { tenantId: tenant.id, diff --git a/backend/src/segment/identify.ts b/backend/src/segment/identify.ts index fb714d4eed..7900eb3f70 100644 --- a/backend/src/segment/identify.ts +++ b/backend/src/segment/identify.ts @@ -1,11 +1,12 @@ import { SEGMENT_CONFIG, API_CONFIG } from '../config' +import { Edition } from '../types/common' export default function identify(user) { const Analytics = require('analytics-node') if (SEGMENT_CONFIG.writeKey) { const analytics = new Analytics(SEGMENT_CONFIG.writeKey) - if (API_CONFIG.edition === 'crowd-hosted') { + if (API_CONFIG.edition === Edition.CROWD_HOSTED) { analytics.identify({ userId: user.id, traits: { @@ -22,7 +23,7 @@ export default function identify(user) { created_an_account__date: user.createdAt, }, }) - } else if (API_CONFIG.edition === 'community') { + } else if (API_CONFIG.edition === Edition.COMMUNITY) { if (!user.email.includes('crowd.dev')) { analytics.identify({ userId: user.id, diff --git a/backend/src/segment/identifyTenant.ts b/backend/src/segment/identifyTenant.ts index 27d117d655..39760c8660 100644 --- a/backend/src/segment/identifyTenant.ts +++ b/backend/src/segment/identifyTenant.ts @@ -1,12 +1,13 @@ import { SEGMENT_CONFIG, API_CONFIG } from '../config' import setPosthogTenantProperties from '../feature-flags/setTenantProperties' +import { Edition } from '../types/common' export default async function identifyTenant(req) { if (SEGMENT_CONFIG.writeKey) { const Analytics = require('analytics-node') const analytics = new Analytics(SEGMENT_CONFIG.writeKey) - if (API_CONFIG.edition === 'crowd-hosted') { + if (API_CONFIG.edition === Edition.CROWD_HOSTED) { analytics.group({ userId: req.currentUser.id, groupId: req.currentTenant.id, @@ -14,7 +15,7 @@ export default async function identifyTenant(req) { name: req.currentTenant.name, }, }) - } else if (API_CONFIG.edition === 'community') { + } else if (API_CONFIG.edition === Edition.COMMUNITY) { if (!req.currentUser.email.includes('crowd.dev')) { analytics.group({ userId: req.currentUser.id, diff --git a/backend/src/segment/telemetryTrack.ts b/backend/src/segment/telemetryTrack.ts index c5d659cc34..6b0efe56c1 100644 --- a/backend/src/segment/telemetryTrack.ts +++ b/backend/src/segment/telemetryTrack.ts @@ -2,6 +2,7 @@ import { createServiceChildLogger } from '../utils/logging' import { SEGMENT_CONFIG, API_CONFIG, IS_TEST_ENV } from '../config' import getTenatUser from './trackHelper' import SequelizeRepository from '../database/repositories/sequelizeRepository' +import { Edition } from '../types/common' const log = createServiceChildLogger('telemetryTrack') @@ -19,7 +20,7 @@ export default function track( !IS_TEST_ENV && SEGMENT_CONFIG.writeKey && // This is only for events in the self-hosted version. Hosted has more telemetry. - API_CONFIG.edition === 'community' && + API_CONFIG.edition === Edition.COMMUNITY && !email.includes('crowd.dev') ) { const Analytics = require('analytics-node') diff --git a/backend/src/segment/track.ts b/backend/src/segment/track.ts index a095a36cdb..28f004e056 100644 --- a/backend/src/segment/track.ts +++ b/backend/src/segment/track.ts @@ -1,6 +1,7 @@ import { createServiceChildLogger } from '../utils/logging' import { SEGMENT_CONFIG, API_CONFIG, IS_TEST_ENV } from '../config' import getTenatUser from './trackHelper' +import { Edition } from '../types/common' const log = createServiceChildLogger('segment') @@ -15,7 +16,7 @@ export default function identify( !IS_TEST_ENV && SEGMENT_CONFIG.writeKey && // This is only for events in the hosted version. Self-hosted has less telemetry. - API_CONFIG.edition === 'crowd-hosted' + API_CONFIG.edition === Edition.CROWD_HOSTED ) { const Analytics = require('analytics-node') const analytics = new Analytics(SEGMENT_CONFIG.writeKey) diff --git a/backend/src/types/common.ts b/backend/src/types/common.ts index 709300c8b0..6193558078 100644 --- a/backend/src/types/common.ts +++ b/backend/src/types/common.ts @@ -9,3 +9,15 @@ export interface SearchCriteria { limit?: number offset?: number } + +export enum FeatureFlag { + AUTOMATIONS = 'automations', + COMMUNITY_HELP_CENTER_PRO = 'community-help-center-pro', + EAGLE_EYE = 'eagle-eye', + ORGANIZATIONS = 'organizations', +} + +export enum Edition { + COMMUNITY = 'community', + CROWD_HOSTED = 'crowd-hosted', +} diff --git a/backend/src/types/featureFlag.ts b/backend/src/types/featureFlag.ts deleted file mode 100644 index be3c1a47cf..0000000000 --- a/backend/src/types/featureFlag.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum FeatureFlag { - AUTOMATIONS = 'automations', - COMMUNITY_HELP_CENTER_PRO = 'community-help-center-pro', - EAGLE_EYE = 'eagle-eye', - ORGANIZATIONS = 'organizations', -} From 6f1c9c93900ba44acdc046380269000cab791365 Mon Sep 17 00:00:00 2001 From: anilb Date: Wed, 7 Dec 2022 17:58:01 +0100 Subject: [PATCH 21/27] enterprise name update --- frontend/src/security/plans.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/security/plans.js b/frontend/src/security/plans.js index 68e4094988..5488ff8e9b 100644 --- a/frontend/src/security/plans.js +++ b/frontend/src/security/plans.js @@ -6,7 +6,7 @@ class Plans { return { essential: 'Essential', growth: 'Growth', - enterprise: 'enterprise' + enterprise: 'Enterprise' } } } From 818738ae1cd015182922ac5e3e0657dfd87570d8 Mon Sep 17 00:00:00 2001 From: anilb Date: Thu, 8 Dec 2022 11:21:49 +0100 Subject: [PATCH 22/27] migrations update plan before altering column --- .../database/migrations/V1670239828__tenantPlanUpdates.sql | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/src/database/migrations/V1670239828__tenantPlanUpdates.sql b/backend/src/database/migrations/V1670239828__tenantPlanUpdates.sql index 8099274484..34709a99a4 100644 --- a/backend/src/database/migrations/V1670239828__tenantPlanUpdates.sql +++ b/backend/src/database/migrations/V1670239828__tenantPlanUpdates.sql @@ -1,6 +1,9 @@ CREATE TYPE tenant_plans_type AS ENUM ('Essential', 'Growth'); ALTER TABLE tenants ALTER plan DROP DEFAULT; + +UPDATE tenants SET plan = 'Essential' WHERE 1=1; + ALTER TABLE tenants ALTER COLUMN plan TYPE public.tenant_plans_type USING plan::tenant_plans_type; @@ -8,5 +11,4 @@ ALTER TABLE tenants ALTER column plan SET DEFAULT 'Essential'; ALTER TABLE tenants ADD COLUMN "isTrialPlan" BOOLEAN NOT NULL DEFAULT FALSE; -ALTER TABLE tenants ADD COLUMN "trialEndsAt" timestamp with time zone DEFAULT null; - +ALTER TABLE tenants ADD COLUMN "trialEndsAt" timestamp with time zone DEFAULT null; \ No newline at end of file From aa8d008a2e9679b6eb2ac6fc2213fbd753bd78b0 Mon Sep 17 00:00:00 2001 From: anilb Date: Fri, 9 Dec 2022 02:30:14 +0100 Subject: [PATCH 23/27] automation count as text because of posthog bug --- backend/src/feature-flags/setTenantProperties.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/feature-flags/setTenantProperties.ts b/backend/src/feature-flags/setTenantProperties.ts index d7174efbb3..8f5be4fd7c 100644 --- a/backend/src/feature-flags/setTenantProperties.ts +++ b/backend/src/feature-flags/setTenantProperties.ts @@ -20,7 +20,7 @@ export default async function setPosthogTenantProperties( properties: { name: tenant.name, plan: tenant.plan, - automationCount, + automationCount: automationCount.toString(), }, } console.log(payload) From 04489bac046985d112f804f1a4392d839cb7c28b Mon Sep 17 00:00:00 2001 From: anilb Date: Fri, 9 Dec 2022 10:19:47 +0100 Subject: [PATCH 24/27] posthog flush queue after setting tenant properties immediately --- backend/src/feature-flags/setTenantProperties.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/feature-flags/setTenantProperties.ts b/backend/src/feature-flags/setTenantProperties.ts index 8f5be4fd7c..ebe7224093 100644 --- a/backend/src/feature-flags/setTenantProperties.ts +++ b/backend/src/feature-flags/setTenantProperties.ts @@ -25,5 +25,6 @@ export default async function setPosthogTenantProperties( } console.log(payload) posthog.groupIdentify(payload) + posthog.flush() } } From ed7914fe4c390fc9eb18b196dfdf9203f8148303 Mon Sep 17 00:00:00 2001 From: anilb Date: Fri, 9 Dec 2022 11:01:43 +0100 Subject: [PATCH 25/27] waiting 100ms before returning in automation create and destroy --- backend/src/api/automation/automationCreate.ts | 5 +++++ backend/src/api/automation/automationDestroy.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/backend/src/api/automation/automationCreate.ts b/backend/src/api/automation/automationCreate.ts index 072743ac35..60b7df5494 100644 --- a/backend/src/api/automation/automationCreate.ts +++ b/backend/src/api/automation/automationCreate.ts @@ -6,6 +6,7 @@ import identifyTenant from '../../segment/identifyTenant' import isFeatureEnabled from '../../feature-flags/isFeatureEnabled' import Error403 from '../../errors/Error403' import { FeatureFlag } from '../../types/common' +import { timeout } from '../../utils/timing' /** * POST /tenant/{tenantId}/automation @@ -39,5 +40,9 @@ export default async (req, res) => { identifyTenant(req) + // wait a small window for posthog + // to process the queue message before returing back + await timeout(100) + await req.responseHandler.success(req, res, payload) } diff --git a/backend/src/api/automation/automationDestroy.ts b/backend/src/api/automation/automationDestroy.ts index 16f9271cc4..117999fadc 100644 --- a/backend/src/api/automation/automationDestroy.ts +++ b/backend/src/api/automation/automationDestroy.ts @@ -3,6 +3,7 @@ import Permissions from '../../security/permissions' import AutomationService from '../../services/automationService' import track from '../../segment/track' import identifyTenant from '../../segment/identifyTenant' +import { timeout } from '../../utils/timing' /** * DELETE /tenant/{tenantId}/automation/{automationId} @@ -23,5 +24,9 @@ export default async (req, res) => { track('Automation Destroyed', { id: req.params.automationId }, { ...req }) identifyTenant(req) + // wait a small window for posthog + // to process the queue message before returing back + await timeout(100) + await req.responseHandler.success(req, res, true, 204) } From fb483360e6600e6c9879a40b08e4a90a5fd2e428 Mon Sep 17 00:00:00 2001 From: anilb Date: Fri, 9 Dec 2022 13:40:43 +0100 Subject: [PATCH 26/27] ensuring flags before returning for realtime use cases --- .../src/api/automation/automationCreate.ts | 20 ++++----- .../src/api/automation/automationDestroy.ts | 14 +++++-- backend/src/api/automation/index.ts | 8 +++- .../repositories/automationRepository.ts | 11 +++++ .../src/feature-flags/ensureFlagUpdated.ts | 41 +++++++++++++++++++ .../src/feature-flags/setTenantProperties.ts | 7 +--- .../src/middlewares/featureFlagMiddleware.ts | 15 +++++++ 7 files changed, 93 insertions(+), 23 deletions(-) create mode 100644 backend/src/feature-flags/ensureFlagUpdated.ts create mode 100644 backend/src/middlewares/featureFlagMiddleware.ts diff --git a/backend/src/api/automation/automationCreate.ts b/backend/src/api/automation/automationCreate.ts index 60b7df5494..ee05e88e8b 100644 --- a/backend/src/api/automation/automationCreate.ts +++ b/backend/src/api/automation/automationCreate.ts @@ -6,7 +6,8 @@ import identifyTenant from '../../segment/identifyTenant' import isFeatureEnabled from '../../feature-flags/isFeatureEnabled' import Error403 from '../../errors/Error403' import { FeatureFlag } from '../../types/common' -import { timeout } from '../../utils/timing' +import ensureFlagUpdated from '../../feature-flags/ensureFlagUpdated' +import AutomationRepository from '../../database/repositories/automationRepository' /** * POST /tenant/{tenantId}/automation @@ -25,24 +26,17 @@ import { timeout } from '../../utils/timing' export default async (req, res) => { new PermissionChecker(req).validateHas(Permissions.values.automationCreate) - if (!(await isFeatureEnabled(FeatureFlag.AUTOMATIONS, req.currentTenant.id, req.posthog))) { - await req.responseHandler.error( - req, - res, - new Error403(req.language, 'entities.automation.errors.planLimitExceeded'), - ) - return - } - const payload = await new AutomationService(req).create(req.body.data) track('Automation Created', { ...payload }, { ...req }) identifyTenant(req) - // wait a small window for posthog - // to process the queue message before returing back - await timeout(100) + const automationCount = await AutomationRepository.countAll(req.database, req.currentTenant.id) + await ensureFlagUpdated(FeatureFlag.AUTOMATIONS, req.currentTenant.id, req.posthog, { + plan: req.currentTenant.plan, + automationCount, + }) await req.responseHandler.success(req, res, payload) } diff --git a/backend/src/api/automation/automationDestroy.ts b/backend/src/api/automation/automationDestroy.ts index 117999fadc..991e8217cf 100644 --- a/backend/src/api/automation/automationDestroy.ts +++ b/backend/src/api/automation/automationDestroy.ts @@ -3,7 +3,9 @@ import Permissions from '../../security/permissions' import AutomationService from '../../services/automationService' import track from '../../segment/track' import identifyTenant from '../../segment/identifyTenant' -import { timeout } from '../../utils/timing' +import ensureFlagUpdated from '../../feature-flags/ensureFlagUpdated' +import { FeatureFlag } from '../../types/common' +import AutomationRepository from '../../database/repositories/automationRepository' /** * DELETE /tenant/{tenantId}/automation/{automationId} @@ -21,12 +23,16 @@ export default async (req, res) => { new PermissionChecker(req).validateHas(Permissions.values.automationDestroy) await new AutomationService(req).destroy(req.params.automationId) + await req.posthog.reloadFeatureFlags() + track('Automation Destroyed', { id: req.params.automationId }, { ...req }) identifyTenant(req) - // wait a small window for posthog - // to process the queue message before returing back - await timeout(100) + const automationCount = await AutomationRepository.countAll(req.database, req.currentTenant.id) + await ensureFlagUpdated(FeatureFlag.AUTOMATIONS, req.currentTenant.id, req.posthog, { + plan: req.currentTenant.plan, + automationCount, + }) await req.responseHandler.success(req, res, true, 204) } diff --git a/backend/src/api/automation/index.ts b/backend/src/api/automation/index.ts index 77cc9f2d7e..0b03481382 100644 --- a/backend/src/api/automation/index.ts +++ b/backend/src/api/automation/index.ts @@ -1,7 +1,13 @@ import { safeWrap } from '../../middlewares/errorMiddleware' +import { featureFlagMiddleware } from '../../middlewares/featureFlagMiddleware' +import { FeatureFlag } from '../../types/common' export default (app) => { - app.post('/tenant/:tenantId/automation', safeWrap(require('./automationCreate').default)) + app.post( + '/tenant/:tenantId/automation', + featureFlagMiddleware(FeatureFlag.AUTOMATIONS, 'entities.automation.errors.planLimitExceeded'), + safeWrap(require('./automationCreate').default), + ) app.put( '/tenant/:tenantId/automation/:automationId', safeWrap(require('./automationUpdate').default), diff --git a/backend/src/database/repositories/automationRepository.ts b/backend/src/database/repositories/automationRepository.ts index 3eb54e493a..23a7d6d1f4 100644 --- a/backend/src/database/repositories/automationRepository.ts +++ b/backend/src/database/repositories/automationRepository.ts @@ -228,4 +228,15 @@ export default class AutomationRepository extends RepositoryBase< limit: criteria.limit, } } + + static async countAll(database: any, tenantId: string): Promise { + const automationCount = await database.automation.count({ + where: { + tenantId, + }, + useMaster: true, + }) + + return automationCount + } } diff --git a/backend/src/feature-flags/ensureFlagUpdated.ts b/backend/src/feature-flags/ensureFlagUpdated.ts new file mode 100644 index 0000000000..430340df9e --- /dev/null +++ b/backend/src/feature-flags/ensureFlagUpdated.ts @@ -0,0 +1,41 @@ +import { PostHog } from 'posthog-node' +import Plans from '../security/plans' +import { FeatureFlag } from '../types/common' +import { getServiceLogger } from '../utils/logging' +import { timeout } from '../utils/timing' + +const log = getServiceLogger() + +export default async ( + featureFlag: FeatureFlag, + tenantId: string, + posthog: PostHog, + payload: any, +): Promise => { + if (featureFlag === FeatureFlag.AUTOMATIONS) { + const expectedFlag = + payload.plan === Plans.values.growth || + (payload.plan === Plans.values.essential && payload.automationCount < 2) + + let featureFlagEnabled = await posthog.isFeatureEnabled(featureFlag, '', { + groups: { tenant: tenantId }, + }) + + let tries = 0 + const MAX_TRY_COUNT = 10 + + while (expectedFlag !== featureFlagEnabled && tries < MAX_TRY_COUNT) { + await timeout(500) + featureFlagEnabled = await posthog.isFeatureEnabled(featureFlag, '', { + groups: { tenant: tenantId }, + }) + + tries += 1 + if (tries === 5) { + log.info(`Tried ${MAX_TRY_COUNT} times to sync the flags without luck... Breaking`) + } + } + } + + log.info(`Feature flag ${featureFlag} is ensured!`) +} diff --git a/backend/src/feature-flags/setTenantProperties.ts b/backend/src/feature-flags/setTenantProperties.ts index ebe7224093..b4e1830dfd 100644 --- a/backend/src/feature-flags/setTenantProperties.ts +++ b/backend/src/feature-flags/setTenantProperties.ts @@ -1,5 +1,6 @@ import { PostHog } from 'posthog-node' import { API_CONFIG, POSTHOG_CONFIG } from '../config' +import AutomationRepository from '../database/repositories/automationRepository' import { Edition } from '../types/common' export default async function setPosthogTenantProperties( @@ -8,11 +9,7 @@ export default async function setPosthogTenantProperties( database: any, ) { if (POSTHOG_CONFIG.apiKey && API_CONFIG.edition === Edition.CROWD_HOSTED) { - const automationCount = await database.automation.count({ - where: { - tenantId: tenant.id, - }, - }) + const automationCount = await AutomationRepository.countAll(database, tenant.id) const payload = { groupType: 'tenant', diff --git a/backend/src/middlewares/featureFlagMiddleware.ts b/backend/src/middlewares/featureFlagMiddleware.ts new file mode 100644 index 0000000000..240038d247 --- /dev/null +++ b/backend/src/middlewares/featureFlagMiddleware.ts @@ -0,0 +1,15 @@ +import Error403 from '../errors/Error403' +import isFeatureEnabled from '../feature-flags/isFeatureEnabled' +import { FeatureFlag } from '../types/common' + +export function featureFlagMiddleware(featureFlag: FeatureFlag, errorMessage: string) { + return async (req, res, next) => { + await req.posthog.reloadFeatureFlags() + + if (!(await isFeatureEnabled(featureFlag, req.currentTenant.id, req.posthog))) { + await req.responseHandler.error(req, res, new Error403(req.language, errorMessage)) + return + } + next() + } +} From 958b2e176c1ebe18357798173186d80634cfaca8 Mon Sep 17 00:00:00 2001 From: anilb Date: Fri, 9 Dec 2022 13:52:09 +0100 Subject: [PATCH 27/27] lint fix --- backend/src/api/automation/automationCreate.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/src/api/automation/automationCreate.ts b/backend/src/api/automation/automationCreate.ts index ee05e88e8b..5fbd9ac221 100644 --- a/backend/src/api/automation/automationCreate.ts +++ b/backend/src/api/automation/automationCreate.ts @@ -3,8 +3,6 @@ import Permissions from '../../security/permissions' import AutomationService from '../../services/automationService' import track from '../../segment/track' import identifyTenant from '../../segment/identifyTenant' -import isFeatureEnabled from '../../feature-flags/isFeatureEnabled' -import Error403 from '../../errors/Error403' import { FeatureFlag } from '../../types/common' import ensureFlagUpdated from '../../feature-flags/ensureFlagUpdated' import AutomationRepository from '../../database/repositories/automationRepository'