From 712bee297ac80b6d0fba4640ee4f61a22cc7eed0 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Thu, 5 Oct 2023 17:26:39 +0200 Subject: [PATCH 01/10] feat(placetel): Implement app structure with authentication --- .../src/apps/placetel/assets/favicon.svg | 6 ++++++ .../backend/src/apps/placetel/auth/index.ts | 21 +++++++++++++++++++ .../apps/placetel/auth/is-still-verified.ts | 9 ++++++++ .../apps/placetel/auth/verify-credentials.ts | 11 ++++++++++ .../apps/placetel/common/add-auth-header.ts | 11 ++++++++++ packages/backend/src/apps/placetel/index.ts | 16 ++++++++++++++ 6 files changed, 74 insertions(+) create mode 100644 packages/backend/src/apps/placetel/assets/favicon.svg create mode 100644 packages/backend/src/apps/placetel/auth/index.ts create mode 100644 packages/backend/src/apps/placetel/auth/is-still-verified.ts create mode 100644 packages/backend/src/apps/placetel/auth/verify-credentials.ts create mode 100644 packages/backend/src/apps/placetel/common/add-auth-header.ts create mode 100644 packages/backend/src/apps/placetel/index.ts diff --git a/packages/backend/src/apps/placetel/assets/favicon.svg b/packages/backend/src/apps/placetel/assets/favicon.svg new file mode 100644 index 0000000000..6df467ad37 --- /dev/null +++ b/packages/backend/src/apps/placetel/assets/favicon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/backend/src/apps/placetel/auth/index.ts b/packages/backend/src/apps/placetel/auth/index.ts new file mode 100644 index 0000000000..6ce142f0f9 --- /dev/null +++ b/packages/backend/src/apps/placetel/auth/index.ts @@ -0,0 +1,21 @@ +import verifyCredentials from './verify-credentials'; +import isStillVerified from './is-still-verified'; + +export default { + fields: [ + { + key: 'apiToken', + label: 'API Token', + type: 'string' as const, + required: true, + readOnly: false, + value: null, + placeholder: null, + description: 'Placetel API Token of your account.', + clickToCopy: false, + }, + ], + + verifyCredentials, + isStillVerified, +}; diff --git a/packages/backend/src/apps/placetel/auth/is-still-verified.ts b/packages/backend/src/apps/placetel/auth/is-still-verified.ts new file mode 100644 index 0000000000..66bb963ead --- /dev/null +++ b/packages/backend/src/apps/placetel/auth/is-still-verified.ts @@ -0,0 +1,9 @@ +import { IGlobalVariable } from '@automatisch/types'; +import verifyCredentials from './verify-credentials'; + +const isStillVerified = async ($: IGlobalVariable) => { + await verifyCredentials($); + return true; +}; + +export default isStillVerified; diff --git a/packages/backend/src/apps/placetel/auth/verify-credentials.ts b/packages/backend/src/apps/placetel/auth/verify-credentials.ts new file mode 100644 index 0000000000..7fab269f28 --- /dev/null +++ b/packages/backend/src/apps/placetel/auth/verify-credentials.ts @@ -0,0 +1,11 @@ +import { IGlobalVariable } from '@automatisch/types'; + +const verifyCredentials = async ($: IGlobalVariable) => { + const { data } = await $.http.get('/v2/me'); + + await $.auth.set({ + screenName: `${data.name} @ ${data.company}`, + }); +}; + +export default verifyCredentials; diff --git a/packages/backend/src/apps/placetel/common/add-auth-header.ts b/packages/backend/src/apps/placetel/common/add-auth-header.ts new file mode 100644 index 0000000000..8a77a4976e --- /dev/null +++ b/packages/backend/src/apps/placetel/common/add-auth-header.ts @@ -0,0 +1,11 @@ +import { TBeforeRequest } from '@automatisch/types'; + +const addAuthHeader: TBeforeRequest = ($, requestConfig) => { + if ($.auth.data?.apiToken) { + requestConfig.headers.Authorization = `Bearer ${$.auth.data.apiToken}`; + } + + return requestConfig; +}; + +export default addAuthHeader; diff --git a/packages/backend/src/apps/placetel/index.ts b/packages/backend/src/apps/placetel/index.ts new file mode 100644 index 0000000000..c8cdeb2da7 --- /dev/null +++ b/packages/backend/src/apps/placetel/index.ts @@ -0,0 +1,16 @@ +import defineApp from '../../helpers/define-app'; +import addAuthHeader from './common/add-auth-header'; +import auth from './auth'; + +export default defineApp({ + name: 'Placetel', + key: 'placetel', + iconUrl: '{BASE_URL}/apps/placetel/assets/favicon.svg', + authDocUrl: 'https://automatisch.io/docs/apps/placetel/connection', + supportsConnections: true, + baseUrl: 'https://placetel.de', + apiBaseUrl: 'https://api.placetel.de', + primaryColor: '069dd9', + beforeRequest: [addAuthHeader], + auth, +}); From 2099978b8fac08e3be8d5ac3cc220ac8257dcf2f Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Fri, 6 Oct 2023 11:49:10 +0200 Subject: [PATCH 02/10] fix: Add fields from substeps to getApps query --- packages/web/src/graphql/queries/get-apps.ts | 30 ++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/web/src/graphql/queries/get-apps.ts b/packages/web/src/graphql/queries/get-apps.ts index de417dacb1..3cfcdd1741 100644 --- a/packages/web/src/graphql/queries/get-apps.ts +++ b/packages/web/src/graphql/queries/get-apps.ts @@ -128,6 +128,36 @@ export const GET_APPS = gql` value } } + fields { + label + key + type + required + description + variables + value + dependsOn + options { + label + value + } + source { + type + name + arguments { + name + value + } + } + additionalFields { + type + name + arguments { + name + value + } + } + } } } } From ef087be4f07ece03052fce6e18f6f01bbb30dd93 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Fri, 6 Oct 2023 16:07:34 +0200 Subject: [PATCH 03/10] feat(placetel): Add hungup call trigger --- .../src/apps/placetel/dynamic-data/index.ts | 3 + .../dynamic-data/list-numbers/index.ts | 31 ++++++ packages/backend/src/apps/placetel/index.ts | 4 + .../placetel/triggers/hungup-call/index.ts | 97 +++++++++++++++++++ .../src/apps/placetel/triggers/index.ts | 3 + 5 files changed, 138 insertions(+) create mode 100644 packages/backend/src/apps/placetel/dynamic-data/index.ts create mode 100644 packages/backend/src/apps/placetel/dynamic-data/list-numbers/index.ts create mode 100644 packages/backend/src/apps/placetel/triggers/hungup-call/index.ts create mode 100644 packages/backend/src/apps/placetel/triggers/index.ts diff --git a/packages/backend/src/apps/placetel/dynamic-data/index.ts b/packages/backend/src/apps/placetel/dynamic-data/index.ts new file mode 100644 index 0000000000..819a758d6c --- /dev/null +++ b/packages/backend/src/apps/placetel/dynamic-data/index.ts @@ -0,0 +1,3 @@ +import listNumbers from './list-numbers'; + +export default [listNumbers]; diff --git a/packages/backend/src/apps/placetel/dynamic-data/list-numbers/index.ts b/packages/backend/src/apps/placetel/dynamic-data/list-numbers/index.ts new file mode 100644 index 0000000000..c3dff3a7d3 --- /dev/null +++ b/packages/backend/src/apps/placetel/dynamic-data/list-numbers/index.ts @@ -0,0 +1,31 @@ +import { IGlobalVariable, IJSONObject } from '@automatisch/types'; + +export default { + name: 'List numbers', + key: 'listNumbers', + + async run($: IGlobalVariable) { + const numbers: { + data: IJSONObject[]; + } = { + data: [], + }; + + const { data } = await $.http.get('/v2/numbers'); + + if (!data) { + return { data: [] }; + } + + if (data.length) { + for (const number of data) { + numbers.data.push({ + value: number.number, + name: number.number, + }); + } + } + + return numbers; + }, +}; diff --git a/packages/backend/src/apps/placetel/index.ts b/packages/backend/src/apps/placetel/index.ts index c8cdeb2da7..f682ea2691 100644 --- a/packages/backend/src/apps/placetel/index.ts +++ b/packages/backend/src/apps/placetel/index.ts @@ -1,6 +1,8 @@ import defineApp from '../../helpers/define-app'; import addAuthHeader from './common/add-auth-header'; import auth from './auth'; +import triggers from './triggers'; +import dynamicData from './dynamic-data'; export default defineApp({ name: 'Placetel', @@ -13,4 +15,6 @@ export default defineApp({ primaryColor: '069dd9', beforeRequest: [addAuthHeader], auth, + triggers, + dynamicData, }); diff --git a/packages/backend/src/apps/placetel/triggers/hungup-call/index.ts b/packages/backend/src/apps/placetel/triggers/hungup-call/index.ts new file mode 100644 index 0000000000..a88abd8788 --- /dev/null +++ b/packages/backend/src/apps/placetel/triggers/hungup-call/index.ts @@ -0,0 +1,97 @@ +import { IJSONObject } from '@automatisch/types'; +import defineTrigger from '../../../../helpers/define-trigger'; + +export default defineTrigger({ + name: 'Hungup Call', + key: 'hungupCall', + type: 'webhook', + description: 'Triggers when a call is hungup.', + arguments: [ + { + label: 'Numbers', + key: 'numbers', + type: 'dynamic' as const, + required: false, + description: '', + fields: [ + { + label: 'Number', + key: 'number', + type: 'dropdown' as const, + required: true, + description: + 'Filter events by number. If the numbers are not specified, all numbers will be notified.', + variables: true, + source: { + type: 'query', + name: 'getDynamicData', + arguments: [ + { + name: 'key', + value: 'listNumbers', + }, + ], + }, + }, + ], + }, + ], + + async testRun($) { + const response = await $.http.get('/v2/calls?order=desc'); + + if (!response?.data) { + return; + } + + const lastCall = response.data[0]; + + const computedWebhookEvent = { + type: lastCall.type, + duration: lastCall.duration, + from: lastCall.from_number, + to: lastCall.to_number.number.number, + call_id: lastCall.id.toString(), + event: 'HungUp', + direction: 'in', + }; + + const dataItem = { + raw: computedWebhookEvent, + meta: { + internalId: computedWebhookEvent.call_id.toString(), + }, + }; + + $.pushTriggerItem(dataItem); + }, + + async registerHook($) { + const numbers = ($.step.parameters.numbers as IJSONObject[]) + .map((number: IJSONObject) => number.number) + .filter(Boolean); + + const subscriptionPayload = { + service: 'string', + url: $.webhookUrl, + incoming: false, + outgoing: false, + hungup: true, + accepted: false, + phone: false, + numbers, + }; + + const headers = { + 'Content-Type': 'application/json', + }; + + const { data } = await $.http.put('/v2/subscriptions', subscriptionPayload); + + await $.flow.setRemoteWebhookId(data.id); + }, + + async unregisterHook($) { + await $.http.delete(`/v2/subscriptions/${$.flow.remoteWebhookId}`); + }, +}); diff --git a/packages/backend/src/apps/placetel/triggers/index.ts b/packages/backend/src/apps/placetel/triggers/index.ts new file mode 100644 index 0000000000..9a8974d82d --- /dev/null +++ b/packages/backend/src/apps/placetel/triggers/index.ts @@ -0,0 +1,3 @@ +import hungupCall from './hungup-call'; + +export default [hungupCall]; From a9fd261bab71953ba38d54e7b64037b1b43717d1 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Fri, 6 Oct 2023 16:14:24 +0200 Subject: [PATCH 04/10] refactor: Use json parser when content type is not specified --- packages/backend/src/app.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/backend/src/app.ts b/packages/backend/src/app.ts index 99c19bf2b8..570427be97 100644 --- a/packages/backend/src/app.ts +++ b/packages/backend/src/app.ts @@ -33,9 +33,11 @@ injectBullBoardHandler(app, serverAdapter); appAssetsHandler(app); app.use(morgan); + app.use( express.json({ limit: appConfig.requestBodySizeLimit, + type: () => true, verify(req, res, buf) { (req as IRequest).rawBody = buf; }, From 5fff9bdc0260e410ad45da2bf264e3770d89ecf7 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Fri, 6 Oct 2023 16:25:46 +0200 Subject: [PATCH 05/10] chore(placetel): Add missing type file --- packages/backend/src/apps/placetel/index.d.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/backend/src/apps/placetel/index.d.ts diff --git a/packages/backend/src/apps/placetel/index.d.ts b/packages/backend/src/apps/placetel/index.d.ts new file mode 100644 index 0000000000..e69de29bb2 From 0a334dff1dca0672dbc276503475064024eab527 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Fri, 6 Oct 2023 21:01:58 +0200 Subject: [PATCH 06/10] docs(placetel): Add connection and triggers pages --- packages/docs/pages/.vitepress/config.js | 9 +++++++++ packages/docs/pages/apps/placetel/connection.md | 7 +++++++ packages/docs/pages/apps/placetel/triggers.md | 12 ++++++++++++ packages/docs/pages/guide/available-apps.md | 1 + packages/docs/pages/public/favicons/placetel.svg | 6 ++++++ 5 files changed, 35 insertions(+) create mode 100644 packages/docs/pages/apps/placetel/connection.md create mode 100644 packages/docs/pages/apps/placetel/triggers.md create mode 100644 packages/docs/pages/public/favicons/placetel.svg diff --git a/packages/docs/pages/.vitepress/config.js b/packages/docs/pages/.vitepress/config.js index 1e1c0c608f..a1a77c5a03 100644 --- a/packages/docs/pages/.vitepress/config.js +++ b/packages/docs/pages/.vitepress/config.js @@ -234,6 +234,15 @@ export default defineConfig({ { text: 'Connection', link: '/apps/pipedrive/connection' }, ], }, + { + text: 'Placetel', + collapsible: true, + collapsed: true, + items: [ + { text: 'Triggers', link: '/apps/placetel/triggers' }, + { text: 'Connection', link: '/apps/placetel/connection' }, + ], + }, { text: 'PostgreSQL', collapsible: true, diff --git a/packages/docs/pages/apps/placetel/connection.md b/packages/docs/pages/apps/placetel/connection.md new file mode 100644 index 0000000000..a0f0728ee7 --- /dev/null +++ b/packages/docs/pages/apps/placetel/connection.md @@ -0,0 +1,7 @@ +# Placetel + +1. Go to [AppStore page](https://web.placetel.de/integrations) on Placetel. +2. Search for `Web API` and click to `Jetzt buchen`. +3. Click to `Neuen API-Token erstellen` button and copy the API Token. +4. Paste the copied API Token into the `API Token` field in Automatisch. +5. Now, you can start using Placetel integration with Automatisch! diff --git a/packages/docs/pages/apps/placetel/triggers.md b/packages/docs/pages/apps/placetel/triggers.md new file mode 100644 index 0000000000..99c86c1ce8 --- /dev/null +++ b/packages/docs/pages/apps/placetel/triggers.md @@ -0,0 +1,12 @@ +--- +favicon: /favicons/placetel.svg +items: + - name: Hungup call + desc: Triggers when a call is hungup. +--- + + + + diff --git a/packages/docs/pages/guide/available-apps.md b/packages/docs/pages/guide/available-apps.md index 29b5e7f8c1..f293d57e4c 100644 --- a/packages/docs/pages/guide/available-apps.md +++ b/packages/docs/pages/guide/available-apps.md @@ -24,6 +24,7 @@ The following integrations are currently supported by Automatisch. - [Odoo](/apps/odoo/actions) - [OpenAI](/apps/openai/actions) - [Pipedrive](/apps/pipedrive/triggers) +- [Placetel](/apps/placetel/triggers) - [PostgreSQL](/apps/postgresql/actions) - [RSS](/apps/rss/triggers) - [Salesforce](/apps/salesforce/triggers) diff --git a/packages/docs/pages/public/favicons/placetel.svg b/packages/docs/pages/public/favicons/placetel.svg new file mode 100644 index 0000000000..6df467ad37 --- /dev/null +++ b/packages/docs/pages/public/favicons/placetel.svg @@ -0,0 +1,6 @@ + + + + + + From 5eed84f9e5d790e4057569b140bfc7b9774738d5 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Mon, 9 Oct 2023 12:18:11 +0200 Subject: [PATCH 07/10] feat: Add run method to placetel hungup trigger --- .../src/apps/placetel/triggers/hungup-call/index.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/backend/src/apps/placetel/triggers/hungup-call/index.ts b/packages/backend/src/apps/placetel/triggers/hungup-call/index.ts index a88abd8788..5fd0256d5d 100644 --- a/packages/backend/src/apps/placetel/triggers/hungup-call/index.ts +++ b/packages/backend/src/apps/placetel/triggers/hungup-call/index.ts @@ -1,3 +1,4 @@ +import Crypto from 'crypto'; import { IJSONObject } from '@automatisch/types'; import defineTrigger from '../../../../helpers/define-trigger'; @@ -37,6 +38,17 @@ export default defineTrigger({ }, ], + async run($) { + const dataItem = { + raw: $.request.body, + meta: { + internalId: Crypto.randomUUID(), + }, + }; + + $.pushTriggerItem(dataItem); + }, + async testRun($) { const response = await $.http.get('/v2/calls?order=desc'); From 265d57d8b7aaafea5e7f404cc1a122d2cee8fc87 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Mon, 9 Oct 2023 12:18:55 +0200 Subject: [PATCH 08/10] refactor(placetel): Remove redundant header variable --- .../backend/src/apps/placetel/triggers/hungup-call/index.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/backend/src/apps/placetel/triggers/hungup-call/index.ts b/packages/backend/src/apps/placetel/triggers/hungup-call/index.ts index 5fd0256d5d..4766722744 100644 --- a/packages/backend/src/apps/placetel/triggers/hungup-call/index.ts +++ b/packages/backend/src/apps/placetel/triggers/hungup-call/index.ts @@ -94,10 +94,6 @@ export default defineTrigger({ numbers, }; - const headers = { - 'Content-Type': 'application/json', - }; - const { data } = await $.http.put('/v2/subscriptions', subscriptionPayload); await $.flow.setRemoteWebhookId(data.id); From b59840cb77862063100c8a9e3ebaa2b865ed57ab Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Mon, 9 Oct 2023 12:49:59 +0200 Subject: [PATCH 09/10] feat(placetel): Add types to hungup call trigger --- .../placetel/triggers/hungup-call/index.ts | 78 ++++++++++++++----- 1 file changed, 57 insertions(+), 21 deletions(-) diff --git a/packages/backend/src/apps/placetel/triggers/hungup-call/index.ts b/packages/backend/src/apps/placetel/triggers/hungup-call/index.ts index 4766722744..99e1d5e033 100644 --- a/packages/backend/src/apps/placetel/triggers/hungup-call/index.ts +++ b/packages/backend/src/apps/placetel/triggers/hungup-call/index.ts @@ -8,6 +8,35 @@ export default defineTrigger({ type: 'webhook', description: 'Triggers when a call is hungup.', arguments: [ + { + label: 'Types', + key: 'types', + type: 'dynamic' as const, + required: false, + description: '', + fields: [ + { + label: 'Type', + key: 'type', + type: 'dropdown' as const, + required: true, + description: + 'Filter events by type. If the types are not specified, all types will be notified.', + variables: true, + options: [ + { label: 'All', value: 'all' }, + { label: 'Voicemail', value: 'voicemail' }, + { label: 'Missed', value: 'missed' }, + { label: 'Blocked', value: 'blocked' }, + { label: 'Accepted', value: 'accepted' }, + { label: 'Busy', value: 'busy' }, + { label: 'Cancelled', value: 'cancelled' }, + { label: 'Unavailable', value: 'unavailable' }, + { label: 'Congestion', value: 'congestion' }, + ], + }, + ], + }, { label: 'Numbers', key: 'numbers', @@ -39,39 +68,46 @@ export default defineTrigger({ ], async run($) { - const dataItem = { - raw: $.request.body, - meta: { - internalId: Crypto.randomUUID(), - }, - }; + let types = ($.step.parameters.types as IJSONObject[]).map( + (type) => type.type + ); - $.pushTriggerItem(dataItem); - }, + if (types.length === 0) { + types = ['all']; + } - async testRun($) { - const response = await $.http.get('/v2/calls?order=desc'); + if (types.includes($.request.body.type) || types.includes('all')) { + const dataItem = { + raw: $.request.body, + meta: { + internalId: Crypto.randomUUID(), + }, + }; - if (!response?.data) { - return; + $.pushTriggerItem(dataItem); } + }, - const lastCall = response.data[0]; + async testRun($) { + const types = ($.step.parameters.types as IJSONObject[]).map( + (type) => type.type + ); - const computedWebhookEvent = { - type: lastCall.type, - duration: lastCall.duration, - from: lastCall.from_number, - to: lastCall.to_number.number.number, - call_id: lastCall.id.toString(), + const sampleEventData = { + type: types[0] || 'missed', + duration: 0, + from: '01662223344', + to: '02229997766', + call_id: + '9c81d4776d3977d920a558cbd4f0950b168e32bd4b5cc141a85b6ed3aa530107', event: 'HungUp', direction: 'in', }; const dataItem = { - raw: computedWebhookEvent, + raw: sampleEventData, meta: { - internalId: computedWebhookEvent.call_id.toString(), + internalId: sampleEventData.call_id.toString(), }, }; From c786d7549ae04a836ccb674d2ca0682e2da64ca1 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Mon, 9 Oct 2023 14:11:39 +0200 Subject: [PATCH 10/10] refactor(placetel): No need to stringify call ID --- .../backend/src/apps/placetel/triggers/hungup-call/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/apps/placetel/triggers/hungup-call/index.ts b/packages/backend/src/apps/placetel/triggers/hungup-call/index.ts index 99e1d5e033..ac466a7573 100644 --- a/packages/backend/src/apps/placetel/triggers/hungup-call/index.ts +++ b/packages/backend/src/apps/placetel/triggers/hungup-call/index.ts @@ -107,7 +107,7 @@ export default defineTrigger({ const dataItem = { raw: sampleEventData, meta: { - internalId: sampleEventData.call_id.toString(), + internalId: sampleEventData.call_id, }, };