Merge pull request #1324 from automatisch/placetel

feat(placetel): Implement app structure with authentication
This commit is contained in:
Ömer Faruk Aydın
2023-10-09 14:19:58 +02:00
committed by GitHub
18 changed files with 323 additions and 0 deletions

View File

@@ -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;
},

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 90">
<g fill="none" fill-rule="evenodd">
<path d="M45 15c13.807 0 25 11.193 25 25S58.807 65 45 65 20 53.807 20 40s11.193-25 25-25Zm0 14c-6.075 0-11 4.925-11 11s4.925 11 11 11 11-4.925 11-11-4.925-11-11-11Z" fill="#069DD9" fill-rule="nonzero"/>
<path fill="#69B52A" d="M20 41h14v33H20z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 368 B

View File

@@ -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,
};

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -0,0 +1,3 @@
import listNumbers from './list-numbers';
export default [listNumbers];

View File

@@ -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;
},
};

View File

View File

@@ -0,0 +1,20 @@
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',
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,
triggers,
dynamicData,
});

View File

@@ -0,0 +1,141 @@
import Crypto from 'crypto';
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: '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',
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 run($) {
let types = ($.step.parameters.types as IJSONObject[]).map(
(type) => type.type
);
if (types.length === 0) {
types = ['all'];
}
if (types.includes($.request.body.type) || types.includes('all')) {
const dataItem = {
raw: $.request.body,
meta: {
internalId: Crypto.randomUUID(),
},
};
$.pushTriggerItem(dataItem);
}
},
async testRun($) {
const types = ($.step.parameters.types as IJSONObject[]).map(
(type) => type.type
);
const sampleEventData = {
type: types[0] || 'missed',
duration: 0,
from: '01662223344',
to: '02229997766',
call_id:
'9c81d4776d3977d920a558cbd4f0950b168e32bd4b5cc141a85b6ed3aa530107',
event: 'HungUp',
direction: 'in',
};
const dataItem = {
raw: sampleEventData,
meta: {
internalId: sampleEventData.call_id,
},
};
$.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 { data } = await $.http.put('/v2/subscriptions', subscriptionPayload);
await $.flow.setRemoteWebhookId(data.id);
},
async unregisterHook($) {
await $.http.delete(`/v2/subscriptions/${$.flow.remoteWebhookId}`);
},
});

View File

@@ -0,0 +1,3 @@
import hungupCall from './hungup-call';
export default [hungupCall];

View File

@@ -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,

View File

@@ -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!

View File

@@ -0,0 +1,12 @@
---
favicon: /favicons/placetel.svg
items:
- name: Hungup call
desc: Triggers when a call is hungup.
---
<script setup>
import CustomListing from '../../components/CustomListing.vue'
</script>
<CustomListing />

View File

@@ -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)

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 90">
<g fill="none" fill-rule="evenodd">
<path d="M45 15c13.807 0 25 11.193 25 25S58.807 65 45 65 20 53.807 20 40s11.193-25 25-25Zm0 14c-6.075 0-11 4.925-11 11s4.925 11 11 11 11-4.925 11-11-4.925-11-11-11Z" fill="#069DD9" fill-rule="nonzero"/>
<path fill="#69B52A" d="M20 41h14v33H20z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 368 B

View File

@@ -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
}
}
}
}
}
}