Skip to content

Commit c5b4b75

Browse files
Automations feature backend (#43)
Co-authored-by: Mário Balça <mario.balca@gmail.com>
1 parent 4b580a5 commit c5b4b75

48 files changed

Lines changed: 2869 additions & 290 deletions

Some content is hidden

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

backend/package-lock.json

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

backend/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
"db:publish": "bash ./util/publish-db.sh",
2121
"docs": "bash ./util/publish-docs.sh",
2222
"sequelize-cli:source": "npm run build:setenv:dev && npm run build && ./node_modules/.bin/sequelize --config src/database/sequelize-cli-config.ts --migrations-source-path src/database/migrations",
23+
"sequelize:migrate:staging": "npm run build:setenv:staging && npm run build && ./node_modules/.bin/sequelize --config src/database/sequelize-cli-config.ts --migrations-source-path src/database/migrations db:migrate",
24+
"sequelize:migrate:prod": "npm run build:setenv:prod && npm run build && ./node_modules/.bin/sequelize --config src/database/sequelize-cli-config.ts --migrations-source-path src/database/migrations db:migrate",
2325
"sequelize-cli:build": "./node_modules/.bin/sequelize --config database/sequelize-cli-config.js --migrations-compiled-path database/migrations",
2426
"stripe:login": "stripe login",
2527
"stripe:start": "stripe listen --forward-to localhost:8080/api/plan/stripe/webhook",
@@ -69,6 +71,7 @@
6971
"sequelize": "6.21.2",
7072
"sequelize-cli-typescript": "^3.2.0-c",
7173
"stripe": "10.0.0",
74+
"superagent": "^8.0.0",
7275
"swagger-ui-dist": "4.1.3",
7376
"uuid": "^8.3.2",
7477
"validator": "^13.7.0"
@@ -81,6 +84,7 @@
8184
"@types/jest": "^27.4.0",
8285
"@types/node": "^17.0.21",
8386
"@types/sanitize-html": "^2.6.2",
87+
"@types/superagent": "^4.1.15",
8488
"@typescript-eslint/eslint-plugin": "^5.17.0",
8589
"@typescript-eslint/parser": "^5.17.0",
8690
"copyfiles": "2.4.1",
@@ -97,7 +101,6 @@
97101
"nodemon": "2.0.4",
98102
"prettier": "^2.5.1",
99103
"rdme": "^7.2.0",
100-
"superagent": "^7.1.2",
101104
"supertest": "^6.2.2",
102105
"ts-jest": "^27.1.3",
103106
"ts-node": "10.6.0",
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import PermissionChecker from '../../services/user/permissionChecker'
2+
import Permissions from '../../security/permissions'
3+
import AutomationService from '../../services/automationService'
4+
import track from '../../segment/track'
5+
import ApiResponseHandler from '../apiResponseHandler'
6+
7+
/**
8+
* POST /tenant/{tenantId}/automation
9+
* @summary Create an automation
10+
* @tag Automations
11+
* @security Bearer
12+
* @description Create a new automation for the tenant.
13+
* @pathParam {string} tenantId - Your workspace/tenant ID
14+
* @bodyContent {AutomationCreateInput} application/json
15+
* @response 200 - Ok
16+
* @responseContent {Automation} 200.application/json
17+
* @responseExample {Automation} 200.application/json.Automation
18+
* @response 401 - Unauthorized
19+
* @response 429 - Too many requests
20+
*/
21+
export default async (req, res) => {
22+
try {
23+
new PermissionChecker(req).validateHas(Permissions.values.automationCreate)
24+
const payload = await new AutomationService(req).create(req.body.data)
25+
26+
track('Automation Created', { ...payload }, { ...req })
27+
28+
await ApiResponseHandler.success(req, res, payload)
29+
} catch (error) {
30+
await ApiResponseHandler.error(req, res, error)
31+
}
32+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import PermissionChecker from '../../services/user/permissionChecker'
2+
import Permissions from '../../security/permissions'
3+
import AutomationService from '../../services/automationService'
4+
import track from '../../segment/track'
5+
import ApiResponseHandler from '../apiResponseHandler'
6+
7+
/**
8+
* DELETE /tenant/{tenantId}/automation/{automationId}
9+
* @summary Destroys an existing automation
10+
* @tag Automations
11+
* @security Bearer
12+
* @description Destroys an existing automation in the tenant.
13+
* @pathParam {string} tenantId - Your workspace/tenant ID
14+
* @pathParam {string} automationId - Automation ID that you want to update
15+
* @response 204 - Ok - No content
16+
* @response 401 - Unauthorized
17+
* @response 429 - Too many requests
18+
*/
19+
export default async (req, res) => {
20+
try {
21+
new PermissionChecker(req).validateHas(Permissions.values.automationDestroy)
22+
await new AutomationService(req).destroy(req.params.automationId)
23+
24+
track('Automation Destroyed', { id: req.params.automationId }, { ...req })
25+
26+
await ApiResponseHandler.success(req, res, true, 204)
27+
} catch (error) {
28+
await ApiResponseHandler.error(req, res, error)
29+
}
30+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import PermissionChecker from '../../services/user/permissionChecker'
2+
import Permissions from '../../security/permissions'
3+
import ApiResponseHandler from '../apiResponseHandler'
4+
import AutomationExecutionService from '../../services/automationExecutionService'
5+
6+
/**
7+
* GET /tenant/{tenantId}/automation/{automationId}/executions
8+
* @summary Get all automation execution history for tenant and automation
9+
* @tag Automations
10+
* @security Bearer
11+
* @description Get all automation execution history for tenant and automation
12+
* @pathParam {string} tenantId - Your workspace/tenant ID
13+
* @pathParam {string} automationId - Your workspace/tenant ID
14+
* @queryParam {integer} [offset=0] - How many elements from the beginning would you like to skip
15+
* @queryParam {integer} [limit=10] - How many elements would you like to fetch
16+
* @response 200 - Ok
17+
* @responseContent {AutomationExecutionPage} 200.application/json
18+
* @responseExample {AutomationExecutionPage} 200.application/json.AutomationExecutionPage
19+
* @response 401 - Unauthorized
20+
* @response 429 - Too many requests
21+
*/
22+
export default async (req, res) => {
23+
try {
24+
new PermissionChecker(req).validateHas(Permissions.values.automationRead)
25+
26+
let offset = 0
27+
if (req.query.offset) {
28+
offset = parseInt(req.query.offset, 10)
29+
}
30+
let limit = 10
31+
if (req.query.limit) {
32+
limit = parseInt(req.query.limit, 10)
33+
}
34+
35+
const payload = await new AutomationExecutionService(req).findAndCountAll({
36+
automationId: req.params.automationId,
37+
offset,
38+
limit,
39+
})
40+
41+
await ApiResponseHandler.success(req, res, payload)
42+
} catch (error) {
43+
await ApiResponseHandler.error(req, res, error)
44+
}
45+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import PermissionChecker from '../../services/user/permissionChecker'
2+
import Permissions from '../../security/permissions'
3+
import AutomationService from '../../services/automationService'
4+
import ApiResponseHandler from '../apiResponseHandler'
5+
6+
/**
7+
* GET /tenant/{tenantId}/automation/{automationId}
8+
* @summary Get an existing automation data
9+
* @tag Automations
10+
* @security Bearer
11+
* @description Get an existing automation data in the tenant.
12+
* @pathParam {string} tenantId - Your workspace/tenant ID
13+
* @pathParam {string} automationId - Automation ID that you want to find
14+
* @response 200 - Ok
15+
* @responseContent {Automation} 200.application/json
16+
* @responseExample {Automation} 200.application/json.Automation
17+
* @response 401 - Unauthorized
18+
* @response 429 - Too many requests
19+
*/
20+
export default async (req, res) => {
21+
try {
22+
new PermissionChecker(req).validateHas(Permissions.values.automationRead)
23+
const payload = await new AutomationService(req).findById(req.params.automationId)
24+
25+
await ApiResponseHandler.success(req, res, payload)
26+
} catch (error) {
27+
await ApiResponseHandler.error(req, res, error)
28+
}
29+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import PermissionChecker from '../../services/user/permissionChecker'
2+
import Permissions from '../../security/permissions'
3+
import AutomationService from '../../services/automationService'
4+
import ApiResponseHandler from '../apiResponseHandler'
5+
import {
6+
AutomationCriteria,
7+
AutomationState,
8+
AutomationTrigger,
9+
AutomationType,
10+
} from '../../types/automationTypes'
11+
12+
/**
13+
* GET /tenant/{tenantId}/automation
14+
* @summary Get all automation data for tenant
15+
* @tag Automations
16+
* @security Bearer
17+
* @description Get all existing automation data for tenant.
18+
* @pathParam {string} tenantId - Your workspace/tenant ID
19+
* @queryParam {string} [filter[type]] - Filter by type of automation
20+
* @queryParam {string} [filter[trigger]] - Filter by trigger type of automation
21+
* @queryParam {string} [filter[state]] - Filter by state of automation
22+
* @queryParam {number} [offset] - Skip the first n results. Default 0.
23+
* @queryParam {number} [limit] - Limit the number of results. Default 50.
24+
* @response 200 - Ok
25+
* @responseContent {AutomationPage} 200.application/json
26+
* @responseExample {AutomationPage} 200.application/json.AutomationPage
27+
* @response 401 - Unauthorized
28+
* @response 429 - Too many requests
29+
*/
30+
export default async (req, res) => {
31+
try {
32+
new PermissionChecker(req).validateHas(Permissions.values.automationRead)
33+
34+
let offset = 0
35+
if (req.query.offset) {
36+
offset = parseInt(req.query.offset, 10)
37+
}
38+
let limit = 50
39+
if (req.query.limit) {
40+
limit = parseInt(req.query.limit, 10)
41+
}
42+
43+
const criteria: AutomationCriteria = {
44+
type: req.query.filter?.type ? (req.query.filter.type as AutomationType) : undefined,
45+
trigger: req.query.filter?.trigger
46+
? (req.query.filter?.trigger as AutomationTrigger)
47+
: undefined,
48+
state: req.query.filter?.state ? (req.query.filter.state as AutomationState) : undefined,
49+
limit,
50+
offset,
51+
}
52+
53+
const payload = await new AutomationService(req).findAndCountAll(criteria)
54+
55+
await ApiResponseHandler.success(req, res, payload)
56+
} catch (error) {
57+
await ApiResponseHandler.error(req, res, error)
58+
}
59+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import PermissionChecker from '../../services/user/permissionChecker'
2+
import Permissions from '../../security/permissions'
3+
import AutomationService from '../../services/automationService'
4+
import track from '../../segment/track'
5+
import ApiResponseHandler from '../apiResponseHandler'
6+
7+
/**
8+
* PUT /tenant/{tenantId}/automation/{automationId}
9+
* @summary Update an existing automation
10+
* @tag Automations
11+
* @security Bearer
12+
* @description Updates an existing automation in the tenant.
13+
* @pathParam {string} tenantId - Your workspace/tenant ID
14+
* @pathParam {string} automationId - Automation ID that you want to update
15+
* @bodyContent {AutomationUpdateInput} application/json
16+
* @response 200 - Ok
17+
* @responseContent {Automation} 200.application/json
18+
* @responseExample {Automation} 200.application/json.Automation
19+
* @response 401 - Unauthorized
20+
* @response 429 - Too many requests
21+
*/
22+
export default async (req, res) => {
23+
try {
24+
new PermissionChecker(req).validateHas(Permissions.values.automationUpdate)
25+
const payload = await new AutomationService(req).update(req.params.automationId, req.body.data)
26+
27+
track('Automation Updated', { ...payload }, { ...req })
28+
29+
await ApiResponseHandler.success(req, res, payload)
30+
} catch (error) {
31+
await ApiResponseHandler.error(req, res, error)
32+
}
33+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export default (app) => {
2+
app.post('/tenant/:tenantId/automation', require('./automationCreate').default)
3+
app.put('/tenant/:tenantId/automation/:automationId', require('./automationUpdate').default)
4+
app.delete('/tenant/:tenantId/automation/:automationId', require('./automationDestroy').default)
5+
app.get(
6+
'/tenant/:tenantId/automation/:automationId/executions',
7+
require('./automationExecutionFind').default,
8+
)
9+
app.get('/tenant/:tenantId/automation/:automationId', require('./automationFind').default)
10+
app.get('/tenant/:tenantId/automation', require('./automationList').default)
11+
}

backend/src/api/components/core.yaml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,3 +225,76 @@ components:
225225

226226
xml:
227227
name: Conversation
228+
229+
# defines automation type enum
230+
AutomationType:
231+
description: Automation type
232+
type: string
233+
enum: ['webhook']
234+
235+
# defines automation state enum
236+
AutomationState:
237+
description: Automation state
238+
type: string
239+
enum: ['active', 'disabled']
240+
241+
# defines automation triggers
242+
AutomationTrigger:
243+
description: What will trigger an automation
244+
type: string
245+
enum: ['new_activity', 'new_member']
246+
247+
# defines automation execution state
248+
AutomationExecutionState:
249+
description: What was the state of the automation execution
250+
type: string
251+
enum: ['success', 'error']
252+
253+
# defines webhook automation settings
254+
WebhookAutomationSettings:
255+
description: Settings used by automation with type webhook
256+
type: object
257+
required:
258+
- url
259+
properties:
260+
url:
261+
description: URL to POST webhook data to
262+
type: string
263+
format: uri
264+
265+
# defines new activity triggered automation settings
266+
NewActivityAutomationSettings:
267+
description: Settings used by automation that is triggered by new activities
268+
type: object
269+
required:
270+
- types
271+
- platforms
272+
- keywords
273+
- teamMemberActivities
274+
properties:
275+
types:
276+
description: 'If activity type matches any of these we should trigger this automation'
277+
type: array
278+
items:
279+
type: string
280+
platforms:
281+
description: 'If activity came from any of these platforms we should trigger this automation'
282+
type: array
283+
items:
284+
type: string
285+
keywords:
286+
description: 'If activity content contains any of these keywords we should trigger this automation'
287+
type: array
288+
items:
289+
type: string
290+
teamMemberActivities:
291+
description: 'If activity came from any of our team members - should we trigger automation or not?'
292+
type: boolean
293+
294+
# defines automation settings object
295+
AutomationSettings:
296+
description: Settings based on automation type and trigger - you need to provide union object of both automation type based settings and trigger based settings
297+
type: object
298+
anyOf:
299+
- $ref: '#/components/schemas/WebhookAutomationSettings'
300+
- $ref: '#/components/schemas/NewActivityAutomationSettings'

0 commit comments

Comments
 (0)