Merge branch 'main' into main
@ -12,7 +12,7 @@
|
||||
|
||||
There are other existing solutions in the market, like Zapier and Integromat, so you might be wondering why you should use Automatisch.
|
||||
|
||||
✅ The most significant advantage of having Automatisch is keeping your data on your own servers. Not all companies want to use an automation service in the cloud, and the current open-source or self-hosted solutions mainly focus on developers rather than a user without a technical background.
|
||||
✅ One of the main benefits of using Automatisch is that it allows you to store your data on your own servers, which is essential for businesses that handle sensitive user information and cannot risk sharing it with external cloud services. This is especially relevant for industries such as healthcare and finance, as well as for European companies that must adhere to the General Data Protection Regulation (GDPR).
|
||||
|
||||
🤓 Your contributions are vital to the development of Automatisch. As an open-source software, anyone can have an impact on how it is being developed.
|
||||
|
||||
@ -32,10 +32,12 @@ git clone git@github.com:automatisch/automatisch.git
|
||||
cd automatisch
|
||||
|
||||
# Start
|
||||
docker compose -p automatisch up
|
||||
docker compose up
|
||||
```
|
||||
|
||||
You can use `user@automatisch.io` email address and `sample` password to login to Automatisch. You can also change your email and password later on from the settings page.
|
||||
You can use `user@automatisch.io` email address and `sample` password to login to Automatisch. Please do not forget to change your email and password from the settings page.
|
||||
|
||||
For other installation types, you can check the [installation](https://automatisch.io/docs/guide/installation) guide.
|
||||
|
||||
## Community Links
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@ services:
|
||||
- POSTGRES_USERNAME=automatisch_user
|
||||
- POSTGRES_PASSWORD=automatisch_password
|
||||
- ENCRYPTION_KEY
|
||||
- WEBHOOK_SECRET_KEY
|
||||
- APP_SECRET_KEY
|
||||
volumes:
|
||||
- automatisch_storage:/automatisch/storage
|
||||
@ -41,6 +42,7 @@ services:
|
||||
- POSTGRES_USERNAME=automatisch_user
|
||||
- POSTGRES_PASSWORD=automatisch_password
|
||||
- ENCRYPTION_KEY
|
||||
- WEBHOOK_SECRET_KEY
|
||||
- APP_SECRET_KEY
|
||||
- WORKER=true
|
||||
volumes:
|
||||
|
||||
@ -2,9 +2,13 @@
|
||||
FROM node:16-alpine
|
||||
WORKDIR /automatisch
|
||||
|
||||
RUN apk --no-cache add --virtual build-dependencies python3 build-base
|
||||
|
||||
COPY ./entrypoint.sh /entrypoint.sh
|
||||
|
||||
RUN yarn global add @automatisch/cli@0.2.0
|
||||
RUN yarn global add @automatisch/cli@0.3.0
|
||||
|
||||
RUN apk del build-dependencies python3 build-base
|
||||
|
||||
EXPOSE 3000
|
||||
ENTRYPOINT ["sh", "/entrypoint.sh"]
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM automatischio/automatisch:0.2.0
|
||||
FROM automatischio/automatisch:0.3.0
|
||||
WORKDIR /automatisch
|
||||
|
||||
RUN apk add --no-cache openssl dos2unix
|
||||
|
||||
@ -5,14 +5,22 @@ set -e
|
||||
if [ ! -f /automatisch/storage/.env ]; then
|
||||
>&2 echo "Saving environment variables"
|
||||
ENCRYPTION_KEY="${ENCRYPTION_KEY:-$(openssl rand -base64 36)}"
|
||||
WEBHOOK_SECRET_KEY="${WEBHOOK_SECRET_KEY:-$(openssl rand -base64 36)}"
|
||||
APP_SECRET_KEY="${APP_SECRET_KEY:-$(openssl rand -base64 36)}"
|
||||
echo "ENCRYPTION_KEY=$ENCRYPTION_KEY" >> /automatisch/storage/.env
|
||||
echo "WEBHOOK_SECRET_KEY=$WEBHOOK_SECRET_KEY" >> /automatisch/storage/.env
|
||||
echo "APP_SECRET_KEY=$APP_SECRET_KEY" >> /automatisch/storage/.env
|
||||
fi
|
||||
|
||||
# initiate env. vars. from /automatisch/storage/.env file
|
||||
export $(grep -v '^#' /automatisch/storage/.env | xargs)
|
||||
|
||||
# migration for webhook secret key, will be removed in the future.
|
||||
if [[ -z "${WEBHOOK_SECRET_KEY}" ]]; then
|
||||
WEBHOOK_SECRET_KEY="$(openssl rand -base64 36)"
|
||||
echo "WEBHOOK_SECRET_KEY=$WEBHOOK_SECRET_KEY" >> /automatisch/storage/.env
|
||||
fi
|
||||
|
||||
echo "Environment variables have been set!"
|
||||
|
||||
sh /entrypoint.sh
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"command": {
|
||||
|
||||
@ -11,6 +11,7 @@ POSTGRES_USERNAME=automatish_development_user
|
||||
POSTGRES_PASSWORD=
|
||||
POSTGRES_ENABLE_SSL=false
|
||||
ENCRYPTION_KEY=sample-encryption-key
|
||||
WEBHOOK_SECRET_KEY=sample-webhook-key
|
||||
APP_SECRET_KEY=sample-app-secret-key
|
||||
REDIS_PORT=6379
|
||||
REDIS_HOST=127.0.0.1
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@automatisch/backend",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||
"scripts": {
|
||||
@ -22,7 +22,7 @@
|
||||
"prebuild": "rm -rf ./dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@automatisch/web": "^0.2.0",
|
||||
"@automatisch/web": "^0.3.0",
|
||||
"@bull-board/express": "^3.10.1",
|
||||
"@graphql-tools/graphql-file-loader": "^7.3.4",
|
||||
"@graphql-tools/load": "^7.5.2",
|
||||
@ -37,7 +37,7 @@
|
||||
"crypto-js": "^4.1.1",
|
||||
"debug": "~2.6.9",
|
||||
"dotenv": "^10.0.0",
|
||||
"express": "~4.16.1",
|
||||
"express": "~4.17.3",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"express-graphql": "^0.12.0",
|
||||
"fast-xml-parser": "^4.0.11",
|
||||
@ -93,7 +93,7 @@
|
||||
"url": "https://github.com/automatisch/automatisch/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@automatisch/types": "^0.2.0",
|
||||
"@automatisch/types": "^0.3.0",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/bull": "^3.15.8",
|
||||
"@types/cors": "^2.8.12",
|
||||
|
||||
56
packages/backend/src/apps/delay/actions/delay-for/index.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Delay For',
|
||||
key: 'delayFor',
|
||||
description:
|
||||
'Delays the execution of the next action by a specified amount of time.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Delay for unit',
|
||||
key: 'delayForUnit',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
value: null,
|
||||
description: 'Delay for unit, e.g. minutes, hours, days, weeks.',
|
||||
variables: false,
|
||||
options: [
|
||||
{
|
||||
label: 'Minutes',
|
||||
value: 'minutes',
|
||||
},
|
||||
{
|
||||
label: 'Hours',
|
||||
value: 'hours',
|
||||
},
|
||||
{
|
||||
label: 'Days',
|
||||
value: 'days',
|
||||
},
|
||||
{
|
||||
label: 'Weeks',
|
||||
value: 'weeks',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Delay for value',
|
||||
key: 'delayForValue',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Delay for value, use a number, e.g. 1, 2, 3.',
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const { delayForUnit, delayForValue } = $.step.parameters;
|
||||
|
||||
const dataItem = {
|
||||
delayForUnit,
|
||||
delayForValue,
|
||||
};
|
||||
|
||||
$.setActionItem({ raw: dataItem });
|
||||
},
|
||||
});
|
||||
28
packages/backend/src/apps/delay/actions/delay-until/index.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Delay Until',
|
||||
key: 'delayUntil',
|
||||
description:
|
||||
'Delays the execution of the next action until a specified date.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Delay until (Date)',
|
||||
key: 'delayUntil',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Delay until the date. E.g. 2022-12-18',
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const { delayUntil } = $.step.parameters;
|
||||
|
||||
const dataItem = {
|
||||
delayUntil,
|
||||
};
|
||||
|
||||
$.setActionItem({ raw: dataItem });
|
||||
},
|
||||
});
|
||||
4
packages/backend/src/apps/delay/actions/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import delayFor from './delay-for';
|
||||
import delayUntil from './delay-until';
|
||||
|
||||
export default [delayFor, delayUntil];
|
||||
7
packages/backend/src/apps/delay/assets/favicon.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 122.88 100.6" style="enable-background:new 0 0 122.88 100.6" xml:space="preserve">
|
||||
<style type="text/css">.st0{fill:#272727;} .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#000000;}</style>
|
||||
<g>
|
||||
<path class="st0" d="M72.58,0c6.8,0,13.3,1.36,19.23,3.81c6.16,2.55,11.7,6.29,16.33,10.92l0,0c4.63,4.63,8.37,10.17,10.92,16.34 c2.46,5.93,3.81,12.43,3.81,19.23c0,6.8-1.36,13.3-3.81,19.23c-2.55,6.16-6.29,11.7-10.92,16.33l0,0 c-4.63,4.63-10.17,8.37-16.34,10.92c-5.93,2.46-12.43,3.81-19.23,3.81c-6.8,0-13.3-1.36-19.23-3.81 c-6.15-2.55-11.69-6.28-16.33-10.92l-0.01-0.01c-4.64-4.64-8.37-10.17-10.92-16.33c-0.79-1.91-1.47-3.87-2.02-5.89 c1.05,0.1,2.12,0.15,3.2,0.15c2.05,0,4.05-0.19,6-0.54c0.32,0.97,0.67,1.93,1.06,2.87c2.09,5.05,5.17,9.6,8.99,13.43 c3.82,3.82,8.38,6.9,13.43,8.99c4.87,2.02,10.21,3.13,15.83,3.13c5.62,0,10.96-1.11,15.83-3.13c5.05-2.09,9.6-5.17,13.43-8.99 c3.82-3.82,6.9-8.38,8.99-13.43c2.02-4.87,3.13-10.21,3.13-15.83c0-5.62-1.11-10.96-3.13-15.83c-2.09-5.05-5.17-9.6-8.99-13.43 c-3.82-3.82-8.38-6.9-13.43-8.99c-4.87-2.02-10.21-3.13-15.83-3.13c-5.62,0-10.96,1.11-15.83,3.13c-0.44,0.18-0.87,0.37-1.3,0.56 c-1.65-2.61-3.66-4.97-5.95-7.02c1.25-0.65,2.53-1.24,3.84-1.79C59.28,1.36,65.78,0,72.58,0L72.58,0z M66.8,26.39 c0-1.23,0.5-2.35,1.31-3.16c0.81-0.81,1.93-1.31,3.16-1.31c1.23,0,2.35,0.5,3.16,1.31c0.81,0.81,1.31,1.93,1.31,3.16v23.47 l17.54,10.4c1.05,0.62,1.76,1.62,2.05,2.73c0.28,1.1,0.15,2.31-0.47,3.37l0,0.01l0,0c-0.62,1.05-1.62,1.76-2.73,2.05 c-1.1,0.28-2.31,0.15-3.37-0.47l-0.01,0l0,0L69.1,56.29c-0.67-0.38-1.24-0.92-1.64-1.57c-0.42-0.68-0.66-1.48-0.66-2.32V26.39 L66.8,26.39z"/>
|
||||
<path class="st1" d="M27.27,3.18c15.06,0,27.27,12.21,27.27,27.27c0,15.06-12.21,27.27-27.27,27.27C12.21,57.73,0,45.52,0,30.45 C0,15.39,12.21,3.18,27.27,3.18L27.27,3.18z M24.35,41.34h5.82v5.16h-5.82V41.34L24.35,41.34L24.35,41.34z M30.17,37.77h-5.82 c-0.58-7.07-1.8-11.56-1.8-18.63c0-2.61,2.12-4.72,4.72-4.72c2.61,0,4.72,2.12,4.72,4.72C32,26.2,30.76,30.7,30.17,37.77 L30.17,37.77L30.17,37.77L30.17,37.77z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
0
packages/backend/src/apps/delay/index.d.ts
vendored
Normal file
14
packages/backend/src/apps/delay/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import actions from './actions';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Delay',
|
||||
key: 'delay',
|
||||
iconUrl: '{BASE_URL}/apps/delay/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/delay/connection',
|
||||
supportsConnections: false,
|
||||
baseUrl: '',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '001F52',
|
||||
actions,
|
||||
});
|
||||
@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="762.564" height="769.312" viewBox="0 0 772772" fill="none">
|
||||
<g clip-path="url(#clip0_493_7535)">
|
||||
<path d="M747.782 474.701C725.051 423.2 683.614 383.657 631.048 363.294L413.561 279.117L429.189 369.569L602.633 436.697C670.591 462.98 704.451 539.58 678.168 607.537C651.885 675.494 575.285 709.354 507.328 683.071C473.231 669.811 446.711 643.883 432.741 610.023C429.426 601.854 426.939 593.33 425.164 584.569L358.509 636.07C358.982 637.372 359.338 638.674 359.811 639.858C382.069 693.963 424.216 735.282 478.795 756.356C503.776 765.946 529.349 770.563 554.566 770.563C639.098 770.563 719.131 719.299 751.334 635.833C771.697 583.267 770.277 526.083 747.545 474.583L747.782 474.701Z" fill="#02AFC7"/>
|
||||
<path d="M440.91 419.881L294.459 533.064C287.119 538.628 279.187 543.482 270.781 547.507C237.631 563.254 200.693 564.556 166.477 551.414C133.564 538.628 107.636 513.884 93.3109 481.563C78.9854 449.242 78.1567 413.369 90.943 380.456C103.729 347.543 128.473 321.615 160.794 307.29C190.866 294.03 224.134 292.491 255.271 302.672L241.064 219.206C147.061 207.13 53.2943 259.815 17.6583 352.042C-2.70517 404.608 -1.28446 461.792 21.4468 513.292C44.1781 564.793 85.6154 604.336 138.181 624.699C162.57 634.171 188.38 638.788 214.189 638.788C245.208 638.788 276.227 632.039 304.641 618.543C318.019 612.268 330.687 604.454 342.29 595.575L527.81 452.083L440.91 419.762V419.881Z" fill="#31DEA4"/>
|
||||
<path d="M624.42 193.396C629.629 229.624 619.921 265.379 597.19 294.148C594.822 297.108 592.335 299.831 589.731 302.554L665.62 333.454C696.402 289.649 709.899 235.307 702.203 182.149C694.153 126.386 665.028 77.135 619.921 43.3933C574.932 9.76989 519.406 -4.43717 463.643 3.49511C407.88 11.5458 358.629 40.6703 324.888 85.7777C291.146 130.885 276.939 186.411 284.989 242.055L323.23 457.529L393.673 404.371L362.773 230.334C357.919 195.646 366.798 161.075 387.872 133.016C409.064 104.72 439.846 86.488 474.89 81.3972C509.935 76.4247 544.623 85.3041 572.801 106.378C601.096 127.57 619.329 158.47 624.42 193.396V193.396Z" fill="#FF5986"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_493_7535">
|
||||
<rect width="762.564" height="769.312" fill="white" transform="translate(3.33301 1.36719)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
43
packages/backend/src/apps/flowers-software/auth/index.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import verifyCredentials from './verify-credentials';
|
||||
import isStillVerified from './is-still-verified';
|
||||
|
||||
export default {
|
||||
fields: [
|
||||
{
|
||||
key: 'username',
|
||||
label: 'Username',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: null,
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'password',
|
||||
label: 'Password',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: null,
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'apiKey',
|
||||
label: 'API Key',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: null,
|
||||
clickToCopy: false,
|
||||
},
|
||||
],
|
||||
|
||||
verifyCredentials,
|
||||
isStillVerified,
|
||||
};
|
||||
@ -0,0 +1,10 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import verifyCredentials from './verify-credentials';
|
||||
|
||||
const isStillVerified = async ($: IGlobalVariable) => {
|
||||
await verifyCredentials($);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export default isStillVerified;
|
||||
@ -0,0 +1,20 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import getWebhooks from '../common/get-webhooks';
|
||||
|
||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
const response = await getWebhooks($);
|
||||
const successful = Array.isArray(response.data);
|
||||
|
||||
if (!successful) {
|
||||
throw new Error('Failed while authorizing!');
|
||||
}
|
||||
|
||||
await $.auth.set({
|
||||
screenName: $.auth.data.username,
|
||||
username: $.auth.data.username,
|
||||
password: $.auth.data.password,
|
||||
apiKey: $.auth.data.apiKey,
|
||||
});
|
||||
};
|
||||
|
||||
export default verifyCredentials;
|
||||
@ -0,0 +1,18 @@
|
||||
import { TBeforeRequest } from '@automatisch/types';
|
||||
|
||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||
const { data } = $.auth;
|
||||
|
||||
if (data?.username && data.password && data.apiKey) {
|
||||
requestConfig.headers['x-api-key'] = data.apiKey as string;
|
||||
|
||||
requestConfig.auth = {
|
||||
username: data.username as string,
|
||||
password: data.password as string,
|
||||
};
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default addAuthHeader;
|
||||
@ -0,0 +1,5 @@
|
||||
import type { IGlobalVariable } from "@automatisch/types";
|
||||
|
||||
export default async function getWebhooks($: IGlobalVariable) {
|
||||
return await $.http.get('/v2/public/api/webhooks');
|
||||
}
|
||||
@ -0,0 +1,488 @@
|
||||
const webhookFilters = [
|
||||
{
|
||||
label: "Contact Company Created",
|
||||
value: "CONTACT_COMPANY_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Contact Company Deleted",
|
||||
value: "CONTACT_COMPANY_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Contact Company Updated",
|
||||
value: "CONTACT_COMPANY_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Contact Created",
|
||||
value: "CONTACT_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Contact Deleted",
|
||||
value: "CONTACT_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Contact Updated",
|
||||
value: "CONTACT_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Customer Created",
|
||||
value: "CUSTOMER_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Customer Updated",
|
||||
value: "CUSTOMER_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Document Deleted",
|
||||
value: "DOCUMENT_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Document Downloaded",
|
||||
value: "DOCUMENT_DOWNLOADED"
|
||||
},
|
||||
{
|
||||
label: "Document Saved",
|
||||
value: "DOCUMENT_SAVED"
|
||||
},
|
||||
{
|
||||
label: "Document Updated",
|
||||
value: "DOCUMENT_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Archived",
|
||||
value: "FLOW_ARCHIVED"
|
||||
},
|
||||
{
|
||||
label: "Flow Created",
|
||||
value: "FLOW_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Automation Action Created",
|
||||
value: "FLOW_OBJECT_AUTOMATION_ACTION_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Automation Action Deleted",
|
||||
value: "FLOW_OBJECT_AUTOMATION_ACTION_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Automation Created",
|
||||
value: "FLOW_OBJECT_AUTOMATION_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Automation Deleted",
|
||||
value: "FLOW_OBJECT_AUTOMATION_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Automation Updated",
|
||||
value: "FLOW_OBJECT_AUTOMATION_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Automation Webdav Created",
|
||||
value: "FLOW_OBJECT_AUTOMATION_WEBDAV_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Automation Webdav Deleted",
|
||||
value: "FLOW_OBJECT_AUTOMATION_WEBDAV_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Automation Webdav Updated",
|
||||
value: "FLOW_OBJECT_AUTOMATION_WEBDAV_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Created",
|
||||
value: "FLOW_OBJECT_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Deleted",
|
||||
value: "FLOW_OBJECT_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Document Added",
|
||||
value: "FLOW_OBJECT_DOCUMENT_ADDED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Document Removed",
|
||||
value: "FLOW_OBJECT_DOCUMENT_REMOVED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Resource Created",
|
||||
value: "FLOW_OBJECT_RESOURCE_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Resource Deleted",
|
||||
value: "FLOW_OBJECT_RESOURCE_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Resource Updated",
|
||||
value: "FLOW_OBJECT_RESOURCE_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Task Condition Created",
|
||||
value: "FLOW_OBJECT_TASK_CONDITION_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Task Condition Deleted",
|
||||
value: "FLOW_OBJECT_TASK_CONDITION_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Task Condition Updated",
|
||||
value: "FLOW_OBJECT_TASK_CONDITION_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Task Created",
|
||||
value: "FLOW_OBJECT_TASK_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Task Deleted",
|
||||
value: "FLOW_OBJECT_TASK_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Task Updated",
|
||||
value: "FLOW_OBJECT_TASK_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Updated",
|
||||
value: "FLOW_OBJECT_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Objects Connection Created",
|
||||
value: "FLOW_OBJECTS_CONNECTION_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Objects Connection Deleted",
|
||||
value: "FLOW_OBJECTS_CONNECTION_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Flow Objects Connection Updated",
|
||||
value: "FLOW_OBJECTS_CONNECTION_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Objects External Connection Created",
|
||||
value: "FLOW_OBJECTS_EXTERNAL_CONNECTION_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Objects External Connection Deleted",
|
||||
value: "FLOW_OBJECTS_EXTERNAL_CONNECTION_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Flow Objects External Connection Updated",
|
||||
value: "FLOW_OBJECTS_EXTERNAL_CONNECTION_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Objects External Connections Group Created",
|
||||
value: "FLOW_OBJECTS_EXTERNAL_CONNECTIONS_GROUP_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Objects External Connections Group Deleted",
|
||||
value: "FLOW_OBJECTS_EXTERNAL_CONNECTIONS_GROUP_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Flow Objects External Connections Group Updated",
|
||||
value: "FLOW_OBJECTS_EXTERNAL_CONNECTIONS_GROUP_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Unarchived",
|
||||
value: "FLOW_UNARCHIVED"
|
||||
},
|
||||
{
|
||||
label: "Flow Updated",
|
||||
value: "FLOW_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Note Created",
|
||||
value: "NOTE_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Note Deleted",
|
||||
value: "NOTE_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Note Updated",
|
||||
value: "NOTE_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Team Created",
|
||||
value: "TEAM_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Team Deleted",
|
||||
value: "TEAM_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Team Updated",
|
||||
value: "TEAM_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "User Added To Team",
|
||||
value: "USER_ADDED_TO_TEAM"
|
||||
},
|
||||
{
|
||||
label: "User Added To Teamleads",
|
||||
value: "USER_ADDED_TO_TEAMLEADS"
|
||||
},
|
||||
{
|
||||
label: "User Archived",
|
||||
value: "USER_ARCHIVED"
|
||||
},
|
||||
{
|
||||
label: "User Changed Password",
|
||||
value: "USER_CHANGED_PASSWORD"
|
||||
},
|
||||
{
|
||||
label: "User Created",
|
||||
value: "USER_CREATED"
|
||||
},
|
||||
{
|
||||
label: "User Forgot Password",
|
||||
value: "USER_FORGOT_PASSWORD"
|
||||
},
|
||||
{
|
||||
label: "User Invited",
|
||||
value: "USER_INVITED"
|
||||
},
|
||||
{
|
||||
label: "User Logged In",
|
||||
value: "USER_LOGGED_IN"
|
||||
},
|
||||
{
|
||||
label: "User Notification Settings Changed",
|
||||
value: "USER_NOTIFICATION_SETTINGS_CHANGED"
|
||||
},
|
||||
{
|
||||
label: "User Profile Updated",
|
||||
value: "USER_PROFILE_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "User Removed From Team",
|
||||
value: "USER_REMOVED_FROM_TEAM"
|
||||
},
|
||||
{
|
||||
label: "User Removed From Teamleads",
|
||||
value: "USER_REMOVED_FROM_TEAMLEADS"
|
||||
},
|
||||
{
|
||||
label: "User Unarchived",
|
||||
value: "USER_UNARCHIVED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Archived",
|
||||
value: "WORKFLOW_ARCHIVED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Completed",
|
||||
value: "WORKFLOW_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Created",
|
||||
value: "WORKFLOW_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Creation Failed",
|
||||
value: "WORKFLOW_CREATION_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Api Get Completed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_API_GET_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Api Get Failed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_API_GET_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Api Post Completed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_API_POST_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Api Post Failed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_API_POST_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Datev Completed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_DATEV_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Datev Failed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_DATEV_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Email Completed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_EMAIL_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Email Failed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_EMAIL_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Lexoffice Completed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_LEXOFFICE_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Lexoffice Failed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_LEXOFFICE_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Rejected",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_REJECTED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Retried",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_RETRIED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Sevdesk Completed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_SEVDESK_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Sevdesk Failed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_SEVDESK_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Stamp Completed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_STAMP_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Stamp Failed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_STAMP_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Task Completed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_TASK_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Task Failed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_TASK_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Template Completed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_TEMPLATE_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Template Failed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_TEMPLATE_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Webdav Document Uploaded",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_WEBDAV_DOCUMENT_UPLOADED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Zapier Completed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_ZAPIER_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Zapier Failed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_ZAPIER_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Combination Task Group Created",
|
||||
value: "WORKFLOW_OBJECT_COMBINATION_TASK_GROUP_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Combination Task Group Deleted",
|
||||
value: "WORKFLOW_OBJECT_COMBINATION_TASK_GROUP_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Completed Automations Finished",
|
||||
value: "WORKFLOW_OBJECT_COMPLETED_AUTOMATIONS_FINISHED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Completed",
|
||||
value: "WORKFLOW_OBJECT_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Created",
|
||||
value: "WORKFLOW_OBJECT_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Document Added",
|
||||
value: "WORKFLOW_OBJECT_DOCUMENT_ADDED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Document Lock Added",
|
||||
value: "WORKFLOW_OBJECT_DOCUMENT_LOCK_ADDED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Document Lock Deleted",
|
||||
value: "WORKFLOW_OBJECT_DOCUMENT_LOCK_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Document Removed",
|
||||
value: "WORKFLOW_OBJECT_DOCUMENT_REMOVED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Email Added",
|
||||
value: "WORKFLOW_OBJECT_EMAIL_ADDED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Email Removed",
|
||||
value: "WORKFLOW_OBJECT_EMAIL_REMOVED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object External User Created",
|
||||
value: "WORKFLOW_OBJECT_EXTERNAL_USER_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object External User Deleted",
|
||||
value: "WORKFLOW_OBJECT_EXTERNAL_USER_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Note Added",
|
||||
value: "WORKFLOW_OBJECT_NOTE_ADDED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Note Removed",
|
||||
value: "WORKFLOW_OBJECT_NOTE_REMOVED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Resource Created",
|
||||
value: "WORKFLOW_OBJECT_RESOURCE_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Snapshot Created",
|
||||
value: "WORKFLOW_OBJECT_SNAPSHOT_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Task Condition Created",
|
||||
value: "WORKFLOW_OBJECT_TASK_CONDITION_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Task Created",
|
||||
value: "WORKFLOW_OBJECT_TASK_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Task Deleted",
|
||||
value: "WORKFLOW_OBJECT_TASK_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Task Snapshot Created",
|
||||
value: "WORKFLOW_OBJECT_TASK_SNAPSHOT_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Task Updated",
|
||||
value: "WORKFLOW_OBJECT_TASK_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Updated",
|
||||
value: "WORKFLOW_OBJECT_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Objects Connection Created",
|
||||
value: "WORKFLOW_OBJECTS_CONNECTION_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Objects External Connection Created",
|
||||
value: "WORKFLOW_OBJECTS_EXTERNAL_CONNECTION_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Objects External Connection Group Created",
|
||||
value: "WORKFLOW_OBJECTS_EXTERNAL_CONNECTION_GROUP_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Unarchived",
|
||||
value: "WORKFLOW_UNARCHIVED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Updated",
|
||||
value: "WORKFLOW_UPDATED"
|
||||
}
|
||||
];
|
||||
|
||||
export default webhookFilters;
|
||||
0
packages/backend/src/apps/flowers-software/index.d.ts
vendored
Normal file
18
packages/backend/src/apps/flowers-software/index.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import addAuthHeader from './common/add-auth-header';
|
||||
import auth from './auth';
|
||||
import triggers from './triggers';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Flowers Software',
|
||||
key: 'flowers-software',
|
||||
iconUrl: '{BASE_URL}/apps/flowers-software/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/flowers-software/connection',
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://flowers-software.com',
|
||||
apiBaseUrl: 'https://webapp.flowers-software.com/api',
|
||||
primaryColor: '02AFC7',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
});
|
||||
@ -0,0 +1,3 @@
|
||||
import newActivity from './new-activity';
|
||||
|
||||
export default [newActivity];
|
||||
@ -0,0 +1,54 @@
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
import webhookFilters from '../../common/webhook-filters';
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'New activity',
|
||||
key: 'newActivity',
|
||||
type: 'webhook',
|
||||
description: 'Triggers when a new activity occurs.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Activity type',
|
||||
key: 'filters',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
description: 'Pick an activity type to receive events for.',
|
||||
variables: false,
|
||||
options: webhookFilters,
|
||||
},
|
||||
],
|
||||
|
||||
async testRun($) {
|
||||
if (!isEmpty($.lastExecutionStep?.dataOut)) {
|
||||
$.pushTriggerItem({
|
||||
raw: $.lastExecutionStep.dataOut,
|
||||
meta: {
|
||||
internalId: '',
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async registerHook($) {
|
||||
const payload = {
|
||||
name: $.flow.id,
|
||||
type: 'POST',
|
||||
url: $.webhookUrl,
|
||||
filters: [$.step.parameters.filters]
|
||||
};
|
||||
|
||||
const { data } = await $.http.post(
|
||||
`/v2/public/api/webhooks`,
|
||||
payload
|
||||
);
|
||||
|
||||
await $.flow.setRemoteWebhookId(data.id);
|
||||
},
|
||||
|
||||
async unregisterHook($) {
|
||||
await $.http.delete(
|
||||
`/v2/public/api/webhooks/${$.flow.remoteWebhookId}`
|
||||
);
|
||||
},
|
||||
});
|
||||
3
packages/backend/src/apps/ntfy/actions/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import sendMessage from './send-message';
|
||||
|
||||
export default [sendMessage];
|
||||
103
packages/backend/src/apps/ntfy/actions/send-message/index.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import qs from 'qs';
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Send message',
|
||||
key: 'sendMessage',
|
||||
description: 'Sends a message to a topic you specify.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Topic',
|
||||
key: 'topic',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Target topic name.',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Message body',
|
||||
key: 'message',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Message body to be sent, set to triggered if empty or not passed.',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Title',
|
||||
key: 'title',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
description: 'Message title.',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Email',
|
||||
key: 'email',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
description: 'E-mail address for e-mail notifications.',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Click URL',
|
||||
key: 'click',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
description: 'Website opened when notification is clicked.',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Attach file by URL',
|
||||
key: 'attach',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
description: 'URL of an attachment.',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Filename',
|
||||
key: 'filename',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
description: 'File name of the attachment.',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Delay',
|
||||
key: 'delay',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
description: 'Timestamp or duration for delayed delivery. For example, 30min or 9am.',
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const {
|
||||
topic,
|
||||
message,
|
||||
title,
|
||||
email,
|
||||
click,
|
||||
attach,
|
||||
filename,
|
||||
delay
|
||||
} = $.step.parameters;
|
||||
const payload = {
|
||||
topic,
|
||||
message,
|
||||
title,
|
||||
email,
|
||||
click,
|
||||
attach,
|
||||
filename,
|
||||
delay
|
||||
};
|
||||
|
||||
const response = await $.http.post('/', payload);
|
||||
|
||||
$.setActionItem({
|
||||
raw: response.data,
|
||||
});
|
||||
},
|
||||
});
|
||||
1
packages/backend/src/apps/ntfy/assets/favicon.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="50mm" height="50mm" viewBox="0 0 50 50" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="a"><stop style="stop-color:#348878;stop-opacity:1" offset="0"/><stop style="stop-color:#56bda8;stop-opacity:1" offset="1"/></linearGradient><linearGradient xlink:href="#a" id="b" x1="160.722" y1="128.533" x2="168.412" y2="134.326" gradientUnits="userSpaceOnUse" gradientTransform="translate(-845.726 -630.598) scale(5.59448)"/></defs><g style="display:inline"><path style="color:#000;fill:url(#b);stroke:none;stroke-width:3.72347;-inkscape-stroke:none" d="M94.237 89.912H59.499c-2.388 0-4.342 1.844-4.342 4.098l.033 27.754-.648 3.738 9.297-2.806h30.396c2.388 0 4.342-1.845 4.342-4.099V94.01c0-2.254-1.954-4.098-4.342-4.098z" transform="translate(-51.147 -81.516)"/><path style="color:#000;fill:#fff;stroke:none;stroke-width:.762343;-inkscape-stroke:none" d="M58.849 86.79c-3.62 0-6.72 2.848-6.72 6.47v.002l.035 30.273-.91 6.708 12.362-3.284h30.729c3.62 0 6.72-2.852 6.72-6.473V93.26c0-3.62-3.099-6.469-6.717-6.469h-.003zm0 4.566h35.499c1.272 0 2.151.927 2.151 1.903v27.227c0 .977-.88 1.924-2.154 1.903h-31.4l-6.28 1.898.065-.37-.035-30.658c0-.977.88-1.903 2.154-1.903z" transform="translate(-51.147 -81.516)"/><g style="font-size:8.48274px;font-family:sans-serif;letter-spacing:0;word-spacing:0;fill:#000;stroke:none;stroke-width:.525121"><path style="color:#000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#fff;stroke:none;-inkscape-stroke:none" d="M62.57 116.77v-1.312l3.28-1.459q.159-.068.306-.102.158-.045.283-.068l.271-.022v-.09q-.136-.012-.271-.046-.125-.023-.283-.057-.147-.045-.306-.113l-3.28-1.459v-1.323l5.068 2.319v1.413z" transform="matrix(2.1689 0 0 2.57844 -124.28 -268.742)"/><path style="color:#000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#fff;stroke:none;-inkscape-stroke:none" d="M62.309 110.31v1.903l3.437 1.53.022.007-.022.008-3.437 1.53v1.892l.37-.17 5.221-2.39v-1.75zm.525.817 4.541 2.08v1.076l-4.541 2.078v-.732l3.12-1.389.003-.002a1.56 1.56 0 0 1 .258-.086h.006l.008-.002c.094-.027.176-.047.246-.06l.498-.041v-.574l-.24-.02a1.411 1.411 0 0 1-.231-.04l-.008-.001-.008-.002a9.077 9.077 0 0 1-.263-.053 2.781 2.781 0 0 1-.266-.097l-.004-.002-3.119-1.39z" transform="matrix(2.1689 0 0 2.57844 -124.28 -268.742)"/></g><g style="font-size:8.48274px;font-family:sans-serif;letter-spacing:0;word-spacing:0;fill:#000;stroke:none;stroke-width:.525121"><path style="color:#000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#fff;stroke:none;-inkscape-stroke:none" d="M69.171 117.754h5.43v1.278h-5.43Z" transform="matrix(2.16247 0 0 2.48294 -122.76 -261.211)"/><path style="color:#000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#fff;stroke:none;-inkscape-stroke:none" d="M68.908 117.492v1.802h5.955v-1.802zm.526.524h4.904v.754h-4.904z" transform="matrix(2.16247 0 0 2.48294 -122.76 -261.211)"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
41
packages/backend/src/apps/ntfy/auth/index.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import verifyCredentials from './verify-credentials';
|
||||
import isStillVerified from './is-still-verified';
|
||||
|
||||
export default {
|
||||
fields: [
|
||||
{
|
||||
key: 'serverUrl',
|
||||
label: 'Server URL',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: 'https://ntfy.sh',
|
||||
placeholder: null,
|
||||
description: 'ntfy server to use.',
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'username',
|
||||
label: 'Username',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
readOnly: false,
|
||||
placeholder: null,
|
||||
clickToCopy: false,
|
||||
description: 'You may need to provide your username if your installation requires authentication.',
|
||||
},
|
||||
{
|
||||
key: 'password',
|
||||
label: 'Password',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
readOnly: false,
|
||||
placeholder: null,
|
||||
clickToCopy: false,
|
||||
description: 'You may need to provide your password if your installation requires authentication.',
|
||||
},
|
||||
],
|
||||
|
||||
verifyCredentials,
|
||||
isStillVerified,
|
||||
};
|
||||
9
packages/backend/src/apps/ntfy/auth/is-still-verified.ts
Normal 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;
|
||||
16
packages/backend/src/apps/ntfy/auth/verify-credentials.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
await $.http.post('/', { topic: 'automatisch' });
|
||||
let screenName = $.auth.data.serverUrl;
|
||||
|
||||
if ($.auth.data.username) {
|
||||
screenName = `${$.auth.data.username} @ ${screenName}`
|
||||
}
|
||||
|
||||
await $.auth.set({
|
||||
screenName,
|
||||
});
|
||||
};
|
||||
|
||||
export default verifyCredentials;
|
||||
18
packages/backend/src/apps/ntfy/common/add-auth-header.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { TBeforeRequest } from '@automatisch/types';
|
||||
|
||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||
if ($.auth.data.apiBaseUrl) {
|
||||
requestConfig.baseURL = $.auth.data.apiBaseUrl as string;
|
||||
}
|
||||
|
||||
if ($.auth.data?.username && $.auth.data?.password) {
|
||||
requestConfig.auth = {
|
||||
username: $.auth.data.username as string,
|
||||
password: $.auth.data.password as string,
|
||||
}
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default addAuthHeader;
|
||||
0
packages/backend/src/apps/ntfy/index.d.ts
vendored
Normal file
18
packages/backend/src/apps/ntfy/index.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import addAuthHeader from './common/add-auth-header';
|
||||
import auth from './auth';
|
||||
import actions from './actions';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Ntfy',
|
||||
key: 'ntfy',
|
||||
iconUrl: '{BASE_URL}/apps/ntfy/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/ntfy/connection',
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://ntfy.sh',
|
||||
apiBaseUrl: 'https://ntfy.sh',
|
||||
primaryColor: '56bda8',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
actions,
|
||||
});
|
||||
3
packages/backend/src/apps/telegram-bot/actions/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import sendMessage from './send-message';
|
||||
|
||||
export default [sendMessage];
|
||||
@ -0,0 +1,59 @@
|
||||
import qs from 'qs';
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Send message',
|
||||
key: 'sendMessage',
|
||||
description: 'Sends a message to a chat you specify.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Chat ID',
|
||||
key: 'chatId',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Unique identifier for the target chat or username of the target channel (in the format @channelusername).',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Message text',
|
||||
key: 'text',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Text of the message to be sent, 1-4096 characters.',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Disable notification?',
|
||||
key: 'disableNotification',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
value: false,
|
||||
description: 'Sends the message silently. Users will receive a notification with no sound.',
|
||||
variables: false,
|
||||
options: [
|
||||
{
|
||||
label: 'Yes',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: 'No',
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const payload = {
|
||||
chat_id: $.step.parameters.chatId,
|
||||
text: $.step.parameters.text,
|
||||
disable_notification: $.step.parameters.disableNotification,
|
||||
};
|
||||
|
||||
const response = await $.http.post('/sendMessage', payload);
|
||||
|
||||
$.setActionItem({
|
||||
raw: response.data,
|
||||
});
|
||||
},
|
||||
});
|
||||
14
packages/backend/src/apps/telegram-bot/assets/favicon.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="256px" height="256px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
|
||||
<title>Telegram</title>
|
||||
<defs>
|
||||
<linearGradient x1="50%" y1="2.77555756e-15%" x2="50%" y2="100%" id="telegramLinearGradient-1">
|
||||
<stop stop-color="#2AABEE" offset="0%"></stop>
|
||||
<stop stop-color="#229ED9" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g>
|
||||
<path d="M128,0 C94.06,0 61.48,13.494 37.5,37.49 C13.5,61.486 0,94.066 0,128 C0,161.934 13.5,194.514 37.5,218.51 C61.48,242.506 94.06,256 128,256 C161.94,256 194.52,242.506 218.5,218.51 C242.5,194.514 256,161.934 256,128 C256,94.066 242.5,61.486 218.5,37.49 C194.52,13.494 161.94,0 128,0 Z" fill="url(#telegramLinearGradient-1)"></path>
|
||||
<path d="M57.94,126.6476 C95.26,110.3916 120.14,99.6736 132.58,94.4956 C168.14,79.7096 175.52,77.1416 180.34,77.0547542 C181.4,77.0376 183.76,77.2996 185.3,78.5456 C186.58,79.5956 186.94,81.0156 187.12,82.0116 C187.28,83.0076 187.5,85.2776 187.32,87.0496 C185.4,107.2896 177.06,156.4056 172.82,179.0756 C171.04,188.668 167.5,191.884 164.08,192.198 C156.64,192.882 151,187.286 143.8,182.5676 C132.54,175.1816 126.18,170.5856 115.24,163.3796 C102.6,155.0516 110.8,150.4736 118,142.9936 C119.88,141.0356 152.64,111.2456 153.26,108.5436 C153.34,108.2056 153.42,106.9456 152.66,106.2816 C151.92,105.6156 150.82,105.8436 150.02,106.0236 C148.88,106.2796 130.9,118.1756 96.02,141.7096 C90.92,145.2176 86.3,146.9276 82.14,146.8376 C77.58,146.7396 68.78,144.2536 62.24,142.1296 C54.24,139.5236 47.86,138.1456 48.42,133.7196 C48.7,131.4156 51.88,129.0576 57.94,126.6476 L57.94,126.6476 Z" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
21
packages/backend/src/apps/telegram-bot/auth/index.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import verifyCredentials from './verify-credentials';
|
||||
import isStillVerified from './is-still-verified';
|
||||
|
||||
export default {
|
||||
fields: [
|
||||
{
|
||||
key: 'token',
|
||||
label: 'Bot token',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: 'Bot token which should be retrieved from @botfather.',
|
||||
clickToCopy: false,
|
||||
},
|
||||
],
|
||||
|
||||
verifyCredentials,
|
||||
isStillVerified,
|
||||
};
|
||||
@ -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;
|
||||
@ -0,0 +1,12 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
const { data } = await $.http.get('/getMe');
|
||||
const { result: me } = data;
|
||||
|
||||
await $.auth.set({
|
||||
screenName: me.first_name,
|
||||
});
|
||||
};
|
||||
|
||||
export default verifyCredentials;
|
||||
@ -0,0 +1,13 @@
|
||||
import { TBeforeRequest } from '@automatisch/types';
|
||||
import { URL } from 'node:url';
|
||||
|
||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||
if ($.auth.data?.token) {
|
||||
const token = $.auth.data.token as string;
|
||||
requestConfig.baseURL = (new URL(`/bot${token}`, requestConfig.baseURL)).toString();
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default addAuthHeader;
|
||||
0
packages/backend/src/apps/telegram-bot/index.d.ts
vendored
Normal file
18
packages/backend/src/apps/telegram-bot/index.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import addAuthHeader from './common/add-auth-header';
|
||||
import auth from './auth';
|
||||
import actions from './actions';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Telegram',
|
||||
key: 'telegram-bot',
|
||||
iconUrl: '{BASE_URL}/apps/telegram-bot/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/telegram-bot/connection',
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://telegram.org',
|
||||
apiBaseUrl: 'https://api.telegram.org',
|
||||
primaryColor: '2AABEE',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
actions,
|
||||
});
|
||||
@ -11,7 +11,7 @@ const verifyWebhook = async ($: IGlobalVariable) => {
|
||||
|
||||
const verifySignature = function (receivedSignature: string, payload: string) {
|
||||
const hash = crypto
|
||||
.createHmac('sha256', appConfig.appSecretKey)
|
||||
.createHmac('sha256', appConfig.webhookSecretKey)
|
||||
.update(payload)
|
||||
.digest('base64');
|
||||
return receivedSignature === `sha256=${hash}`;
|
||||
|
||||
@ -72,7 +72,7 @@ export default defineTrigger({
|
||||
const subscriptionPayload = {
|
||||
enabled: true,
|
||||
url: $.webhookUrl,
|
||||
secret: appConfig.appSecretKey,
|
||||
secret: appConfig.webhookSecretKey,
|
||||
};
|
||||
|
||||
await $.http.put(
|
||||
|
||||
8
packages/backend/src/apps/webhook/assets/favicon.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48" width="48px" height="48px"><g id="surface56721297">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,12.156863%,32.156864%);fill-opacity:1;" d="M 35 37 C 32.800781 37 31 35.199219 31 33 C 31 30.800781 32.800781 29 35 29 C 37.199219 29 39 30.800781 39 33 C 39 35.199219 37.199219 37 35 37 Z M 35 37 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,12.156863%,32.156864%);fill-opacity:1;" d="M 35 43 C 32 43 29.101562 41.601562 27.199219 39.300781 L 30.300781 36.800781 C 31.398438 38.199219 33.199219 39.101562 35 39.101562 C 38.300781 39.101562 41 36.398438 41 33.101562 C 41 29.800781 38.300781 27.101562 35 27.101562 C 34 27.101562 33 27.398438 32.101562 27.800781 L 30.398438 28.800781 L 23.300781 16 L 26.800781 14.101562 L 32.101562 23.5 C 33.101562 23.199219 34.101562 23 35.101562 23 C 40.601562 23 45.101562 27.5 45.101562 33 C 45.101562 38.5 40.5 43 35 43 Z M 35 43 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,12.156863%,32.156864%);fill-opacity:1;" d="M 14 43 C 8.5 43 4 38.5 4 33 C 4 28.398438 7.101562 24.5 11.5 23.300781 L 12.5 27.199219 C 9.898438 27.898438 8 30.300781 8 33 C 8 36.300781 10.699219 39 14 39 C 17.300781 39 20 36.300781 20 33 L 20 31 L 35 31 L 35 35 L 23.800781 35 C 22.898438 39.601562 18.800781 43 14 43 Z M 14 43 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,34.901962%,96.862745%);fill-opacity:1;" d="M 14 37 C 11.800781 37 10 35.199219 10 33 C 10 30.800781 11.800781 29 14 29 C 16.199219 29 18 30.800781 18 33 C 18 35.199219 16.199219 37 14 37 Z M 14 37 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,12.156863%,32.156864%);fill-opacity:1;" d="M 25 19 C 22.800781 19 21 17.199219 21 15 C 21 12.800781 22.800781 11 25 11 C 27.199219 11 29 12.800781 29 15 C 29 17.199219 27.199219 19 25 19 Z M 25 19 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,34.901962%,96.862745%);fill-opacity:1;" d="M 15.699219 34 L 12.300781 32 L 18.199219 22.300781 C 16.199219 20.398438 15 17.800781 15 15 C 15 9.5 19.5 5 25 5 C 30.5 5 35 9.5 35 15 C 35 15.898438 34.898438 16.699219 34.699219 17.5 L 30.800781 16.5 C 30.898438 16 31 15.5 31 15 C 31 11.699219 28.300781 9 25 9 C 21.699219 9 19 11.699219 19 15 C 19 17.101562 20.101562 19 21.898438 20.101562 L 23.601562 21.101562 Z M 15.699219 34 "/>
|
||||
</g></svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
0
packages/backend/src/apps/webhook/index.d.ts
vendored
Normal file
14
packages/backend/src/apps/webhook/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import triggers from './triggers';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Webhook',
|
||||
key: 'webhook',
|
||||
iconUrl: '{BASE_URL}/apps/webhook/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/webhook/connection',
|
||||
supportsConnections: false,
|
||||
baseUrl: '',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '0059F7',
|
||||
triggers,
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'Catch raw webhook',
|
||||
key: 'catchRawWebhook',
|
||||
type: 'webhook',
|
||||
description: 'Triggers when the webhook receives a request.',
|
||||
|
||||
async testRun($) {
|
||||
if (!isEmpty($.lastExecutionStep?.dataOut)) {
|
||||
$.pushTriggerItem({
|
||||
raw: $.lastExecutionStep.dataOut,
|
||||
meta: {
|
||||
internalId: '',
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
3
packages/backend/src/apps/webhook/triggers/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import catchRawWebhook from './catch-raw-webhook';
|
||||
|
||||
export default [catchRawWebhook];
|
||||
@ -1,3 +1,4 @@
|
||||
import { URL } from 'node:url';
|
||||
import * as dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
@ -18,6 +19,7 @@ type AppConfig = {
|
||||
postgresEnableSsl: boolean;
|
||||
baseUrl: string;
|
||||
encryptionKey: string;
|
||||
webhookSecretKey: string;
|
||||
appSecretKey: string;
|
||||
serveWebAppSeparately: boolean;
|
||||
redisHost: string;
|
||||
@ -37,14 +39,23 @@ const port = process.env.PORT || '3000';
|
||||
const serveWebAppSeparately =
|
||||
process.env.SERVE_WEB_APP_SEPARATELY === 'true' ? true : false;
|
||||
|
||||
let webAppUrl = `${protocol}://${host}:${port}`;
|
||||
const webhookUrl = process.env.WEBHOOK_URL || webAppUrl;
|
||||
let apiUrl = (new URL(`${protocol}://${host}:${port}`)).toString();
|
||||
apiUrl = apiUrl.substring(0, apiUrl.length - 1);
|
||||
|
||||
if (serveWebAppSeparately) {
|
||||
webAppUrl = process.env.WEB_APP_URL || 'http://localhost:3001';
|
||||
// use apiUrl by default, which has less priority over the following cases
|
||||
let webAppUrl = apiUrl;
|
||||
|
||||
if (process.env.WEB_APP_URL) {
|
||||
// use env. var. if provided
|
||||
webAppUrl = (new URL(process.env.WEB_APP_URL)).toString();
|
||||
webAppUrl = webAppUrl.substring(0, webAppUrl.length - 1);
|
||||
} else if (serveWebAppSeparately) {
|
||||
// no env. var. and serving separately, sign of development
|
||||
webAppUrl = 'http://localhost:3001'
|
||||
}
|
||||
|
||||
const baseUrl = `${protocol}://${host}:${port}`;
|
||||
let webhookUrl = (new URL(process.env.WEBHOOK_URL || apiUrl)).toString();
|
||||
webhookUrl = webhookUrl.substring(0, webhookUrl.length - 1);
|
||||
|
||||
const appEnv = process.env.APP_ENV || 'development';
|
||||
|
||||
@ -63,6 +74,7 @@ const appConfig: AppConfig = {
|
||||
postgresPassword: process.env.POSTGRES_PASSWORD,
|
||||
postgresEnableSsl: process.env.POSTGRES_ENABLE_SSL === 'true',
|
||||
encryptionKey: process.env.ENCRYPTION_KEY || '',
|
||||
webhookSecretKey: process.env.WEBHOOK_SECRET_KEY || '',
|
||||
appSecretKey: process.env.APP_SECRET_KEY || '',
|
||||
serveWebAppSeparately,
|
||||
redisHost: process.env.REDIS_HOST || '127.0.0.1',
|
||||
@ -70,11 +82,10 @@ const appConfig: AppConfig = {
|
||||
redisUsername: process.env.REDIS_USERNAME,
|
||||
redisPassword: process.env.REDIS_PASSWORD,
|
||||
redisTls: process.env.REDIS_TLS === 'true',
|
||||
enableBullMQDashboard:
|
||||
process.env.ENABLE_BULLMQ_DASHBOARD === 'true',
|
||||
enableBullMQDashboard: process.env.ENABLE_BULLMQ_DASHBOARD === 'true',
|
||||
bullMQDashboardUsername: process.env.BULLMQ_DASHBOARD_USERNAME,
|
||||
bullMQDashboardPassword: process.env.BULLMQ_DASHBOARD_PASSWORD,
|
||||
baseUrl,
|
||||
baseUrl: apiUrl,
|
||||
webAppUrl,
|
||||
webhookUrl,
|
||||
telemetryEnabled: process.env.TELEMETRY_ENABLED === 'false' ? false : true,
|
||||
@ -84,4 +95,8 @@ if (!appConfig.encryptionKey) {
|
||||
throw new Error('ENCRYPTION_KEY environment variable needs to be set!');
|
||||
}
|
||||
|
||||
if (!appConfig.webhookSecretKey) {
|
||||
throw new Error('WEBHOOK_SECRET_KEY environment variable needs to be set!');
|
||||
}
|
||||
|
||||
export default appConfig;
|
||||
|
||||
@ -6,6 +6,7 @@ type TRedisConfig = {
|
||||
username?: string,
|
||||
password?: string,
|
||||
tls?: Record<string, unknown>,
|
||||
enableReadyCheck?: boolean,
|
||||
enableOfflineQueue: boolean,
|
||||
}
|
||||
|
||||
@ -15,6 +16,7 @@ const redisConfig: TRedisConfig = {
|
||||
username: appConfig.redisUsername,
|
||||
password: appConfig.redisPassword,
|
||||
enableOfflineQueue: false,
|
||||
enableReadyCheck: true,
|
||||
};
|
||||
|
||||
if (appConfig.redisTls) {
|
||||
|
||||
@ -13,20 +13,21 @@ export default async (request: IRequest, response: Response) => {
|
||||
.findById(request.params.flowId)
|
||||
.throwIfNotFound();
|
||||
|
||||
if (!flow.active) {
|
||||
return response.send(404);
|
||||
}
|
||||
|
||||
const testRun = !flow.active;
|
||||
const triggerStep = await flow.getTriggerStep();
|
||||
const triggerCommand = await triggerStep.getTriggerCommand();
|
||||
const app = await triggerStep.getApp();
|
||||
const isWebhookApp = app.key === 'webhook';
|
||||
|
||||
if (triggerCommand.type !== 'webhook') {
|
||||
return response.send(404);
|
||||
if (testRun && !isWebhookApp) {
|
||||
return response.sendStatus(404);
|
||||
}
|
||||
|
||||
const app = await triggerStep.getApp();
|
||||
if (triggerCommand.type !== 'webhook') {
|
||||
return response.sendStatus(404);
|
||||
}
|
||||
|
||||
if (app.auth.verifyWebhook) {
|
||||
if (app.auth?.verifyWebhook) {
|
||||
const $ = await globalVariable({
|
||||
flow,
|
||||
connection: await triggerStep.$relatedQuery('connection'),
|
||||
@ -42,10 +43,25 @@ export default async (request: IRequest, response: Response) => {
|
||||
}
|
||||
}
|
||||
|
||||
// in case trigger type is 'webhook'
|
||||
let payload = request.body;
|
||||
let rawInternalId: string | Buffer = request.rawBody;
|
||||
|
||||
// in case it's our built-in generic webhook trigger
|
||||
if (isWebhookApp) {
|
||||
payload = {
|
||||
headers: request.headers,
|
||||
body: request.body,
|
||||
query: request.query,
|
||||
}
|
||||
|
||||
rawInternalId = JSON.stringify(payload);
|
||||
}
|
||||
|
||||
const triggerItem: ITriggerItem = {
|
||||
raw: request.body,
|
||||
raw: payload,
|
||||
meta: {
|
||||
internalId: await bcrypt.hash(request.rawBody, 1),
|
||||
internalId: await bcrypt.hash(rawInternalId, 1),
|
||||
},
|
||||
};
|
||||
|
||||
@ -53,8 +69,13 @@ export default async (request: IRequest, response: Response) => {
|
||||
flowId: flow.id,
|
||||
stepId: triggerStep.id,
|
||||
triggerItem,
|
||||
testRun
|
||||
});
|
||||
|
||||
if (testRun) {
|
||||
return response.sendStatus(200);
|
||||
}
|
||||
|
||||
const nextStep = await triggerStep.getNextStep();
|
||||
const jobName = `${executionId}-${nextStep.id}`;
|
||||
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
return knex.schema.table('flows', (table) => {
|
||||
table.string('remote_webhook_id');
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
return knex.schema.table('flows', (table) => {
|
||||
table.dropColumn('remote_webhook_id');
|
||||
});
|
||||
}
|
||||
@ -49,9 +49,9 @@ const updateFlowStatus = async (
|
||||
testRun: false,
|
||||
});
|
||||
|
||||
if (flow.active) {
|
||||
if (flow.active && trigger.registerHook) {
|
||||
await trigger.registerHook($);
|
||||
} else {
|
||||
} else if (!flow.active && trigger.unregisterHook) {
|
||||
await trigger.unregisterHook($);
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -342,6 +342,7 @@ type Step {
|
||||
key: String
|
||||
appKey: String
|
||||
iconUrl: String
|
||||
webhookUrl: String
|
||||
type: StepEnumType
|
||||
parameters: JSONObject
|
||||
connection: Connection
|
||||
@ -380,6 +381,7 @@ type Trigger {
|
||||
key: String
|
||||
description: String
|
||||
pollInterval: Int
|
||||
type: String
|
||||
substeps: [TriggerSubstep]
|
||||
}
|
||||
|
||||
|
||||
11
packages/backend/src/helpers/check-worker-readiness.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import Redis from 'ioredis';
|
||||
import logger from './logger';
|
||||
import redisConfig from '../config/redis';
|
||||
|
||||
const redisClient = new Redis(redisConfig);
|
||||
|
||||
redisClient.on('ready', () => {
|
||||
logger.info(`Workers are ready!`);
|
||||
|
||||
redisClient.disconnect();
|
||||
});
|
||||
25
packages/backend/src/helpers/delay-as-milliseconds.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import Step from '../models/step';
|
||||
import delayForAsMilliseconds, {
|
||||
TDelayForUnit,
|
||||
} from './delay-for-as-milliseconds';
|
||||
import delayUntilAsMilliseconds from './delay-until-as-milliseconds';
|
||||
|
||||
const delayAsMilliseconds = (step: Step) => {
|
||||
let delayDuration = 0;
|
||||
|
||||
if (step.key === 'delayFor') {
|
||||
const { delayForUnit, delayForValue } = step.parameters;
|
||||
|
||||
delayDuration = delayForAsMilliseconds(
|
||||
delayForUnit as TDelayForUnit,
|
||||
Number(delayForValue)
|
||||
);
|
||||
} else if (step.key === 'delayUntil') {
|
||||
const { delayUntil } = step.parameters;
|
||||
delayDuration = delayUntilAsMilliseconds(delayUntil as string);
|
||||
}
|
||||
|
||||
return delayDuration;
|
||||
};
|
||||
|
||||
export default delayAsMilliseconds;
|
||||
21
packages/backend/src/helpers/delay-for-as-milliseconds.ts
Normal file
@ -0,0 +1,21 @@
|
||||
export type TDelayForUnit = 'minutes' | 'hours' | 'days' | 'weeks';
|
||||
|
||||
const delayAsMilliseconds = (
|
||||
delayForUnit: TDelayForUnit,
|
||||
delayForValue: number
|
||||
) => {
|
||||
switch (delayForUnit) {
|
||||
case 'minutes':
|
||||
return delayForValue * 60 * 1000;
|
||||
case 'hours':
|
||||
return delayForValue * 60 * 60 * 1000;
|
||||
case 'days':
|
||||
return delayForValue * 24 * 60 * 60 * 1000;
|
||||
case 'weeks':
|
||||
return delayForValue * 7 * 24 * 60 * 60 * 1000;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
export default delayAsMilliseconds;
|
||||
@ -0,0 +1,8 @@
|
||||
const delayUntilAsMilliseconds = (delayUntil: string) => {
|
||||
const delayUntilDate = new Date(delayUntil);
|
||||
const now = new Date();
|
||||
|
||||
return delayUntilDate.getTime() - now.getTime();
|
||||
};
|
||||
|
||||
export default delayUntilAsMilliseconds;
|
||||
@ -37,6 +37,7 @@ const globalVariable = async (
|
||||
testRun = false,
|
||||
} = options;
|
||||
|
||||
const isTrigger = step?.isTrigger;
|
||||
const lastInternalId = testRun ? undefined : await flow?.lastInternalId();
|
||||
const nextStep = await step?.getNextStep();
|
||||
|
||||
@ -77,6 +78,7 @@ const globalVariable = async (
|
||||
id: execution?.id,
|
||||
testRun,
|
||||
},
|
||||
lastExecutionStep: (await step?.getLastExecutionStep())?.toJSON(),
|
||||
triggerOutput: {
|
||||
data: [],
|
||||
},
|
||||
@ -122,6 +124,18 @@ const globalVariable = async (
|
||||
$.webhookUrl = webhookUrl;
|
||||
}
|
||||
|
||||
if (isTrigger && (await step.getTriggerCommand()).type === 'webhook') {
|
||||
$.flow.setRemoteWebhookId = async (remoteWebhookId) => {
|
||||
await flow.$query().patchAndFetch({
|
||||
remoteWebhookId,
|
||||
});
|
||||
|
||||
$.flow.remoteWebhookId = remoteWebhookId;
|
||||
};
|
||||
|
||||
$.flow.remoteWebhookId = flow.remoteWebhookId;
|
||||
}
|
||||
|
||||
const lastInternalIds =
|
||||
testRun || (flow && step.isAction) ? [] : await flow?.lastInternalIds(2000);
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ class Flow extends Base {
|
||||
active: boolean;
|
||||
steps: Step[];
|
||||
published_at: string;
|
||||
remoteWebhookId: string;
|
||||
executions?: Execution[];
|
||||
|
||||
static tableName = 'flows';
|
||||
@ -25,6 +26,7 @@ class Flow extends Base {
|
||||
id: { type: 'string', format: 'uuid' },
|
||||
name: { type: 'string', minLength: 1 },
|
||||
userId: { type: 'string', format: 'uuid' },
|
||||
remoteWebhookId: { type: 'string' },
|
||||
active: { type: 'boolean' },
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { URL } from 'node:url';
|
||||
import { QueryContext, ModelOptions } from 'objection';
|
||||
import type { IJSONObject, IStep } from '@automatisch/types';
|
||||
import Base from './base';
|
||||
import App from './app';
|
||||
import Flow from './flow';
|
||||
import Connection from './connection';
|
||||
import ExecutionStep from './execution-step';
|
||||
import type { IJSONObject, IStep } from '@automatisch/types';
|
||||
import Telemetry from '../helpers/telemetry';
|
||||
import appConfig from '../config/app';
|
||||
|
||||
@ -46,7 +47,7 @@ class Step extends Base {
|
||||
};
|
||||
|
||||
static get virtualAttributes() {
|
||||
return ['iconUrl'];
|
||||
return ['iconUrl', 'webhookUrl'];
|
||||
}
|
||||
|
||||
static relationMappings = () => ({
|
||||
@ -82,6 +83,13 @@ class Step extends Base {
|
||||
return `${appConfig.baseUrl}/apps/${this.appKey}/assets/favicon.svg`;
|
||||
}
|
||||
|
||||
get webhookUrl() {
|
||||
if (this.appKey !== 'webhook') return null;
|
||||
|
||||
const url = new URL(`/webhooks/${this.flowId}`, appConfig.webhookUrl);
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
async $afterInsert(queryContext: QueryContext) {
|
||||
await super.$afterInsert(queryContext);
|
||||
Telemetry.stepCreated(this);
|
||||
@ -106,6 +114,14 @@ class Step extends Base {
|
||||
return await App.findOneByKey(this.appKey);
|
||||
}
|
||||
|
||||
async getLastExecutionStep() {
|
||||
const lastExecutionStep = await this.$relatedQuery('executionSteps')
|
||||
.orderBy('created_at', 'desc')
|
||||
.first();
|
||||
|
||||
return lastExecutionStep;
|
||||
}
|
||||
|
||||
async getNextStep() {
|
||||
const flow = await this.$relatedQuery('flow');
|
||||
|
||||
|
||||
@ -3,6 +3,9 @@ import webhookHandler from '../controllers/webhooks/handler';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get('/:flowId', webhookHandler);
|
||||
router.put('/:flowId', webhookHandler);
|
||||
router.patch('/:flowId', webhookHandler);
|
||||
router.post('/:flowId', webhookHandler);
|
||||
|
||||
export default router;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import './config/orm';
|
||||
import './helpers/check-worker-readiness';
|
||||
import './workers/flow';
|
||||
import './workers/trigger';
|
||||
import './workers/action';
|
||||
|
||||
@ -4,7 +4,11 @@ import logger from '../helpers/logger';
|
||||
import Step from '../models/step';
|
||||
import actionQueue from '../queues/action';
|
||||
import { processAction } from '../services/action';
|
||||
import { REMOVE_AFTER_30_DAYS_OR_150_JOBS, REMOVE_AFTER_7_DAYS_OR_50_JOBS } from '../helpers/remove-job-configuration';
|
||||
import {
|
||||
REMOVE_AFTER_30_DAYS_OR_150_JOBS,
|
||||
REMOVE_AFTER_7_DAYS_OR_50_JOBS,
|
||||
} from '../helpers/remove-job-configuration';
|
||||
import delayAsMilliseconds from '../helpers/delay-as-milliseconds';
|
||||
|
||||
type JobData = {
|
||||
flowId: string;
|
||||
@ -12,6 +16,8 @@ type JobData = {
|
||||
stepId: string;
|
||||
};
|
||||
|
||||
const DEFAULT_DELAY_DURATION = 0;
|
||||
|
||||
export const worker = new Worker(
|
||||
'action',
|
||||
async (job) => {
|
||||
@ -35,6 +41,11 @@ export const worker = new Worker(
|
||||
const jobOptions = {
|
||||
removeOnComplete: REMOVE_AFTER_7_DAYS_OR_50_JOBS,
|
||||
removeOnFail: REMOVE_AFTER_30_DAYS_OR_150_JOBS,
|
||||
delay: DEFAULT_DELAY_DURATION,
|
||||
};
|
||||
|
||||
if (step.appKey === 'delay') {
|
||||
jobOptions.delay = delayAsMilliseconds(step);
|
||||
}
|
||||
|
||||
await actionQueue.add(jobName, jobPayload, jobOptions);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@automatisch/cli",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||
"contributors": [
|
||||
@ -33,7 +33,7 @@
|
||||
"version": "oclif readme && git add README.md"
|
||||
},
|
||||
"dependencies": {
|
||||
"@automatisch/backend": "^0.2.0",
|
||||
"@automatisch/backend": "^0.3.0",
|
||||
"@oclif/core": "^1",
|
||||
"@oclif/plugin-help": "^5",
|
||||
"@oclif/plugin-plugins": "^2.0.1",
|
||||
|
||||
@ -6463,10 +6463,10 @@ decimal.js@^10.2.1:
|
||||
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.1.tgz#be75eeac4a2281aace80c1a8753587c27ef053e7"
|
||||
integrity sha512-F29o+vci4DodHYT9UrR5IEbfBw9pE5eSapIJdTqXK5+6hq+t8VRxwQyKlW2i+KDKFkkJQRvFyI/QXD83h8LyQw==
|
||||
|
||||
decode-uri-component@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
|
||||
integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==
|
||||
decode-uri-component@0.2.2, decode-uri-component@^0.2.0:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
|
||||
integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==
|
||||
|
||||
decompress-response@^6.0.0:
|
||||
version "6.0.0"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@automatisch/docs",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||
"private": true,
|
||||
|
||||
@ -41,6 +41,15 @@ export default defineConfig({
|
||||
{ text: 'Connection', link: '/apps/deepl/connection' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Delay',
|
||||
collapsible: true,
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'Actions', link: '/apps/delay/actions' },
|
||||
{ text: 'Connection', link: '/apps/delay/connection' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Discord',
|
||||
collapsible: true,
|
||||
@ -69,6 +78,15 @@ export default defineConfig({
|
||||
{ text: 'Connection', link: '/apps/github/connection' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Ntfy',
|
||||
collapsible: true,
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'Actions', link: '/apps/ntfy/actions' },
|
||||
{ text: 'Connection', link: '/apps/ntfy/connection' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'RSS',
|
||||
collapsible: true,
|
||||
@ -123,6 +141,15 @@ export default defineConfig({
|
||||
{ text: 'Connection', link: '/apps/stripe/connection' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Telegram',
|
||||
collapsible: true,
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'Actions', link: '/apps/telegram-bot/actions' },
|
||||
{ text: 'Connection', link: '/apps/telegram-bot/connection' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Twilio',
|
||||
collapsible: true,
|
||||
@ -152,6 +179,15 @@ export default defineConfig({
|
||||
{ text: 'Connection', link: '/apps/typeform/connection' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Webhooks',
|
||||
collapsible: true,
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'Triggers', link: '/apps/webhooks/triggers' },
|
||||
{ text: 'Connection', link: '/apps/webhooks/connection' },
|
||||
],
|
||||
},
|
||||
],
|
||||
'/': [
|
||||
{
|
||||
|
||||
51
packages/docs/pages/.vitepress/theme/CustomLayout.vue
Normal file
@ -0,0 +1,51 @@
|
||||
<style scoped>
|
||||
.announcement-bar {
|
||||
background: var(--vp-c-brand-dark);
|
||||
color: #fff;
|
||||
border-bottom: 2px solid rgba(194, 194, 194, 0.2);
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
position: fixed;
|
||||
padding: 4px 40px;
|
||||
z-index: 999999;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 400;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.announcement-bar {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script setup>
|
||||
import DefaultTheme from 'vitepress/theme';
|
||||
|
||||
const { Layout } = DefaultTheme;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Layout>
|
||||
<template #layout-top>
|
||||
<div class="announcement-bar">
|
||||
<span
|
||||
>⭐ If you like what we are doing with Automatisch, please give us a
|
||||
star on
|
||||
<strong
|
||||
><a
|
||||
href="https://github.com/automatisch/automatisch"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>GitHub</a
|
||||
></strong
|
||||
>.</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</Layout>
|
||||
</template>
|
||||
@ -117,9 +117,33 @@
|
||||
|
||||
:root {
|
||||
overflow-y: scroll;
|
||||
|
||||
--announcement-bar-height: 50px;
|
||||
}
|
||||
|
||||
.VPTeamMembersItem .avatar-img {
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
header.VPNav {
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.VPNavScreen.VPNavScreen {
|
||||
top: calc(var(--announcement-bar-height) + var(--vp-nav-height-mobile));
|
||||
}
|
||||
|
||||
.VPLocalNav.VPLocalNav {
|
||||
top: 50px;
|
||||
}
|
||||
|
||||
aside.VPSidebar {
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
#VPContent {
|
||||
margin-top: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
import DefaultTheme from 'vitepress/theme';
|
||||
import './custom.css';
|
||||
export default DefaultTheme;
|
||||
import CustomLayout from './CustomLayout.vue';
|
||||
|
||||
export default {
|
||||
...DefaultTheme,
|
||||
Layout: CustomLayout,
|
||||
};
|
||||
|
||||
@ -11,28 +11,31 @@ The default values for some environment variables might be different in our deve
|
||||
:::
|
||||
|
||||
:::danger
|
||||
Please be careful with the `ENCRYPTION_KEY` environment variable. It is used to encrypt your credentials from third-party services. If you change it, you will not be able to access your connections and thus, your existing flows and connections will be useless.
|
||||
Please be careful with the `ENCRYPTION_KEY` and `WEBHOOK_SECRET_KEY` environment variables. They are used to encrypt your credentials from third-party services and verify webhook requests. If you change them, your existing connections and flows will not continue to work.
|
||||
:::
|
||||
|
||||
| Variable Name | Type | Default Value | Description |
|
||||
| --------------------------- | ------- | ------------------ | ----------------------------------- |
|
||||
| `HOST` | string | `localhost` | HTTP Host |
|
||||
| `PROTOCOL` | string | `http` | HTTP Protocol |
|
||||
| `PORT` | string | `3000` | HTTP Port |
|
||||
| `APP_ENV` | string | `production` | Automatisch Environment |
|
||||
| `POSTGRES_DATABASE` | string | `automatisch` | Database Name |
|
||||
| `POSTGRES_PORT` | number | `5432` | Database Port |
|
||||
| `POSTGRES_HOST` | string | `postgres` | Database Host |
|
||||
| `POSTGRES_USERNAME` | string | `automatisch_user` | Database User |
|
||||
| `POSTGRES_PASSWORD` | string | | Password of Database User |
|
||||
| `ENCRYPTION_KEY` | string | | Encryption Key to store credentials |
|
||||
| `APP_SECRET_KEY` | string | | Secret Key to authenticate the user |
|
||||
| `REDIS_HOST` | string | `redis` | Redis Host |
|
||||
| `REDIS_PORT` | number | `6379` | Redis Port |
|
||||
| `REDIS_USERNAME` | string | `` | Redis Username |
|
||||
| `REDIS_PASSWORD` | string | `` | Redis Password |
|
||||
| `REDIS_TLS` | boolean | `false` | Redis TLS |
|
||||
| `TELEMETRY_ENABLED` | boolean | `true` | Enable/Disable Telemetry |
|
||||
| `ENABLE_BULLMQ_DASHBOARD` | boolean | `false` | Enable BullMQ Dashboard |
|
||||
| `BULLMQ_DASHBOARD_USERNAME` | string | | Username to login BullMQ Dashboard |
|
||||
| `BULLMQ_DASHBOARD_PASSWORD` | string | | Password to login BullMQ Dashboard |
|
||||
| Variable Name | Type | Default Value | Description |
|
||||
| --------------------------- | ------- | ------------------ | ---------------------------------------------------- |
|
||||
| `HOST` | string | `localhost` | HTTP Host |
|
||||
| `PROTOCOL` | string | `http` | HTTP Protocol |
|
||||
| `PORT` | string | `3000` | HTTP Port |
|
||||
| `APP_ENV` | string | `production` | Automatisch Environment |
|
||||
| `WEB_APP_URL` | string | | Can be used to override connection URLs and CORS URL |
|
||||
| `WEBHOOK_URL` | string | | Can be used to override webhook URL |
|
||||
| `POSTGRES_DATABASE` | string | `automatisch` | Database Name |
|
||||
| `POSTGRES_PORT` | number | `5432` | Database Port |
|
||||
| `POSTGRES_HOST` | string | `postgres` | Database Host |
|
||||
| `POSTGRES_USERNAME` | string | `automatisch_user` | Database User |
|
||||
| `POSTGRES_PASSWORD` | string | | Password of Database User |
|
||||
| `ENCRYPTION_KEY` | string | | Encryption Key to store credentials |
|
||||
| `WEBHOOK_SECRET_KEY` | string | | Webhook Secret Key to verify webhook requests |
|
||||
| `APP_SECRET_KEY` | string | | Secret Key to authenticate the user |
|
||||
| `REDIS_HOST` | string | `redis` | Redis Host |
|
||||
| `REDIS_PORT` | number | `6379` | Redis Port |
|
||||
| `REDIS_USERNAME` | string | | Redis Username |
|
||||
| `REDIS_PASSWORD` | string | | Redis Password |
|
||||
| `REDIS_TLS` | boolean | `false` | Redis TLS |
|
||||
| `TELEMETRY_ENABLED` | boolean | `true` | Enable/Disable Telemetry |
|
||||
| `ENABLE_BULLMQ_DASHBOARD` | boolean | `false` | Enable BullMQ Dashboard |
|
||||
| `BULLMQ_DASHBOARD_USERNAME` | string | | Username to login BullMQ Dashboard |
|
||||
| `BULLMQ_DASHBOARD_PASSWORD` | string | | Password to login BullMQ Dashboard |
|
||||
|
||||
@ -5,5 +5,5 @@ We need to store your credentials in order to automatically communicate with thi
|
||||
Automatisch uses AES specification to encrypt and decrypt your credentials of third-party services. The Advanced Encryption Standard (AES) is a U.S. Federal Information Processing Standard (FIPS). It was selected after a 5-year process where 15 competing designs were evaluated. AES is now used worldwide to protect sensitive information.
|
||||
|
||||
:::danger
|
||||
Please be careful with the `ENCRYPTION_KEY` environment variable. It is used to encrypt your credentials from third-party services. If you change it, you will not be able to access your connections and thus, your existing flows and connections will be useless.
|
||||
Please be careful with the `ENCRYPTION_KEY` and `WEBHOOK_SECRET_KEY` environment variables. They are used to encrypt your credentials from third-party services and verify webhook requests. If you change them, your existing connections and flows will not continue to work.
|
||||
:::
|
||||
|
||||
14
packages/docs/pages/apps/delay/actions.md
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
favicon: /favicons/delay.svg
|
||||
items:
|
||||
- name: Delay For
|
||||
desc: Delays the execution of the next action by a specified amount of time.
|
||||
- name: Delay Until
|
||||
desc: Delays the execution of the next action until a specified date.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import CustomListing from '../../components/CustomListing.vue'
|
||||
</script>
|
||||
|
||||
<CustomListing />
|
||||
3
packages/docs/pages/apps/delay/connection.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Delay
|
||||
|
||||
Delay is a built-in app shipped with Automatisch, and it doesn't need to talk with any other external service to run. So there are no additional steps to use the Delay app. It can be used only as an action and it delays the execution of the next action by a specified amount of time.
|
||||
12
packages/docs/pages/apps/ntfy/actions.md
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
favicon: /favicons/ntfy.svg
|
||||
items:
|
||||
- name: Send a message
|
||||
desc: Sends a message to a topic you specify.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import CustomListing from '../../components/CustomListing.vue'
|
||||
</script>
|
||||
|
||||
<CustomListing />
|
||||
10
packages/docs/pages/apps/ntfy/connection.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Ntfy
|
||||
|
||||
:::info
|
||||
This page explains the steps you need to follow to set up the Ntfy
|
||||
connection in Automatisch. If any of the steps are outdated, please let us know!
|
||||
:::
|
||||
|
||||
If you use ntfy.sh, the official public server for this service, you do not need to set up a connection with a custom configuration. It's enough to create one with the default server URL.
|
||||
|
||||
However, if you have a ntfy installation, that's different than ntfy.sh, you need to specify your server URL on Automatisch while creating a connection. Additionally, you may need to provide your username and password if your installation requires authentication.
|
||||
12
packages/docs/pages/apps/telegram-bot/actions.md
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
favicon: /favicons/telegram-bot.svg
|
||||
items:
|
||||
- name: Send a message
|
||||
desc: Sends a message to a chat you specify.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import CustomListing from '../../components/CustomListing.vue'
|
||||
</script>
|
||||
|
||||
<CustomListing />
|
||||
14
packages/docs/pages/apps/telegram-bot/connection.md
Normal file
@ -0,0 +1,14 @@
|
||||
# Telegram
|
||||
|
||||
:::info
|
||||
This page explains the steps you need to follow to set up the Telegram
|
||||
connection in Automatisch. If any of the steps are outdated, please let us know!
|
||||
:::
|
||||
|
||||
1. Start a chat with [Botfather](https://telegram.me/BotFather).
|
||||
1. Enter `/newbot`.
|
||||
1. Enter a name for your bot.
|
||||
1. Enter a username for your bot.
|
||||
1. Copy the **token** value from the answer to the **Bot token** field on Automatisch.
|
||||
1. Click **Submit** button on Automatisch.
|
||||
1. Congrats! Start using your new Telegram connection within the flows.
|
||||
7
packages/docs/pages/apps/webhooks/connection.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Webhooks
|
||||
|
||||
Webhooks is a built-in app shipped with Automatisch, and it doesn't need to authenticate with any other external service to run.
|
||||
|
||||
## How to use
|
||||
|
||||
You will be given a webhook URL in the test substep on the editor page, and you can use it to send a GET, POST, PUT, or PATCH request to Automatisch to trigger the flow.
|
||||
12
packages/docs/pages/apps/webhooks/triggers.md
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
favicon: /favicons/webhooks.svg
|
||||
items:
|
||||
- name: Catch raw webhook
|
||||
desc: Triggers when the webhook receives a request.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import CustomListing from '../../components/CustomListing.vue'
|
||||
</script>
|
||||
|
||||
<CustomListing />
|
||||
@ -49,6 +49,10 @@ The build integrations section is best understood when read from beginning to en
|
||||
|
||||
### Webhook-based triggers
|
||||
|
||||
:::warning
|
||||
If you are developing a webhook-based trigger, you need to ensure that the webhook is publicly accessible. You can use [ngrok](https://ngrok.com) for this purpose and override the webhook URL by setting the **WEBHOOK_URL** environment variable.
|
||||
:::
|
||||
|
||||
- [New entry - Typeform](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/typeform/triggers/new-entry/index.ts)
|
||||
|
||||
### Pagination with descending order
|
||||
|
||||
@ -7,14 +7,19 @@ We just have a few available integrations at the moment and we also know that wo
|
||||
Following integrations are currently supported by Automatisch.
|
||||
|
||||
- [DeepL](/apps/deepl/actions)
|
||||
- [Delay](/apps/delay/actions)
|
||||
- [Discord](/apps/discord/actions)
|
||||
- [Flickr](/apps/flickr/triggers)
|
||||
- [Github](/apps/github/triggers)
|
||||
- [RSS](/apps/rss/triggers)
|
||||
- [Ntfy](/apps/ntfy/triggers)
|
||||
- [Salesforce](/apps/salesforce/triggers)
|
||||
- [Scheduler](/apps/scheduler/triggers)
|
||||
- [Slack](/apps/slack/actions)
|
||||
- [SMTP](/apps/smtp/actions)
|
||||
- [Stripe](/apps/stripe/triggers)
|
||||
- [Telegram](/apps/telegram-bot/actions)
|
||||
- [Twilio](/apps/twilio/triggers)
|
||||
- [Twitter](/apps/twitter/triggers)
|
||||
- [Typeform](/apps/typeform/triggers)
|
||||
- [Webhooks](/apps/webhooks/triggers)
|
||||
|
||||
@ -1 +1,53 @@
|
||||
TBD
|
||||
# Create Flow
|
||||
|
||||
To understand how we can create a flow, it's better to start with a real use case. Let's say we want to create a flow that will fetch new submissions from Typeform and then send them to a Slack channel. To do that, we will use [Typeform](/apps/typeform/triggers) and [Slack](/apps/slack/actions) apps. Let's start with creating connections for these apps.
|
||||
|
||||
## Typeform connection
|
||||
|
||||
- Go to the **My Apps** page in Automatisch and click on **Add connection** button.
|
||||
- Select the **Typeform** app from the list.
|
||||
- It will ask you `Client ID` and `Client Secret` from Typeform and there is an information box above the fields.
|
||||
- Click on **our documentation** link in the information box and follow the instructions to get the `Client ID` and `Client Secret` from Typeform.
|
||||
|
||||
:::tip
|
||||
Whenever you want to create a connection for an app, you can click on **our documentation** link in the information box to learn how to create a connection for that specific app.
|
||||
:::
|
||||
|
||||
- After you get the `Client ID` and `Client Secret` from Typeform, you can paste them to the fields in Automatisch and click on **Submit** button.
|
||||
|
||||
## Slack connection
|
||||
|
||||
- Go to the **My Apps** page in Automatisch and click on **Add connection** button.
|
||||
- Select the **Slack** app from the list.
|
||||
- It will ask you `API Key` and `API Secret` values from Slack and there is an information box above the fields.
|
||||
- Click on **our documentation** link in the information box and follow the instructions to get the `API Key` and `API Secret` from Slack.
|
||||
- After you get the `API Key` and `API Secret` from Slack, you can paste them into the fields in Automatisch and click on **Submit** button.
|
||||
|
||||
## Build the flow
|
||||
|
||||
### Trigger step
|
||||
|
||||
- Go to the **Flows** page in Automatisch and click on **Create flow** button.
|
||||
- It will give you empty trigger and action steps.
|
||||
- For the trigger step (1st step), select the **Typeform** app from `Choose an app` dropdown.
|
||||
- Select the **New entry** as the trigger event and click on the **Continue** button.
|
||||
- It will ask you to select the connection you created for the Typeform app. Select the connection you have just created and click on the **Continue** button.
|
||||
- Select the form you want to get the new entries from and click on the **Continue** button.
|
||||
- Click on **Test & Continue** button to test the trigger step. If you see the data that reflects the recent submission in the form, you can continue to the next (action) step.
|
||||
|
||||
### Action step
|
||||
|
||||
- For the action step (2nd step), select the **Slack** app from `Choose an app` dropdown.
|
||||
- Select the **Send a message to channel** as the action event and click on the **Continue** button.
|
||||
- It will ask you to select the connection you created for the Slack app. Select the connection you have just created and click on the **Continue** button.
|
||||
- Select the channel you want to send the message to.
|
||||
- Write the message you want to send to the channel. You can use variables in the message from the trigger step.
|
||||
- Select `Yes` for the `Send as a bot` option.
|
||||
- Give a name for the bot and click on the **Continue** button.
|
||||
- Click on **Test & Continue** button to test the action step. If you see the message in the Slack channel you selected, we can say that the flow is working as expected and is ready to be published.
|
||||
|
||||
### Publish the flow
|
||||
|
||||
- Click on the **Publish** button to publish the flow.
|
||||
- Published flows will be executed automatically when the trigger event happens or at intervals of 15 minutes depending on the trigger type.
|
||||
- You can not change the flow after it's published. If you want to change the flow, you need to unpublish it first and then make the changes.
|
||||
|
||||
@ -1,6 +1,20 @@
|
||||
# Installation
|
||||
|
||||
You can install Automatisch by using docker compose.
|
||||
:::info
|
||||
We have installation guides for docker compose and docker setup at the moment, but if you need another installation type, let us know by [creating a GitHub issue](https://github.com/automatisch/automatisch/issues/new).
|
||||
:::
|
||||
|
||||
:::tip
|
||||
|
||||
You can use `user@automatisch.io` email address and `sample` password to login to Automatisch. Please do not forget to change your email and password from the settings page.
|
||||
|
||||
:::
|
||||
|
||||
:::danger
|
||||
Please be careful with the `ENCRYPTION_KEY` and `WEBHOOK_SECRET_KEY` environment variables. They are used to encrypt your credentials from third-party services and verify webhook requests. If you change them, your existing connections and flows will not continue to work.
|
||||
:::
|
||||
|
||||
## Docker Compose
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
@ -10,17 +24,72 @@ git clone git@github.com:automatisch/automatisch.git
|
||||
cd automatisch
|
||||
|
||||
# Start
|
||||
docker compose -p automatisch up
|
||||
docker compose up
|
||||
```
|
||||
|
||||
## Let's discover!
|
||||
✌️ That's it; you have Automatisch running. Let's check it out by browsing [http://localhost:3000](https://localhost:3000)
|
||||
|
||||
✌️ That's it; you have Automatisch running. Let's check it out by browsing `http://localhost:3000`
|
||||
## Docker
|
||||
|
||||
:::tip
|
||||
Automatisch comes with two services which are `main` and `worker`. They both use the same image and need to have the same environment variables except for the `WORKER` environment variable which is set to `true` for the worker service.
|
||||
|
||||
You can use `user@automatisch.io` email address and `sample` password to login to Automatisch. You can also change your email and password later on from the settings page.
|
||||
::: warning
|
||||
We give the sample environment variable files for the setup but you should adjust them to include your own values.
|
||||
:::
|
||||
|
||||
To run the main:
|
||||
|
||||
```bash
|
||||
docker run --env-file=./.env automatischio/automatisch
|
||||
```
|
||||
|
||||
To run the worker:
|
||||
|
||||
```bash
|
||||
docker run --env-file=./.env -e WORKER=true automatischio/automatisch
|
||||
```
|
||||
|
||||
::: details .env
|
||||
|
||||
```bash
|
||||
APP_ENV=production
|
||||
HOST=
|
||||
PROTOCOL=
|
||||
PORT=
|
||||
ENCRYPTION_KEY=
|
||||
WEBHOOK_SECRET_KEY=
|
||||
APP_SECRET_KEY=
|
||||
POSTGRES_HOST=
|
||||
POSTGRES_PORT=
|
||||
POSTGRES_DATABASE=
|
||||
POSTGRES_USERNAME=
|
||||
POSTGRES_PASSWORD=
|
||||
POSTGRES_ENABLE_SSL=
|
||||
REDIS_HOST=
|
||||
REDIS_PORT=
|
||||
REDIS_USERNAME=
|
||||
REDIS_PASSWORD=
|
||||
REDIS_TLS=
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Render
|
||||
|
||||
<a href="https://render.com/deploy?repo=https://github.com/automatisch/automatisch">
|
||||
<img src="https://render.com/images/deploy-to-render-button.svg" alt="Deploy to Render">
|
||||
</a>
|
||||
|
||||
:::info
|
||||
|
||||
We use default values of render plans with the `render.yaml` file, if you want to use the free plan or change the plan, you can change the `render.yaml` file in your fork and use your repository URL while creating a blueprint in Render.
|
||||
|
||||
:::
|
||||
|
||||
## Production setup
|
||||
|
||||
If you need to change any other environment variables for your production setup, let's check out the [environment variables](/advanced/configuration#environment-variables) section of the configuration page.
|
||||
|
||||
## Let's discover!
|
||||
|
||||
If you see any problems while installing Automatisch, let us know via [github issues](https://github.com/automatisch/automatisch/issues) or our [discord server](https://discord.gg/dJSah9CVrC).
|
||||
|
||||
@ -28,7 +28,7 @@ You need to prepare the workflow once, and it will run continuously until you st
|
||||
|
||||
There are other existing solutions in the market, like Zapier and Integromat, so you might be wondering why you should use Automatisch.
|
||||
|
||||
✅ The most significant advantage of having Automatisch is **keeping your data on your own servers**. Not all companies want to use an automation service in the cloud, and the current open-source or self-hosted solutions mainly focus on developers rather than a user without a technical background.
|
||||
✅ One of the main benefits of using Automatisch is that it allows you to **store your data on your own servers**, which is essential for businesses that handle sensitive user information and cannot risk sharing it with external cloud services. This is especially relevant for industries such as healthcare and finance, as well as for European companies that must adhere to the General Data Protection Regulation (GDPR).
|
||||
|
||||
🤓 Your contributions are vital to the development of Automatisch. As an **open-source software**, anyone can have an impact on how it is being developed.
|
||||
|
||||
|
||||
7
packages/docs/pages/public/favicons/delay.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 122.88 100.6" style="enable-background:new 0 0 122.88 100.6" xml:space="preserve">
|
||||
<style type="text/css">.st0{fill:#272727;} .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#000000;}</style>
|
||||
<g>
|
||||
<path class="st0" d="M72.58,0c6.8,0,13.3,1.36,19.23,3.81c6.16,2.55,11.7,6.29,16.33,10.92l0,0c4.63,4.63,8.37,10.17,10.92,16.34 c2.46,5.93,3.81,12.43,3.81,19.23c0,6.8-1.36,13.3-3.81,19.23c-2.55,6.16-6.29,11.7-10.92,16.33l0,0 c-4.63,4.63-10.17,8.37-16.34,10.92c-5.93,2.46-12.43,3.81-19.23,3.81c-6.8,0-13.3-1.36-19.23-3.81 c-6.15-2.55-11.69-6.28-16.33-10.92l-0.01-0.01c-4.64-4.64-8.37-10.17-10.92-16.33c-0.79-1.91-1.47-3.87-2.02-5.89 c1.05,0.1,2.12,0.15,3.2,0.15c2.05,0,4.05-0.19,6-0.54c0.32,0.97,0.67,1.93,1.06,2.87c2.09,5.05,5.17,9.6,8.99,13.43 c3.82,3.82,8.38,6.9,13.43,8.99c4.87,2.02,10.21,3.13,15.83,3.13c5.62,0,10.96-1.11,15.83-3.13c5.05-2.09,9.6-5.17,13.43-8.99 c3.82-3.82,6.9-8.38,8.99-13.43c2.02-4.87,3.13-10.21,3.13-15.83c0-5.62-1.11-10.96-3.13-15.83c-2.09-5.05-5.17-9.6-8.99-13.43 c-3.82-3.82-8.38-6.9-13.43-8.99c-4.87-2.02-10.21-3.13-15.83-3.13c-5.62,0-10.96,1.11-15.83,3.13c-0.44,0.18-0.87,0.37-1.3,0.56 c-1.65-2.61-3.66-4.97-5.95-7.02c1.25-0.65,2.53-1.24,3.84-1.79C59.28,1.36,65.78,0,72.58,0L72.58,0z M66.8,26.39 c0-1.23,0.5-2.35,1.31-3.16c0.81-0.81,1.93-1.31,3.16-1.31c1.23,0,2.35,0.5,3.16,1.31c0.81,0.81,1.31,1.93,1.31,3.16v23.47 l17.54,10.4c1.05,0.62,1.76,1.62,2.05,2.73c0.28,1.1,0.15,2.31-0.47,3.37l0,0.01l0,0c-0.62,1.05-1.62,1.76-2.73,2.05 c-1.1,0.28-2.31,0.15-3.37-0.47l-0.01,0l0,0L69.1,56.29c-0.67-0.38-1.24-0.92-1.64-1.57c-0.42-0.68-0.66-1.48-0.66-2.32V26.39 L66.8,26.39z"/>
|
||||
<path class="st1" d="M27.27,3.18c15.06,0,27.27,12.21,27.27,27.27c0,15.06-12.21,27.27-27.27,27.27C12.21,57.73,0,45.52,0,30.45 C0,15.39,12.21,3.18,27.27,3.18L27.27,3.18z M24.35,41.34h5.82v5.16h-5.82V41.34L24.35,41.34L24.35,41.34z M30.17,37.77h-5.82 c-0.58-7.07-1.8-11.56-1.8-18.63c0-2.61,2.12-4.72,4.72-4.72c2.61,0,4.72,2.12,4.72,4.72C32,26.2,30.76,30.7,30.17,37.77 L30.17,37.77L30.17,37.77L30.17,37.77z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
1
packages/docs/pages/public/favicons/ntfy.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg width="50mm" height="50mm" viewBox="0 0 50 50" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="a"><stop style="stop-color:#348878;stop-opacity:1" offset="0"/><stop style="stop-color:#56bda8;stop-opacity:1" offset="1"/></linearGradient><linearGradient xlink:href="#a" id="b" x1="160.722" y1="128.533" x2="168.412" y2="134.326" gradientUnits="userSpaceOnUse" gradientTransform="translate(-845.726 -630.598) scale(5.59448)"/></defs><g style="display:inline"><path style="color:#000;fill:url(#b);stroke:none;stroke-width:3.72347;-inkscape-stroke:none" d="M94.237 89.912H59.499c-2.388 0-4.342 1.844-4.342 4.098l.033 27.754-.648 3.738 9.297-2.806h30.396c2.388 0 4.342-1.845 4.342-4.099V94.01c0-2.254-1.954-4.098-4.342-4.098z" transform="translate(-51.147 -81.516)"/><path style="color:#000;fill:#fff;stroke:none;stroke-width:.762343;-inkscape-stroke:none" d="M58.849 86.79c-3.62 0-6.72 2.848-6.72 6.47v.002l.035 30.273-.91 6.708 12.362-3.284h30.729c3.62 0 6.72-2.852 6.72-6.473V93.26c0-3.62-3.099-6.469-6.717-6.469h-.003zm0 4.566h35.499c1.272 0 2.151.927 2.151 1.903v27.227c0 .977-.88 1.924-2.154 1.903h-31.4l-6.28 1.898.065-.37-.035-30.658c0-.977.88-1.903 2.154-1.903z" transform="translate(-51.147 -81.516)"/><g style="font-size:8.48274px;font-family:sans-serif;letter-spacing:0;word-spacing:0;fill:#000;stroke:none;stroke-width:.525121"><path style="color:#000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#fff;stroke:none;-inkscape-stroke:none" d="M62.57 116.77v-1.312l3.28-1.459q.159-.068.306-.102.158-.045.283-.068l.271-.022v-.09q-.136-.012-.271-.046-.125-.023-.283-.057-.147-.045-.306-.113l-3.28-1.459v-1.323l5.068 2.319v1.413z" transform="matrix(2.1689 0 0 2.57844 -124.28 -268.742)"/><path style="color:#000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#fff;stroke:none;-inkscape-stroke:none" d="M62.309 110.31v1.903l3.437 1.53.022.007-.022.008-3.437 1.53v1.892l.37-.17 5.221-2.39v-1.75zm.525.817 4.541 2.08v1.076l-4.541 2.078v-.732l3.12-1.389.003-.002a1.56 1.56 0 0 1 .258-.086h.006l.008-.002c.094-.027.176-.047.246-.06l.498-.041v-.574l-.24-.02a1.411 1.411 0 0 1-.231-.04l-.008-.001-.008-.002a9.077 9.077 0 0 1-.263-.053 2.781 2.781 0 0 1-.266-.097l-.004-.002-3.119-1.39z" transform="matrix(2.1689 0 0 2.57844 -124.28 -268.742)"/></g><g style="font-size:8.48274px;font-family:sans-serif;letter-spacing:0;word-spacing:0;fill:#000;stroke:none;stroke-width:.525121"><path style="color:#000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#fff;stroke:none;-inkscape-stroke:none" d="M69.171 117.754h5.43v1.278h-5.43Z" transform="matrix(2.16247 0 0 2.48294 -122.76 -261.211)"/><path style="color:#000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#fff;stroke:none;-inkscape-stroke:none" d="M68.908 117.492v1.802h5.955v-1.802zm.526.524h4.904v.754h-4.904z" transform="matrix(2.16247 0 0 2.48294 -122.76 -261.211)"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
14
packages/docs/pages/public/favicons/telegram-bot.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="256px" height="256px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
|
||||
<title>Telegram</title>
|
||||
<defs>
|
||||
<linearGradient x1="50%" y1="2.77555756e-15%" x2="50%" y2="100%" id="telegramLinearGradient-1">
|
||||
<stop stop-color="#2AABEE" offset="0%"></stop>
|
||||
<stop stop-color="#229ED9" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g>
|
||||
<path d="M128,0 C94.06,0 61.48,13.494 37.5,37.49 C13.5,61.486 0,94.066 0,128 C0,161.934 13.5,194.514 37.5,218.51 C61.48,242.506 94.06,256 128,256 C161.94,256 194.52,242.506 218.5,218.51 C242.5,194.514 256,161.934 256,128 C256,94.066 242.5,61.486 218.5,37.49 C194.52,13.494 161.94,0 128,0 Z" fill="url(#telegramLinearGradient-1)"></path>
|
||||
<path d="M57.94,126.6476 C95.26,110.3916 120.14,99.6736 132.58,94.4956 C168.14,79.7096 175.52,77.1416 180.34,77.0547542 C181.4,77.0376 183.76,77.2996 185.3,78.5456 C186.58,79.5956 186.94,81.0156 187.12,82.0116 C187.28,83.0076 187.5,85.2776 187.32,87.0496 C185.4,107.2896 177.06,156.4056 172.82,179.0756 C171.04,188.668 167.5,191.884 164.08,192.198 C156.64,192.882 151,187.286 143.8,182.5676 C132.54,175.1816 126.18,170.5856 115.24,163.3796 C102.6,155.0516 110.8,150.4736 118,142.9936 C119.88,141.0356 152.64,111.2456 153.26,108.5436 C153.34,108.2056 153.42,106.9456 152.66,106.2816 C151.92,105.6156 150.82,105.8436 150.02,106.0236 C148.88,106.2796 130.9,118.1756 96.02,141.7096 C90.92,145.2176 86.3,146.9276 82.14,146.8376 C77.58,146.7396 68.78,144.2536 62.24,142.1296 C54.24,139.5236 47.86,138.1456 48.42,133.7196 C48.7,131.4156 51.88,129.0576 57.94,126.6476 L57.94,126.6476 Z" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
8
packages/docs/pages/public/favicons/webhooks.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48" width="48px" height="48px"><g id="surface56721297">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,12.156863%,32.156864%);fill-opacity:1;" d="M 35 37 C 32.800781 37 31 35.199219 31 33 C 31 30.800781 32.800781 29 35 29 C 37.199219 29 39 30.800781 39 33 C 39 35.199219 37.199219 37 35 37 Z M 35 37 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,12.156863%,32.156864%);fill-opacity:1;" d="M 35 43 C 32 43 29.101562 41.601562 27.199219 39.300781 L 30.300781 36.800781 C 31.398438 38.199219 33.199219 39.101562 35 39.101562 C 38.300781 39.101562 41 36.398438 41 33.101562 C 41 29.800781 38.300781 27.101562 35 27.101562 C 34 27.101562 33 27.398438 32.101562 27.800781 L 30.398438 28.800781 L 23.300781 16 L 26.800781 14.101562 L 32.101562 23.5 C 33.101562 23.199219 34.101562 23 35.101562 23 C 40.601562 23 45.101562 27.5 45.101562 33 C 45.101562 38.5 40.5 43 35 43 Z M 35 43 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,12.156863%,32.156864%);fill-opacity:1;" d="M 14 43 C 8.5 43 4 38.5 4 33 C 4 28.398438 7.101562 24.5 11.5 23.300781 L 12.5 27.199219 C 9.898438 27.898438 8 30.300781 8 33 C 8 36.300781 10.699219 39 14 39 C 17.300781 39 20 36.300781 20 33 L 20 31 L 35 31 L 35 35 L 23.800781 35 C 22.898438 39.601562 18.800781 43 14 43 Z M 14 43 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,34.901962%,96.862745%);fill-opacity:1;" d="M 14 37 C 11.800781 37 10 35.199219 10 33 C 10 30.800781 11.800781 29 14 29 C 16.199219 29 18 30.800781 18 33 C 18 35.199219 16.199219 37 14 37 Z M 14 37 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,12.156863%,32.156864%);fill-opacity:1;" d="M 25 19 C 22.800781 19 21 17.199219 21 15 C 21 12.800781 22.800781 11 25 11 C 27.199219 11 29 12.800781 29 15 C 29 17.199219 27.199219 19 25 19 Z M 25 19 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,34.901962%,96.862745%);fill-opacity:1;" d="M 15.699219 34 L 12.300781 32 L 18.199219 22.300781 C 16.199219 20.398438 15 17.800781 15 15 C 15 9.5 19.5 5 25 5 C 30.5 5 35 9.5 35 15 C 35 15.898438 34.898438 16.699219 34.699219 17.5 L 30.800781 16.5 C 30.898438 16 31 15.5 31 15 C 31 11.699219 28.300781 9 25 9 C 21.699219 9 19 11.699219 19 15 C 19 17.101562 20.101562 19 21.898438 20.101562 L 23.601562 21.101562 Z M 15.699219 34 "/>
|
||||
</g></svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@automatisch/e2e-tests",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||
|
||||
18
packages/types/index.d.ts
vendored
@ -54,6 +54,7 @@ export interface IStep {
|
||||
key?: string;
|
||||
appKey?: string;
|
||||
iconUrl: string;
|
||||
webhookUrl: string;
|
||||
type: 'action' | 'trigger';
|
||||
connectionId?: string;
|
||||
status: string;
|
||||
@ -75,6 +76,7 @@ export interface IFlow {
|
||||
steps: IStep[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
remoteWebhookId: string;
|
||||
lastInternalId: () => Promise<string>;
|
||||
}
|
||||
|
||||
@ -180,23 +182,16 @@ export interface IDynamicData {
|
||||
|
||||
export interface IAuth {
|
||||
generateAuthUrl?($: IGlobalVariable): Promise<void>;
|
||||
verifyCredentials($: IGlobalVariable): Promise<void>;
|
||||
isStillVerified($: IGlobalVariable): Promise<boolean>;
|
||||
verifyCredentials?($: IGlobalVariable): Promise<void>;
|
||||
isStillVerified?($: IGlobalVariable): Promise<boolean>;
|
||||
refreshToken?($: IGlobalVariable): Promise<void>;
|
||||
verifyWebhook?($: IGlobalVariable): Promise<boolean>;
|
||||
isRefreshTokenRequested?: boolean;
|
||||
fields: IField[];
|
||||
fields?: IField[];
|
||||
authenticationSteps?: IAuthenticationStep[];
|
||||
reconnectionSteps?: IAuthenticationStep[];
|
||||
}
|
||||
|
||||
export interface IService {
|
||||
authenticationClient?: IAuthentication;
|
||||
triggers?: any;
|
||||
actions?: any;
|
||||
data?: any;
|
||||
}
|
||||
|
||||
export interface ITriggerOutput {
|
||||
data: ITriggerItem[];
|
||||
error?: IJSONObject;
|
||||
@ -285,6 +280,8 @@ export type IGlobalVariable = {
|
||||
id: string;
|
||||
lastInternalId: string;
|
||||
isAlreadyProcessed?: (internalId: string) => boolean;
|
||||
remoteWebhookId?: string;
|
||||
setRemoteWebhookId?: (remoteWebhookId: string) => Promise<void>;
|
||||
};
|
||||
step?: {
|
||||
id: string;
|
||||
@ -300,6 +297,7 @@ export type IGlobalVariable = {
|
||||
id: string;
|
||||
testRun: boolean;
|
||||
};
|
||||
lastExecutionStep?: IExecutionStep;
|
||||
webhookUrl?: string;
|
||||
triggerOutput?: ITriggerOutput;
|
||||
actionOutput?: IActionOutput;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@automatisch/types",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "Type definitions for automatisch",
|
||||
"homepage": "https://github.com/automatisch/automatisch",
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "@automatisch/web",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.6.9",
|
||||
"@automatisch/types": "^0.2.0",
|
||||
"@automatisch/types": "^0.3.0",
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@hookform/resolvers": "^2.8.8",
|
||||
|
||||
@ -6,22 +6,15 @@ import DialogContentText from '@mui/material/DialogContentText';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import LoadingButton from '@mui/lab/LoadingButton';
|
||||
import { FieldValues, SubmitHandler } from 'react-hook-form';
|
||||
import { IJSONObject } from '@automatisch/types';
|
||||
import type { IApp, IJSONObject, IField } from '@automatisch/types';
|
||||
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
import computeAuthStepVariables from 'helpers/computeAuthStepVariables';
|
||||
import { processStep } from 'helpers/authenticationSteps';
|
||||
import InputCreator from 'components/InputCreator';
|
||||
import type { IApp, IField } from '@automatisch/types';
|
||||
import { generateExternalLink } from '../../helpers/translation-values';
|
||||
import { Form } from './style';
|
||||
|
||||
const generateDocsLink = (link: string) => (str: string) =>
|
||||
(
|
||||
<a href={link} target="_blank">
|
||||
{str}
|
||||
</a>
|
||||
);
|
||||
|
||||
type AddAppConnectionProps = {
|
||||
onClose: (response: Record<string, unknown>) => void;
|
||||
application: IApp;
|
||||
@ -112,7 +105,7 @@ export default function AddAppConnection(
|
||||
<Alert severity="info" sx={{ fontWeight: 300 }}>
|
||||
{formatMessage('addAppConnection.callToDocs', {
|
||||
appName: name,
|
||||
docsLink: generateDocsLink(authDocUrl),
|
||||
docsLink: generateExternalLink(authDocUrl),
|
||||
})}
|
||||
</Alert>
|
||||
)}
|
||||
@ -123,7 +116,7 @@ export default function AddAppConnection(
|
||||
sx={{ mt: 1, fontWeight: 500, wordBreak: 'break-all' }}
|
||||
>
|
||||
{error.message}
|
||||
<pre>{JSON.stringify(error.details, null, 2)}</pre>
|
||||
{error.details && <pre>{JSON.stringify(error.details, null, 2)}</pre>}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ import ListItemText from '@mui/material/ListItemText';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import Box from '@mui/material/Box';
|
||||
import type { IApp } from '@automatisch/types';
|
||||
|
||||
import * as URLS from 'config/urls';
|
||||
@ -76,7 +77,7 @@ export default function AddNewAppConnection(
|
||||
<Dialog open={true} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>{formatMessage('apps.addNewAppConnection')}</DialogTitle>
|
||||
|
||||
<DialogContent>
|
||||
<Box px={3}>
|
||||
<FormControl
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
@ -103,7 +104,9 @@ export default function AddNewAppConnection(
|
||||
data-test="search-for-app-text-field"
|
||||
/>
|
||||
</FormControl>
|
||||
</Box>
|
||||
|
||||
<DialogContent>
|
||||
<List sx={{ pt: 2, width: '100%' }}>
|
||||
{loading && (
|
||||
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} />
|
||||
|
||||
@ -25,11 +25,11 @@ const countTranslation = (value: React.ReactNode) => (
|
||||
|
||||
function AppRow(props: AppRowProps): React.ReactElement {
|
||||
const formatMessage = useFormatMessage();
|
||||
const { name, primaryColor, iconUrl, connectionCount, flowCount } =
|
||||
const { name, key, primaryColor, iconUrl, connectionCount, flowCount } =
|
||||
props.application;
|
||||
|
||||
return (
|
||||
<Link to={URLS.APP(name.toLowerCase())} data-test="app-row">
|
||||
<Link to={URLS.APP(key)} data-test="app-row">
|
||||
<Card sx={{ mb: 1 }}>
|
||||
<CardActionArea>
|
||||
<CardContent>
|
||||
|
||||