Skip to content

Commit 636c3dd

Browse files
authored
Tenant plans and feature flagging (#318)
1 parent cffd492 commit 636c3dd

52 files changed

Lines changed: 737 additions & 357 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend/.env.dist.local

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ CROWD_COMPREHEND_AWS_REGION=
6868
# Segment settings
6969
CROWD_SEGMENT_WRITE_KEY=TdX3BLaZuHpHyzN2lcDiNiRHDSH9Piyl
7070

71+
# Posthog settings
72+
CROWD_POSTHOG_API_KEY=
73+
7174
SUPERFACE_SDK_TOKEN=sfs_29f8c03402d48b583f9b35bbb5fbb423cf638e73bde38a7cce6a5fb7bb352720193471189882b6d82494203bc998be47a52ab9d4250f90b16d2bd86b4c7ace2b_1d8a819b
7275

7376
# Netlify settings

backend/config/custom-environment-variables.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@
6666
"segment": {
6767
"writeKey": "CROWD_SEGMENT_WRITE_KEY"
6868
},
69+
"posthog": {
70+
"apiKey": "CROWD_POSTHOG_API_KEY"
71+
},
6972
"comprehend": {
7073
"aws": {
7174
"accountId": "CROWD_COMPREHEND_AWS_ACCOUNT_ID",

backend/config/default.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"cubejs": {},
1414
"searchEngine": {},
1515
"segment": {},
16+
"posthog": {},
1617
"comprehend": {
1718
"aws": {}
1819
},

backend/package-lock.json

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
"stripe:start": "stripe listen --forward-to localhost:8080/api/plan/stripe/webhook",
2727
"lint": "npx eslint .",
2828
"format": "npx prettier --write .",
29-
"script:process-integration": "SERVICE=script ts-node ./src/bin/scripts/process-integration.ts"
29+
"script:process-integration": "SERVICE=script ts-node ./src/bin/scripts/process-integration.ts",
30+
"script:change-tenant-plan": "SERVICE=script ts-node ./src/bin/scripts/change-tenant-plan.ts"
3031
},
3132
"dependencies": {
3233
"@aws-sdk/client-comprehend": "^3.159.0",
@@ -80,6 +81,7 @@
8081
"passport-slack": "0.0.7",
8182
"pg": "^8.7.3",
8283
"pm2": "^5.2.0",
84+
"posthog-node": "^2.2.3",
8385
"redis": "^4.5.0",
8486
"sanitize-html": "^2.7.1",
8587
"sequelize": "6.21.2",

backend/src/api/automation/automationCreate.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import PermissionChecker from '../../services/user/permissionChecker'
22
import Permissions from '../../security/permissions'
33
import AutomationService from '../../services/automationService'
44
import track from '../../segment/track'
5+
import identifyTenant from '../../segment/identifyTenant'
6+
import { FeatureFlag } from '../../types/common'
7+
import ensureFlagUpdated from '../../feature-flags/ensureFlagUpdated'
8+
import AutomationRepository from '../../database/repositories/automationRepository'
59

610
/**
711
* POST /tenant/{tenantId}/automation
@@ -19,9 +23,18 @@ import track from '../../segment/track'
1923
*/
2024
export default async (req, res) => {
2125
new PermissionChecker(req).validateHas(Permissions.values.automationCreate)
26+
2227
const payload = await new AutomationService(req).create(req.body.data)
2328

2429
track('Automation Created', { ...payload }, { ...req })
2530

31+
identifyTenant(req)
32+
33+
const automationCount = await AutomationRepository.countAll(req.database, req.currentTenant.id)
34+
await ensureFlagUpdated(FeatureFlag.AUTOMATIONS, req.currentTenant.id, req.posthog, {
35+
plan: req.currentTenant.plan,
36+
automationCount,
37+
})
38+
2639
await req.responseHandler.success(req, res, payload)
2740
}

backend/src/api/automation/automationDestroy.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import PermissionChecker from '../../services/user/permissionChecker'
22
import Permissions from '../../security/permissions'
33
import AutomationService from '../../services/automationService'
44
import track from '../../segment/track'
5+
import identifyTenant from '../../segment/identifyTenant'
6+
import ensureFlagUpdated from '../../feature-flags/ensureFlagUpdated'
7+
import { FeatureFlag } from '../../types/common'
8+
import AutomationRepository from '../../database/repositories/automationRepository'
59

610
/**
711
* DELETE /tenant/{tenantId}/automation/{automationId}
@@ -19,7 +23,16 @@ export default async (req, res) => {
1923
new PermissionChecker(req).validateHas(Permissions.values.automationDestroy)
2024
await new AutomationService(req).destroy(req.params.automationId)
2125

26+
await req.posthog.reloadFeatureFlags()
27+
2228
track('Automation Destroyed', { id: req.params.automationId }, { ...req })
29+
identifyTenant(req)
30+
31+
const automationCount = await AutomationRepository.countAll(req.database, req.currentTenant.id)
32+
await ensureFlagUpdated(FeatureFlag.AUTOMATIONS, req.currentTenant.id, req.posthog, {
33+
plan: req.currentTenant.plan,
34+
automationCount,
35+
})
2336

2437
await req.responseHandler.success(req, res, true, 204)
2538
}

backend/src/api/automation/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { safeWrap } from '../../middlewares/errorMiddleware'
2+
import { featureFlagMiddleware } from '../../middlewares/featureFlagMiddleware'
3+
import { FeatureFlag } from '../../types/common'
24

35
export default (app) => {
4-
app.post('/tenant/:tenantId/automation', safeWrap(require('./automationCreate').default))
6+
app.post(
7+
'/tenant/:tenantId/automation',
8+
featureFlagMiddleware(FeatureFlag.AUTOMATIONS, 'entities.automation.errors.planLimitExceeded'),
9+
safeWrap(require('./automationCreate').default),
10+
)
511
app.put(
612
'/tenant/:tenantId/automation/:automationId',
713
safeWrap(require('./automationUpdate').default),

backend/src/api/conversation/conversationSettingsUpdate.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,33 @@
1+
import Error403 from '../../errors/Error403'
2+
import isFeatureEnabled from '../../feature-flags/isFeatureEnabled'
13
import Permissions from '../../security/permissions'
24
import ConversationService from '../../services/conversationService'
35
import PermissionChecker from '../../services/user/permissionChecker'
6+
import { FeatureFlag } from '../../types/common'
47

58
export default async (req, res) => {
69
new PermissionChecker(req).validateHas(Permissions.values.conversationEdit)
710

11+
if (
12+
req.body.customUrl &&
13+
!(await isFeatureEnabled(
14+
FeatureFlag.COMMUNITY_HELP_CENTER_PRO,
15+
req.currentTenant.id,
16+
req.posthog,
17+
))
18+
) {
19+
await req.responseHandler.error(
20+
req,
21+
res,
22+
new Error403(
23+
req.language,
24+
'communityHelpCenter.errors.planNotSupportingCustomUrls',
25+
req.currentTenant.plan,
26+
),
27+
)
28+
return
29+
}
30+
831
const payload = await new ConversationService(req).updateSettings(req.body)
932

1033
await req.responseHandler.success(req, res, payload)

backend/src/api/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import express from 'express'
22
import cors from 'cors'
33
import helmet from 'helmet'
44
import bunyanMiddleware from 'bunyan-middleware'
5+
import { PostHog } from 'posthog-node'
56
import { authMiddleware } from '../middlewares/authMiddleware'
67
import { tenantMiddleware } from '../middlewares/tenantMiddleware'
78
import { databaseMiddleware } from '../middlewares/databaseMiddleware'
@@ -16,6 +17,7 @@ import { errorMiddleware } from '../middlewares/errorMiddleware'
1617
import { passportStrategyMiddleware } from '../middlewares/passportStrategyMiddleware'
1718
import { redisMiddleware } from '../middlewares/redisMiddleware'
1819
import { createRedisClient } from '../utils/redis'
20+
import { POSTHOG_CONFIG } from '../config'
1921

2022
const serviceLogger = createServiceLogger()
2123

@@ -24,6 +26,12 @@ const app = express()
2426
setImmediate(async () => {
2527
const redis = await createRedisClient(true)
2628

29+
let posthog = null
30+
31+
if (POSTHOG_CONFIG.apiKey) {
32+
posthog = new PostHog(POSTHOG_CONFIG.apiKey)
33+
}
34+
2735
// Enables CORS
2836
app.use(cors({ origin: true }))
2937

@@ -47,6 +55,12 @@ setImmediate(async () => {
4755
// Bind redis to request
4856
app.use(redisMiddleware(redis))
4957

58+
// Bind posthog to request
59+
app.use((req: any, res, next) => {
60+
req.posthog = posthog
61+
next()
62+
})
63+
5064
// initialize passport strategies
5165
app.use(passportStrategyMiddleware)
5266

0 commit comments

Comments
 (0)