Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(placetel): Implement app structure with authentication #1324

Merged
merged 10 commits into from
Oct 9, 2023
2 changes: 2 additions & 0 deletions packages/backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
},
Expand Down
6 changes: 6 additions & 0 deletions packages/backend/src/apps/placetel/assets/favicon.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions packages/backend/src/apps/placetel/auth/index.ts
Original file line number Diff line number Diff line change
@@ -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,
};
9 changes: 9 additions & 0 deletions packages/backend/src/apps/placetel/auth/is-still-verified.ts
Original file line number Diff line number Diff line change
@@ -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;
11 changes: 11 additions & 0 deletions packages/backend/src/apps/placetel/auth/verify-credentials.ts
Original file line number Diff line number Diff line change
@@ -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;
11 changes: 11 additions & 0 deletions packages/backend/src/apps/placetel/common/add-auth-header.ts
Original file line number Diff line number Diff line change
@@ -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;
3 changes: 3 additions & 0 deletions packages/backend/src/apps/placetel/dynamic-data/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import listNumbers from './list-numbers';

export default [listNumbers];
Original file line number Diff line number Diff line change
@@ -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;
},
};
Empty file.
20 changes: 20 additions & 0 deletions packages/backend/src/apps/placetel/index.ts
Original file line number Diff line number Diff line change
@@ -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,
});
141 changes: 141 additions & 0 deletions packages/backend/src/apps/placetel/triggers/hungup-call/index.ts
Original file line number Diff line number Diff line change
@@ -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}`);
},
});
3 changes: 3 additions & 0 deletions packages/backend/src/apps/placetel/triggers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import hungupCall from './hungup-call';

export default [hungupCall];
9 changes: 9 additions & 0 deletions packages/docs/pages/.vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions packages/docs/pages/apps/placetel/connection.md
Original file line number Diff line number Diff line change
@@ -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!
12 changes: 12 additions & 0 deletions packages/docs/pages/apps/placetel/triggers.md
Original file line number Diff line number Diff line change
@@ -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 />
1 change: 1 addition & 0 deletions packages/docs/pages/guide/available-apps.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions packages/docs/pages/public/favicons/placetel.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions packages/web/src/graphql/queries/get-apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
}
}
}
Expand Down