Compare commits

..

111 Commits

Author SHA1 Message Date
Ali BARIN
1125f31810 Release v0.3.0 2022-12-08 20:12:28 +01:00
Ali BARIN
ef3c93a907 Merge pull request #785 from automatisch/docs/webhooks
Docs: webhooks app
2022-12-08 20:02:53 +01:00
Faruk AYDIN
89bcaa96ed docs: Add trigger and connection pages of webhooks app 2022-12-08 22:00:17 +03:00
Ali BARIN
ce1eecf82e Merge pull request #782 from automatisch/docs/docker-installation
docs: Docker installation
2022-12-08 19:45:52 +01:00
Ali BARIN
3164663b4f Merge pull request #783 from automatisch/ntfy-field-descs
feat(ntfy): describe user & psswd in auth dialog
2022-12-08 19:45:11 +01:00
Ali BARIN
4f99bc36d0 Merge pull request #781 from automatisch/dependabot/npm_and_yarn/decode-uri-component-0.2.2
chore(deps): bump decode-uri-component from 0.2.0 to 0.2.2
2022-12-08 19:45:00 +01:00
Ali BARIN
179c0a39d4 feat(ntfy): describe user & psswd in auth dialog 2022-12-08 19:36:41 +01:00
Faruk AYDIN
0fefa4e43a chore: Make telegram and ntfy apps collapsed for docs sidebar 2022-12-08 21:31:23 +03:00
Ömer Faruk Aydın
d4e8eb276d Merge pull request #780 from automatisch/generic-webhook
Add webhook integration
2022-12-08 19:20:52 +01:00
Ömer Faruk Aydın
fd3c26ad73 Merge pull request #777 from automatisch/feature/ntfy
Add ntfy support with sending message
2022-12-08 19:13:57 +01:00
Ali BARIN
d9f9056515 refactor(webhook): remove auth placeholder 2022-12-08 11:57:36 +01:00
Faruk AYDIN
d326b5a36a chore: Add migration for webhook secret key 2022-12-08 13:27:08 +03:00
Faruk AYDIN
8c4970b550 chore: Use webhook secret key for new entry trigger 2022-12-08 13:00:17 +03:00
Ali BARIN
51ccdf3577 feat(webhook): do not process actions in test run 2022-12-08 10:42:35 +01:00
Ali BARIN
f79245d24a fix(webhook): remove registrations logic 2022-12-08 10:26:07 +01:00
Ali BARIN
e8d8a5e89d feat(webhook): mark test run in executions 2022-12-08 10:22:39 +01:00
Faruk AYDIN
b1dddcb511 docs: Add installation types info box 2022-12-08 10:33:15 +03:00
Faruk AYDIN
af45b8a1e4 docs: Adjust docker setup to use one .env file 2022-12-08 10:33:15 +03:00
Faruk AYDIN
2c8b60ab9b chore: Remove project flag from docker compose command 2022-12-08 10:33:15 +03:00
Faruk AYDIN
75196cbf84 feat: Introduce webhook secret key to verify webhook requests 2022-12-08 10:33:15 +03:00
Faruk AYDIN
5d80fd523c docs: Remind to change password after installation 2022-12-08 10:33:15 +03:00
Faruk AYDIN
22dc61f39b docs: Add docker installation section 2022-12-08 10:33:15 +03:00
dependabot[bot]
f4611d88cd chore(deps): bump decode-uri-component from 0.2.0 to 0.2.2
Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2.
- [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases)
- [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2)

---
updated-dependencies:
- dependency-name: decode-uri-component
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-08 01:56:18 +00:00
Ali BARIN
346a706e41 feat(Editor): show webhook info in test substep 2022-12-08 00:34:28 +01:00
Ali BARIN
3c62f182ab feat(webhook): add webhook application 2022-12-08 00:34:26 +01:00
Ömer Faruk Aydın
67964192de Merge pull request #778 from automatisch/worker-readiness-log
feat(worker): log when workers are ready
2022-12-06 18:37:51 +01:00
Ömer Faruk Aydın
c6f62fd078 Merge pull request #779 from automatisch/barinali-patch-1
docs(available-apps): fix telegram link
2022-12-06 18:17:50 +01:00
Ali BARIN
8ea547c1d7 docs(available-apps): fix telegram link 2022-12-06 18:14:21 +01:00
Ali BARIN
c5fedda195 feat(worker): log when workers are ready 2022-12-05 23:53:57 +01:00
Ali BARIN
8300a4b6df docs: add ntfy in available apps 2022-12-05 09:57:42 +01:00
Ali BARIN
32e77de8bb docs(ntfy): describe actions and auth 2022-12-05 00:52:13 +01:00
Ali BARIN
e1c080f237 feat(ntfy): add send message action 2022-12-05 00:45:30 +01:00
Ali BARIN
6995323a45 feat(ntfy): add auth support 2022-12-05 00:45:02 +01:00
Ali BARIN
46bfc597ec feat(ntfy): add application 2022-12-05 00:44:25 +01:00
Ömer Faruk Aydın
0abc614a78 Merge pull request #775 from automatisch/fix-autocomplete-search
fix(ControlledAutocomplete): use option value as option key
2022-12-04 22:51:56 +01:00
Ömer Faruk Aydın
7518456fe8 Merge pull request #776 from automatisch/add-option-value-in-autocomplete
feat(FlowSubstep): show autocomplete option value
2022-12-04 22:46:25 +01:00
Ali BARIN
4ab76e7507 feat(FlowSubstep): show autocomplete option value 2022-12-04 22:33:28 +01:00
Ömer Faruk Aydın
1cf5a88582 Merge pull request #770 from automatisch/feature/telegram-send-message
Add telegram bot support with sending message
2022-12-04 22:28:37 +01:00
Ali BARIN
14a8551e82 docs: add typeform and telegram in available apps 2022-12-04 21:26:11 +01:00
Ali BARIN
4b0916a214 fix(AppRow): use app key for links instead of name 2022-12-04 21:24:19 +01:00
Ali BARIN
e451dd2262 fix(ControlledAutocomplete): use option value as option key 2022-12-04 21:16:16 +01:00
Ali BARIN
2e4ecfab16 docs(telegram-bot): describe actions and auth 2022-12-04 17:27:59 +01:00
Ali BARIN
8c343abac5 feat(telegram-bot): add send message action 2022-12-04 17:27:59 +01:00
Ali BARIN
39ac3ff4c2 feat(telegram-bot): add authentication support 2022-12-04 17:27:58 +01:00
Ali BARIN
677880c633 feat(telegram-bot): add application 2022-12-04 17:27:58 +01:00
Ali BARIN
bd2f2cbf64 Merge pull request #761 from automatisch/docs/typeform-app
docs(typeform): Add connection and trigger pages
2022-12-01 18:22:45 +01:00
Ömer Faruk Aydın
4129264d1d Merge pull request #762 from automatisch/docs/webhook-examples
docs: Add the webhook-based trigger to integration examples
2022-12-01 18:00:49 +01:00
Faruk AYDIN
f9133a6e96 docs: Add the webhook-based trigger to integration examples 2022-12-01 17:57:38 +01:00
Faruk AYDIN
57a23946cc docs(typeform): Add connection and trigger pages 2022-12-01 17:57:07 +01:00
Ömer Faruk Aydın
6faa45ac16 Merge pull request #759 from automatisch/feature/typeform-integration
Add new entry trigger for typeform with webhook support
2022-11-30 22:06:07 +01:00
Ali BARIN
bf62ebd20a chore(typeform): add empty type definition file 2022-11-30 19:38:57 +01:00
Ali BARIN
687c6a09bc feat(webhook): register in mutation calls 2022-11-30 19:33:57 +01:00
Ali BARIN
77a7df1cff feat(webhook): process trigger in controller 2022-11-30 19:33:30 +01:00
Ali BARIN
05ce3edb80 fix(webhook): respond with 404 for non-webhook flows 2022-11-30 19:32:58 +01:00
Ali BARIN
2564d7d976 fix(typeform/new-entry): early exit when no response 2022-11-30 19:31:38 +01:00
Ali BARIN
2f06cda82b fix: remove circular serialization in GQL errors 2022-11-30 16:57:35 +01:00
Ali BARIN
945b777c3c fix(typeform): correct app color 2022-11-30 02:13:00 +01:00
Ali BARIN
d2a6c45fd6 refactor: simplify computed webhook event 2022-11-30 02:13:00 +01:00
Ali BARIN
b5436fe7fa chore: add comments in update flow status 2022-11-30 02:13:00 +01:00
Ali BARIN
82e3b40e7c refactor: remove redundant function signature 2022-11-30 02:12:59 +01:00
Ali BARIN
0f4f36c654 refactor: name webhook controller handler 2022-11-30 02:12:59 +01:00
Faruk AYDIN
d83e8dabf8 feat: Implement webhook logic along with new entry typeform trigger 2022-11-30 02:12:56 +01:00
Faruk AYDIN
397926f994 feat: Add listForms dynamic data to Typeform app 2022-11-30 02:12:26 +01:00
Faruk AYDIN
8d8a9f7c78 feat: Implement Typeform authentication 2022-11-30 02:12:25 +01:00
Ömer Faruk Aydın
40b8860e7c Merge pull request #751 from automatisch/fix/stripe-types
chore: Add type file for the stripe app
2022-11-29 22:23:08 +01:00
Faruk AYDIN
9978241329 chore: Add type file for the stripe app 2022-11-29 22:10:25 +01:00
Ömer Faruk Aydın
666a1ab81e Merge pull request #749 from PierreSchwang/feat/stripe
Feature: Stripe integration (triggers)
2022-11-29 22:06:58 +01:00
Pierre Maurice Schwang
1d6ed8d9d0 chore: align trigger key with trigger name 2022-11-29 21:32:20 +01:00
Pierre Maurice Schwang
34e2c77934 chore: address review comments 2022-11-29 20:06:17 +01:00
Pierre Maurice Schwang
f47d912074 Merge branch 'main' into feat/stripe 2022-11-28 19:28:33 +01:00
Pierre Maurice Schwang
f25b67c81c docs: add documentation for stripe connection / triggers 2022-11-28 19:24:51 +01:00
Ali BARIN
08e60e6961 Merge pull request #747 from automatisch/issue-728
feat(queue): auto clean up complete and fail jobs
2022-11-28 17:55:25 +01:00
Ali BARIN
c393a80185 Merge branch 'main' into issue-728 2022-11-28 17:06:44 +01:00
Ali BARIN
8e28ec491c Merge pull request #748 from automatisch/issue-745
feat: accept username, password, tls for redis configuration
2022-11-28 17:06:20 +01:00
Ali BARIN
2e391cc651 feat(queue): auto clean up complete and fail jobs 2022-11-28 02:01:12 +01:00
Ali BARIN
2e0b2191c0 feat: accept username, password, tls for redis configuration 2022-11-28 01:57:43 +01:00
Ömer Faruk Aydın
4240849a2a Merge pull request #746 from automatisch/separate-dockerfile-for-release
Separate dockerfile for release
2022-11-27 23:08:38 +01:00
Ali BARIN
a0f0db1dd4 refactor(docker-compose): incorporate standalone build 2022-11-27 01:39:08 +01:00
Ali BARIN
e455497596 refactor(docker): rework for release 2022-11-27 01:39:08 +01:00
Ali BARIN
bb99dd82ba chore: remove unused wait for postgres scripts 2022-11-27 01:39:07 +01:00
Ömer Faruk Aydın
48ccddb555 Merge pull request #744 from automatisch/extend-http-error
feat(http-error): add response
2022-11-25 20:01:25 +01:00
Ali BARIN
23f56e4deb feat(http-error): add response 2022-11-25 00:49:40 +01:00
Pierre Maurice Schwang
ef27c9348b feat: stripe triggers (payout + transaction) 2022-11-24 23:23:22 +01:00
Ali BARIN
cb664b563d Merge pull request #739 from ksurl/docker-alpine
chore: switch to alpine based image
2022-11-23 00:14:20 +01:00
ksurl
53f38c583a add openssl package 2022-11-22 14:31:21 -08:00
ksurl
12622e2045 add dos2unix prestage 2022-11-22 10:56:04 -08:00
ksurl
36dfedbaab Merge remote-tracking branch 'upstream/main' into docker-alpine 2022-11-22 10:48:28 -08:00
Ali BARIN
d3a059d759 fix(docker-compose): add dos2unix 2022-11-22 17:36:55 +01:00
ksurl
9ba1351a3b refactor: use healthcheck for db, remove psql client from image (#737)
* refactor: use healthcheck for db, remove postgresql client from main image

* fix: redis dependency condition

* cleanup entrypoint.sh

* fix healthcheck command
2022-11-22 01:18:05 +01:00
ksurl
399f8ed1db switch to alpine based image 2022-11-21 16:14:06 -08:00
ksurl
d36b5e8091 fix(docker-compose): add compose image tag (#734)
* fix: update compose image tag

* fix: update image org name
2022-11-22 00:01:46 +01:00
Ömer Faruk Aydın
fc616e818a Merge pull request #736 from automatisch/docs/refresh-token
docs: Add OAuth with refresh token to example integrations
2022-11-21 23:23:35 +01:00
Faruk AYDIN
edd438c37f docs: Add OAuth with refresh token to example integrations 2022-11-21 23:15:49 +01:00
ksurl
92e5ae0ebd refactor(docker-compose): combine dockerfiles with entrypoint (#727)
* combine worker and main image, support encryption key, app secret key env, and postgres pw

* Update docker/entrypoint.sh

fix env file path

Co-authored-by: Ali BARIN <ali.barin53@gmail.com>

* add build tag, move compose to root

* add volumes

* style: remove trailing indent

* refactor(docker-compose): empty encryption env. vars

* docs(docker-compose): update compose folder

* refactor(docker-compose): remove host network

* fix(docker-compose): add environment variable keys

Co-authored-by: Ali BARIN <ali.barin53@gmail.com>
2022-11-21 20:26:44 +01:00
Faruk AYDIN
d3e13c30a6 chore: Make isRefreshTokenRequested optional for IAuth 2022-11-21 09:43:04 +01:00
Faruk AYDIN
6bf0e799a1 feat: Add isRefreshTokenRequested condition to auth in global variable 2022-11-21 09:43:04 +01:00
Ali BARIN
166dda4a4b feat(salesforce): implement refresh token logic 2022-11-21 09:43:04 +01:00
Ali BARIN
901605fd9c feat: add refresh token capability 2022-11-21 09:43:04 +01:00
dependabot[bot]
e3e8f570f8 chore(deps): bump loader-utils from 1.4.1 to 1.4.2
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.1 to 1.4.2.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.2/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v1.4.1...v1.4.2)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-19 15:59:50 +01:00
Ali BARIN
6fdfdefe5c fix: use actual DB name in wait for postgres script 2022-11-19 13:37:36 +01:00
Faruk AYDIN
5cc1afa6de docs: Add explanation of automatic error handling for http client 2022-11-19 13:37:23 +01:00
Ali BARIN
4398570583 feat: show doc info in auth dialog in editor 2022-11-14 18:23:59 +01:00
Ömer Faruk Aydın
2e5b354f49 Merge pull request #716 from automatisch/plausible-docs
feat(docs): add plausible
2022-11-13 22:55:50 +01:00
Ali BARIN
11f96e8039 feat(docs): add plausible 2022-11-13 22:15:19 +01:00
Ömer Faruk Aydın
7ae1a079ea Merge pull request #715 from automatisch/chore/docker-compose-project-name
chore: Pass project name option to docker compose
2022-11-13 21:00:02 +01:00
Faruk AYDIN
b6d2624c4a chore: Pass project name option to docker compose 2022-11-13 20:54:31 +01:00
Ömer Faruk Aydın
51d6362863 Merge pull request #714 from automatisch/docker-compose-project-name
chore: add project name in docker compose
2022-11-13 16:30:13 +01:00
Ali BARIN
e30bfe67ed chore: add project name in docker compose 2022-11-13 16:19:11 +01:00
Faruk AYDIN
c5bcebad9a docs: Explain postgres user for db creation 2022-11-13 00:50:08 +01:00
Ömer Faruk Aydın
5deead7f94 Merge pull request #712 from automatisch/release/0.2.0
Release v0.2.0
2022-11-12 16:34:22 +01:00
Faruk AYDIN
2cff284122 chore: Update cli version in dockerfiles 2022-11-12 16:26:54 +01:00
134 changed files with 1922 additions and 221 deletions

View File

@@ -27,13 +27,13 @@ The official documentation can be found here: [https://automatisch.io/docs](http
git clone git@github.com:automatisch/automatisch.git git clone git@github.com:automatisch/automatisch.git
# Go to the repository folder # Go to the repository folder
cd automatisch/docker/compose cd automatisch
# Start # Start
docker compose 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.
## Community Links ## Community Links

70
docker-compose.yml Normal file
View File

@@ -0,0 +1,70 @@
version: '3.9'
services:
main:
build:
context: ./docker
dockerfile: Dockerfile.compose
entrypoint: /compose-entrypoint.sh
ports:
- '3000:3000'
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
environment:
- HOST=localhost
- PROTOCOL=http
- PORT=3000
- APP_ENV=production
- REDIS_HOST=redis
- POSTGRES_HOST=postgres
- POSTGRES_DATABASE=automatisch
- POSTGRES_USERNAME=automatisch_user
- POSTGRES_PASSWORD=automatisch_password
- ENCRYPTION_KEY
- WEBHOOK_SECRET_KEY
- APP_SECRET_KEY
volumes:
- automatisch_storage:/automatisch/storage
worker:
build:
context: ./docker
dockerfile: Dockerfile.compose
entrypoint: /compose-entrypoint.sh
depends_on:
- main
environment:
- APP_ENV=production
- REDIS_HOST=redis
- POSTGRES_HOST=postgres
- POSTGRES_DATABASE=automatisch
- POSTGRES_USERNAME=automatisch_user
- POSTGRES_PASSWORD=automatisch_password
- ENCRYPTION_KEY
- WEBHOOK_SECRET_KEY
- APP_SECRET_KEY
- WORKER=true
volumes:
- automatisch_storage:/automatisch/storage
postgres:
image: 'postgres:14.5'
environment:
- POSTGRES_DB=automatisch
- POSTGRES_USER=automatisch_user
- POSTGRES_PASSWORD=automatisch_password
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}']
interval: 10s
timeout: 5s
retries: 5
redis:
image: 'redis:7.0.4'
volumes:
- redis_data:/data
volumes:
automatisch_storage:
postgres_data:
redis_data:

10
docker/Dockerfile Normal file
View File

@@ -0,0 +1,10 @@
# syntax=docker/dockerfile:1
FROM node:16-alpine
WORKDIR /automatisch
COPY ./entrypoint.sh /entrypoint.sh
RUN yarn global add @automatisch/cli@0.2.0
EXPOSE 3000
ENTRYPOINT ["sh", "/entrypoint.sh"]

11
docker/Dockerfile.compose Normal file
View File

@@ -0,0 +1,11 @@
# syntax=docker/dockerfile:1
FROM automatischio/automatisch:0.2.0
WORKDIR /automatisch
RUN apk add --no-cache openssl dos2unix
COPY ./compose-entrypoint.sh /compose-entrypoint.sh
RUN dos2unix /compose-entrypoint.sh
EXPOSE 3000
ENTRYPOINT ["sh", "/compose-entrypoint.sh"]

26
docker/compose-entrypoint.sh Executable file
View File

@@ -0,0 +1,26 @@
#!/bin/sh
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

View File

@@ -1,46 +0,0 @@
version: '3.9'
services:
main:
build:
context: ../images/main
network: host
ports:
- '3000:3000'
depends_on:
- postgres
- redis
environment:
- HOST=localhost
- PROTOCOL=http
- PORT=3000
- APP_ENV=production
- REDIS_HOST=redis
- POSTGRES_HOST=postgres
- POSTGRES_DATABASE=automatisch
- POSTGRES_USERNAME=automatisch_user
volumes:
- automatisch_storage:/automatisch/storage
worker:
build:
context: ../images/worker
network: host
depends_on:
- main
environment:
- APP_ENV=production
- REDIS_HOST=redis
- POSTGRES_HOST=postgres
- POSTGRES_DATABASE=automatisch
- POSTGRES_USERNAME=automatisch_user
volumes:
- automatisch_storage:/automatisch/storage
postgres:
image: 'postgres:14.5'
environment:
POSTGRES_HOST_AUTH_METHOD: trust
POSTGRES_DB: automatisch
POSTGRES_USER: automatisch_user
redis:
image: 'redis:7.0.4'
volumes:
automatisch_storage:

9
docker/entrypoint.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/sh
set -e
if [ -n "$WORKER" ]; then
automatisch start-worker
else
automatisch start
fi

View File

@@ -1,15 +0,0 @@
# syntax=docker/dockerfile:1
FROM node:16
WORKDIR /automatisch
RUN apt-get update && apt-get install -y postgresql-client
COPY ./wait-for-postgres.sh /automatisch/wait-for-postgres.sh
RUN mkdir -p /automatisch/storage
RUN touch /automatisch/storage/.env
RUN echo "ENCRYPTION_KEY=$(openssl rand -base64 36)" >> /automatisch/storage/.env
RUN echo "APP_SECRET_KEY=$(openssl rand -base64 36)" >> /automatisch/storage/.env
RUN yarn global add @automatisch/cli@0.1.5
EXPOSE 3000
CMD sh /automatisch/wait-for-postgres.sh automatisch start --env-file=/automatisch/storage/.env

View File

@@ -1,11 +0,0 @@
#!/bin/sh
set -e
until psql -h "$POSTGRES_HOST" -U "$POSTGRES_USERNAME" -d "$POSTGRES_HOST" -c '\q'; do
>&2 echo "Waiting for Postgres to be ready..."
sleep 1
done
>&2 echo "Postgres is up - executing command"
exec "$@"

View File

@@ -1,10 +0,0 @@
# syntax=docker/dockerfile:1
FROM node:16
WORKDIR /automatisch
RUN apt-get update && apt-get install -y postgresql-client
COPY ./wait-for-postgres.sh /automatisch/wait-for-postgres.sh
RUN yarn global add @automatisch/cli@0.1.5
CMD sh /automatisch/wait-for-postgres.sh automatisch start-worker --env-file /automatisch/storage/.env

View File

@@ -1,11 +0,0 @@
#!/bin/sh
set -e
until psql -h "$POSTGRES_HOST" -U "$POSTGRES_USERNAME" -d "$POSTGRES_HOST" -c '\q'; do
>&2 echo "Waiting for Postgres to be ready..."
sleep 1
done
>&2 echo "Postgres is up - executing command"
exec "$@"

View File

@@ -2,7 +2,7 @@
"packages": [ "packages": [
"packages/*" "packages/*"
], ],
"version": "0.2.0", "version": "0.3.0",
"npmClient": "yarn", "npmClient": "yarn",
"useWorkspaces": true, "useWorkspaces": true,
"command": { "command": {

View File

@@ -2,6 +2,7 @@ HOST=localhost
PROTOCOL=http PROTOCOL=http
PORT=3000 PORT=3000
WEB_APP_URL=http://localhost:3001 WEB_APP_URL=http://localhost:3001
WEBHOOK_URL=http://localhost:3000
APP_ENV=development APP_ENV=development
POSTGRES_DATABASE=automatisch_development POSTGRES_DATABASE=automatisch_development
POSTGRES_PORT=5432 POSTGRES_PORT=5432
@@ -10,8 +11,12 @@ POSTGRES_USERNAME=automatish_development_user
POSTGRES_PASSWORD= POSTGRES_PASSWORD=
POSTGRES_ENABLE_SSL=false POSTGRES_ENABLE_SSL=false
ENCRYPTION_KEY=sample-encryption-key ENCRYPTION_KEY=sample-encryption-key
WEBHOOK_SECRET_KEY=sample-webhook-key
APP_SECRET_KEY=sample-app-secret-key APP_SECRET_KEY=sample-app-secret-key
REDIS_PORT=6379 REDIS_PORT=6379
REDIS_HOST=127.0.0.1 REDIS_HOST=127.0.0.1
REDIS_USERNAME=redis_username
REDIS_PASSWORD=redis_password
REDIS_TLS=true
ENABLE_BULLMQ_DASHBOARD=false ENABLE_BULLMQ_DASHBOARD=false
SERVE_WEB_APP_SEPARATELY=true SERVE_WEB_APP_SEPARATELY=true

View File

@@ -1,6 +1,6 @@
{ {
"name": "@automatisch/backend", "name": "@automatisch/backend",
"version": "0.2.0", "version": "0.3.0",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.", "description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
"scripts": { "scripts": {
@@ -22,7 +22,7 @@
"prebuild": "rm -rf ./dist" "prebuild": "rm -rf ./dist"
}, },
"dependencies": { "dependencies": {
"@automatisch/web": "^0.2.0", "@automatisch/web": "^0.3.0",
"@bull-board/express": "^3.10.1", "@bull-board/express": "^3.10.1",
"@graphql-tools/graphql-file-loader": "^7.3.4", "@graphql-tools/graphql-file-loader": "^7.3.4",
"@graphql-tools/load": "^7.5.2", "@graphql-tools/load": "^7.5.2",
@@ -93,7 +93,7 @@
"url": "https://github.com/automatisch/automatisch/issues" "url": "https://github.com/automatisch/automatisch/issues"
}, },
"devDependencies": { "devDependencies": {
"@automatisch/types": "^0.2.0", "@automatisch/types": "^0.3.0",
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.0",
"@types/bull": "^3.15.8", "@types/bull": "^3.15.8",
"@types/cors": "^2.8.12", "@types/cors": "^2.8.12",

View File

@@ -1,8 +1,7 @@
import createError from 'http-errors'; import createError from 'http-errors';
import express, { Request, Response, NextFunction } from 'express'; import express from 'express';
import cors from 'cors'; import cors from 'cors';
import corsOptions from './config/cors-options'; import corsOptions from './config/cors-options';
import graphQLInstance from './helpers/graphql-instance';
import morgan from './helpers/morgan'; import morgan from './helpers/morgan';
import appAssetsHandler from './helpers/app-assets-handler'; import appAssetsHandler from './helpers/app-assets-handler';
import webUIHandler from './helpers/web-ui-handler'; import webUIHandler from './helpers/web-ui-handler';
@@ -13,6 +12,8 @@ import {
serverAdapter, serverAdapter,
} from './helpers/create-bull-board-handler'; } from './helpers/create-bull-board-handler';
import injectBullBoardHandler from './helpers/inject-bull-board-handler'; import injectBullBoardHandler from './helpers/inject-bull-board-handler';
import router from './routes';
import { IRequest } from '@automatisch/types';
createBullBoardHandler(serverAdapter); createBullBoardHandler(serverAdapter);
@@ -23,15 +24,21 @@ injectBullBoardHandler(app, serverAdapter);
appAssetsHandler(app); appAssetsHandler(app);
app.use(morgan); app.use(morgan);
app.use(express.json()); app.use(
express.json({
verify: (req, res, buf) => {
(req as IRequest).rawBody = buf;
},
})
);
app.use(express.urlencoded({ extended: false })); app.use(express.urlencoded({ extended: false }));
app.use(cors(corsOptions)); app.use(cors(corsOptions));
app.use('/graphql', graphQLInstance); app.use('/', router);
webUIHandler(app); webUIHandler(app);
// catch 404 and forward to error handler // catch 404 and forward to error handler
app.use(function (req: Request, res: Response, next: NextFunction) { app.use(function (req, res, next) {
next(createError(404)); next(createError(404));
}); });

View File

@@ -0,0 +1,3 @@
import sendMessage from './send-message';
export default [sendMessage];

View 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,
});
},
});

View 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

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

View File

@@ -0,0 +1,9 @@
import { IGlobalVariable } from '@automatisch/types';
import verifyCredentials from './verify-credentials';
const isStillVerified = async ($: IGlobalVariable) => {
await verifyCredentials($);
return true;
};
export default isStillVerified;

View File

@@ -0,0 +1,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;

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

View File

View 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,
});

View File

@@ -1,6 +1,7 @@
import generateAuthUrl from './generate-auth-url'; import generateAuthUrl from './generate-auth-url';
import verifyCredentials from './verify-credentials'; import verifyCredentials from './verify-credentials';
import isStillVerified from './is-still-verified'; import isStillVerified from './is-still-verified';
import refreshToken from './refresh-token';
export default { export default {
fields: [ fields: [
@@ -61,6 +62,7 @@ export default {
}, },
], ],
refreshToken,
generateAuthUrl, generateAuthUrl,
verifyCredentials, verifyCredentials,
isStillVerified, isStillVerified,

View File

@@ -0,0 +1,25 @@
import { IGlobalVariable } from '@automatisch/types';
import qs from 'querystring';
const refreshToken = async ($: IGlobalVariable) => {
const searchParams = qs.stringify({
grant_type: 'refresh_token',
client_id: $.auth.data.consumerKey as string,
client_secret: $.auth.data.consumerSecret as string,
refresh_token: $.auth.data.refreshToken as string,
});
const { data } = await $.http.post(
`${$.auth.data.oauth2Url}/token?${searchParams}`
);
await $.auth.set({
accessToken: data.access_token,
tokenType: data.token_type,
idToken: data.id_token,
instanceUrl: data.instance_url,
signature: data.signature,
});
};
export default refreshToken;

View File

@@ -20,7 +20,9 @@ const verifyCredentials = async ($: IGlobalVariable) => {
await $.auth.set({ await $.auth.set({
accessToken: data.access_token, accessToken: data.access_token,
refreshToken: data.refresh_token,
tokenType: data.token_type, tokenType: data.token_type,
idToken: data.id_token,
instanceUrl: data.instance_url, instanceUrl: data.instance_url,
signature: data.signature, signature: data.signature,
userId: data.id, userId: data.id,

View File

@@ -0,0 +1,10 @@
<svg id="svg" version="1.1" xmlns="http://www.w3.org/2000/svg" width="400" height="400" viewBox="0, 0, 400,400">
<g id="svgg">
<path id="path0"
d="M198.800 76.002 C 192.960 76.301,187.099 76.918,183.400 77.623 C 177.677 78.714,175.057 79.288,171.600 80.207 C 169.510 80.763,167.440 81.382,167.000 81.583 C 166.560 81.783,165.030 82.342,163.600 82.824 C 142.177 90.045,124.260 107.805,117.820 128.200 C 113.910 140.585,112.933 156.448,115.418 167.200 C 116.306 171.043,116.577 172.064,117.276 174.200 C 117.708 175.520,118.588 177.733,119.231 179.118 C 119.874 180.503,120.400 181.708,120.400 181.797 C 120.400 181.885,121.061 183.092,121.869 184.479 C 127.040 193.356,135.581 201.605,146.011 207.795 C 149.302 209.748,159.236 214.800,159.785 214.800 C 159.892 214.800,161.109 215.310,162.490 215.933 C 165.453 217.270,173.763 220.551,179.400 222.609 C 195.215 228.384,198.386 229.722,205.534 233.636 C 220.392 241.770,219.950 260.095,204.800 264.076 C 199.139 265.564,187.804 266.135,181.833 265.232 C 180.495 265.030,177.870 264.655,176.000 264.398 C 174.130 264.142,172.060 263.788,171.400 263.612 C 170.740 263.436,169.120 263.065,167.800 262.789 C 154.801 260.064,141.875 255.414,126.486 247.928 C 122.463 245.972,118.998 244.478,118.786 244.609 C 118.159 244.996,118.271 308.079,118.900 308.899 C 119.725 309.975,133.794 315.264,142.000 317.584 C 142.990 317.864,144.250 318.235,144.800 318.408 C 145.350 318.582,146.610 318.911,147.600 319.140 C 148.590 319.369,150.930 319.912,152.800 320.347 C 154.670 320.782,157.370 321.348,158.800 321.604 C 160.230 321.860,162.300 322.234,163.400 322.435 C 177.558 325.023,201.685 325.201,214.900 322.815 C 216.165 322.587,218.145 322.227,219.300 322.016 C 225.011 320.971,235.080 317.946,239.600 315.918 C 240.590 315.474,242.390 314.665,243.600 314.121 C 250.325 311.097,259.619 304.705,265.005 299.400 C 285.928 278.789,291.480 241.782,277.725 214.602 C 271.038 201.387,255.237 188.197,238.000 181.440 C 237.010 181.052,235.660 180.488,235.000 180.187 C 232.351 178.979,213.304 172.081,208.000 170.409 C 187.266 163.873,178.497 154.218,183.708 143.661 C 190.425 130.050,233.857 133.347,267.600 150.029 C 270.130 151.280,272.335 152.225,272.500 152.128 C 272.665 152.032,272.800 137.647,272.800 120.162 L 272.800 88.372 270.900 87.554 C 268.928 86.705,259.102 83.332,257.000 82.781 C 256.340 82.609,254.075 82.016,251.967 81.464 C 240.954 78.580,228.764 76.709,217.800 76.220 C 207.102 75.743,204.456 75.712,198.800 76.002 "
stroke="none" fill="#ffffff" fill-rule="evenodd"/>
<path id="path1"
d="M0.000 200.000 L 0.000 400.000 200.000 400.000 L 400.000 400.000 400.000 200.000 L 400.000 0.000 200.000 0.000 L 0.000 0.000 0.000 200.000 M217.800 76.220 C 228.764 76.709,240.954 78.580,251.967 81.464 C 254.075 82.016,256.340 82.609,257.000 82.781 C 259.102 83.332,268.928 86.705,270.900 87.554 L 272.800 88.372 272.800 120.162 C 272.800 137.647,272.665 152.032,272.500 152.128 C 272.335 152.225,270.130 151.280,267.600 150.029 C 233.857 133.347,190.425 130.050,183.708 143.661 C 178.497 154.218,187.266 163.873,208.000 170.409 C 213.304 172.081,232.351 178.979,235.000 180.187 C 235.660 180.488,237.010 181.052,238.000 181.440 C 255.237 188.197,271.038 201.387,277.725 214.602 C 291.480 241.782,285.928 278.789,265.005 299.400 C 259.619 304.705,250.325 311.097,243.600 314.121 C 242.390 314.665,240.590 315.474,239.600 315.918 C 235.080 317.946,225.011 320.971,219.300 322.016 C 218.145 322.227,216.165 322.587,214.900 322.815 C 201.685 325.201,177.558 325.023,163.400 322.435 C 162.300 322.234,160.230 321.860,158.800 321.604 C 157.370 321.348,154.670 320.782,152.800 320.347 C 150.930 319.912,148.590 319.369,147.600 319.140 C 146.610 318.911,145.350 318.582,144.800 318.408 C 144.250 318.235,142.990 317.864,142.000 317.584 C 133.794 315.264,119.725 309.975,118.900 308.899 C 118.271 308.079,118.159 244.996,118.786 244.609 C 118.998 244.478,122.463 245.972,126.486 247.928 C 141.875 255.414,154.801 260.064,167.800 262.789 C 169.120 263.065,170.740 263.436,171.400 263.612 C 172.060 263.788,174.130 264.142,176.000 264.398 C 177.870 264.655,180.495 265.030,181.833 265.232 C 187.804 266.135,199.139 265.564,204.800 264.076 C 219.950 260.095,220.392 241.770,205.534 233.636 C 198.386 229.722,195.215 228.384,179.400 222.609 C 173.763 220.551,165.453 217.270,162.490 215.933 C 161.109 215.310,159.892 214.800,159.785 214.800 C 159.236 214.800,149.302 209.748,146.011 207.795 C 135.581 201.605,127.040 193.356,121.869 184.479 C 121.061 183.092,120.400 181.885,120.400 181.797 C 120.400 181.708,119.874 180.503,119.231 179.118 C 118.588 177.733,117.708 175.520,117.276 174.200 C 116.577 172.064,116.306 171.043,115.418 167.200 C 112.933 156.448,113.910 140.585,117.820 128.200 C 124.260 107.805,142.177 90.045,163.600 82.824 C 165.030 82.342,166.560 81.783,167.000 81.583 C 167.440 81.382,169.510 80.763,171.600 80.207 C 175.057 79.288,177.677 78.714,183.400 77.623 C 187.099 76.918,192.960 76.301,198.800 76.002 C 204.456 75.712,207.102 75.743,217.800 76.220 "
stroke="none" fill="#635bff" fill-rule="evenodd"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -0,0 +1,31 @@
import verifyCredentials from "./verify-credentials";
import isStillVerified from "./is-still-verified";
export default {
fields: [
{
key: 'secretKey',
label: 'Secret Key',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
{
key: 'displayName',
label: 'Account Name',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: 'The display name that identifies this stripe connection - most likely the associated account name',
clickToCopy: false,
},
],
verifyCredentials,
isStillVerified
};

View File

@@ -0,0 +1,9 @@
import { IGlobalVariable } from '@automatisch/types';
import verifyCredentials from "./verify-credentials";
const isStillVerified = async ($: IGlobalVariable) => {
await verifyCredentials($);
return true;
};
export default isStillVerified;

View File

@@ -0,0 +1,12 @@
import { IGlobalVariable } from '@automatisch/types';
const verifyCredentials = async ($: IGlobalVariable) => {
await $.http.get(
`/v1/events`,
);
await $.auth.set({
screenName: $.auth.data?.displayName,
});
};
export default verifyCredentials;

View File

@@ -0,0 +1,8 @@
import {TBeforeRequest} from "@automatisch/types";
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
requestConfig.headers['Authorization'] = `Bearer ${$.auth.data?.secretKey}`
return requestConfig
}
export default addAuthHeader;

View File

View File

@@ -0,0 +1,19 @@
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: 'Stripe',
key: 'stripe',
iconUrl: '{BASE_URL}/apps/stripe/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/stripe/connection',
supportsConnections: true,
baseUrl: 'https://stripe.com',
apiBaseUrl: 'https://api.stripe.com',
primaryColor: '635bff',
beforeRequest: [addAuthHeader],
auth,
triggers,
actions: [],
})

View File

@@ -0,0 +1,32 @@
import {IGlobalVariable, IJSONObject} from "@automatisch/types";
import {URLSearchParams} from "url";
import {isEmpty, omitBy} from "lodash";
const getBalanceTransactions = async ($: IGlobalVariable) => {
let response;
let lastId = undefined;
do {
const params: IJSONObject = {
starting_after: lastId,
ending_before: $.flow.lastInternalId
}
const queryParams = new URLSearchParams(omitBy(params, isEmpty))
const requestPath = `/v1/balance_transactions${
queryParams.toString() ? `?${queryParams.toString()}` : ''
}`;
response = (await $.http.get(requestPath)).data
for (const entry of response.data) {
$.pushTriggerItem({
raw: entry,
meta: {
internalId: entry.id as string
}
})
lastId = entry.id
}
} while (response.has_more)
};
export default getBalanceTransactions;

View File

@@ -0,0 +1,12 @@
import defineTrigger from "../../../../helpers/define-trigger";
import getBalanceTransactions from "./get-balance-transactions";
export default defineTrigger({
name: 'New Balance Transactions',
key: 'newBalanceTransactions',
description: 'Triggers when a new transaction is processed (refund, payout, adjustment, ...)',
pollInterval: 15,
async run($) {
await getBalanceTransactions($)
}
})

View File

@@ -0,0 +1,4 @@
import balanceTransaction from "./balance-transaction";
import payouts from "./payouts";
export default [balanceTransaction, payouts];

View File

@@ -0,0 +1,32 @@
import {IGlobalVariable, IJSONObject} from "@automatisch/types";
import {URLSearchParams} from "url";
import {isEmpty, omitBy} from "lodash";
const getPayouts = async ($: IGlobalVariable) => {
let response;
let lastId = undefined;
do {
const params: IJSONObject = {
starting_after: lastId,
ending_before: $.flow.lastInternalId
}
const queryParams = new URLSearchParams(omitBy(params, isEmpty))
const requestPath = `/v1/payouts${
queryParams.toString() ? `?${queryParams.toString()}` : ''
}`;
response = (await $.http.get(requestPath)).data
for (const entry of response.data) {
$.pushTriggerItem({
raw: entry,
meta: {
internalId: entry.id as string
}
})
lastId = entry.id
}
} while (response.has_more)
};
export default getPayouts;

View File

@@ -0,0 +1,12 @@
import defineTrigger from "../../../../helpers/define-trigger";
import getPayouts from "./get-payouts";
export default defineTrigger({
name: 'New Payouts',
key: 'newPayouts',
description: 'Triggers when a payout (Stripe <-> Bank account) has been updated',
pollInterval: 15,
async run($) {
await getPayouts($)
}
})

View File

@@ -0,0 +1,3 @@
import sendMessage from './send-message';
export default [sendMessage];

View File

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

View 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

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

View File

@@ -0,0 +1,9 @@
import { IGlobalVariable } from '@automatisch/types';
import verifyCredentials from './verify-credentials';
const isStillVerified = async ($: IGlobalVariable) => {
await verifyCredentials($);
return true;
};
export default isStillVerified;

View File

@@ -0,0 +1,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;

View File

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

View File

View 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,
});

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 245 55" width="100">
<title>Typeform</title>
<path d="M158.248 38.0664C153.148 38.0664 150.529 33.8672 150.529 28.7095C150.529 23.5519 153.102 19.5809 158.248 19.5809C163.486 19.5809 165.968 23.7344 165.968 28.7095C165.922 33.9585 163.303 38.0664 158.248 38.0664ZM108.348 19.5809C111.334 19.5809 112.575 21.3154 112.575 22.8216C112.575 26.473 108.853 28.0705 101.088 28.2988C101.088 23.7801 103.661 19.5809 108.348 19.5809ZM75.2644 38.0664C70.4398 38.0664 68.418 34.1411 68.418 28.7095C68.418 23.3237 70.4857 19.5809 75.2644 19.5809C80.135 19.5809 82.4325 23.4606 82.4325 28.7095C82.4325 34.1867 80.0431 38.0664 75.2644 38.0664ZM34.4617 14.2407H26.9261L39.4242 42.9046C37.0349 48.1992 35.7483 49.5228 34.0941 49.5228C32.3481 49.5228 30.6939 48.1079 29.4992 46.8755L26.1909 51.2573C28.4424 53.6307 31.521 55 34.6915 55C38.6431 55 41.6757 52.7178 43.4218 48.61L57.8498 14.195H50.452L42.9164 34.7344L34.4617 14.2407ZM234.064 19.5809C237.74 19.5809 238.475 22.0913 238.475 27.9793V43.4523H245V23.917C245 16.888 240.175 13.8299 235.718 13.8299C231.767 13.8299 228.137 16.2946 226.115 20.8589C224.966 16.4772 221.29 13.8299 217.017 13.8299C213.295 13.8299 209.573 16.2033 207.552 20.6307V14.2407H201.027V43.4066H207.552V30.9461C207.552 24.6473 210.86 19.5809 215.363 19.5809C219.039 19.5809 219.728 22.0913 219.728 27.9793V43.4523H226.253L226.207 30.9461C226.207 24.6473 229.561 19.5809 234.064 19.5809ZM182 14.2407H178.696V43.4066H185.22V32.2241C185.22 25.1494 188.345 20.3568 192.986 20.3568C194.18 20.3568 195.191 20.4025 196.294 20.8589L197.305 13.9668C196.478 13.8755 195.743 13.7842 195.007 13.7842C190.413 13.7842 187.104 16.9336 185.174 20.9959V14.2407H182ZM158.248 13.7842C149.61 13.7842 143.774 20.3568 143.774 28.6639C143.774 37.2905 149.702 43.7261 158.248 43.7261C166.933 43.7261 172.86 37.1079 172.86 28.6639C172.814 20.3112 166.795 13.7842 158.248 13.7842ZM108.715 38.0664C105.315 38.0664 102.788 36.332 101.731 32.8174C110.369 32.4523 118.824 30.3527 118.824 22.7303C118.824 18.3485 114.505 13.8299 108.302 13.8299C99.939 13.8299 94.2873 20.6307 94.2873 28.7095C94.2873 37.1535 99.8471 43.7718 108.164 43.7718C113.953 43.7718 117.629 41.444 120.524 38.0664L117.354 33.7759C113.999 37.1992 111.932 38.0664 108.715 38.0664ZM76.551 13.7842C73.2427 13.7842 69.9344 15.7012 68.6478 18.7137V14.2407H62.123V54.8631H68.6478V40.3485C70.0263 42.4481 73.1967 43.8174 75.9996 43.8174C84.684 43.8174 89.2789 37.3817 89.2789 28.7095C89.2329 20.1286 84.7299 13.7842 76.551 13.7842ZM30.7858 2.55602H0V8.90042H12.0386V43.4066H18.885V8.90042H30.7858V2.55602ZM127.784 14.2407H123.878V20.083H127.784V43.4066H134.309V20.083H140.65V14.2407H134.309V9.67635C134.309 6.75519 135.504 5.75104 138.215 5.75104C139.225 5.75104 140.144 6.0249 141.385 6.43568L142.855 0.958506C141.661 0.273859 139.271 0 137.709 0C131.46 0 127.784 3.74274 127.784 10.2241V14.2407Z" fill="#262627"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,21 @@
import { IField, IGlobalVariable } from '@automatisch/types';
import { URLSearchParams } from 'url';
import authScope from '../common/auth-scope';
export default async function generateAuthUrl($: IGlobalVariable) {
const oauthRedirectUrl = $.app.auth.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
).value;
const searchParams = new URLSearchParams({
client_id: $.auth.data.clientId as string,
redirect_uri: oauthRedirectUrl as string,
scope: authScope.join(' '),
});
const url = `${$.app.apiBaseUrl}/oauth/authorize?${searchParams.toString()}`;
await $.auth.set({
url,
});
}

View File

@@ -0,0 +1,50 @@
import generateAuthUrl from './generate-auth-url';
import verifyCredentials from './verify-credentials';
import isStillVerified from './is-still-verified';
import refreshToken from './refresh-token';
import verifyWebhook from './verify-webhook';
export default {
fields: [
{
key: 'oAuthRedirectUrl',
label: 'OAuth Redirect URL',
type: 'string' as const,
required: true,
readOnly: true,
value: '{WEB_APP_URL}/app/typeform/connections/add',
placeholder: null,
description:
'When asked to input an OAuth callback or redirect URL in Typeform OAuth, enter the URL above.',
clickToCopy: true,
},
{
key: 'clientId',
label: 'Client ID',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
{
key: 'clientSecret',
label: 'Client Secret',
type: 'string' as const,
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
],
generateAuthUrl,
verifyCredentials,
isStillVerified,
refreshToken,
verifyWebhook,
};

View File

@@ -0,0 +1,9 @@
import { IGlobalVariable } from '@automatisch/types';
const isStillVerified = async ($: IGlobalVariable) => {
await $.http.get('/me');
return true;
};
export default isStillVerified;

View File

@@ -0,0 +1,24 @@
import { IGlobalVariable } from '@automatisch/types';
import { URLSearchParams } from 'url';
import authScope from '../common/auth-scope';
const refreshToken = async ($: IGlobalVariable) => {
const params = new URLSearchParams({
grant_type: 'refresh_token',
client_id: $.auth.data.clientId as string,
client_secret: $.auth.data.clientSecret as string,
refresh_token: $.auth.data.refreshToken as string,
scope: authScope.join(' '),
});
const { data } = await $.http.post('/oauth/token', params.toString());
await $.auth.set({
accessToken: data.access_token,
expiresIn: data.expires_in,
tokenType: data.token_type,
refreshToken: data.refresh_token,
});
};
export default refreshToken;

View File

@@ -0,0 +1,46 @@
import { IField, IGlobalVariable } from '@automatisch/types';
import { URLSearchParams } from 'url';
const verifyCredentials = async ($: IGlobalVariable) => {
const oauthRedirectUrl = $.app.auth.fields.find(
(field: IField) => field.key == 'oAuthRedirectUrl'
).value;
const params = new URLSearchParams({
grant_type: 'authorization_code',
code: $.auth.data.code as string,
client_id: $.auth.data.clientId as string,
client_secret: $.auth.data.clientSecret as string,
redirect_uri: oauthRedirectUrl as string,
});
const { data: verifiedCredentials } = await $.http.post(
'/oauth/token',
params.toString()
);
const {
access_token: accessToken,
expires_in: expiresIn,
token_type: tokenType,
refresh_token: refreshToken,
} = verifiedCredentials;
const { data: user } = await $.http.get('/me', {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
await $.auth.set({
accessToken,
expiresIn,
tokenType,
userId: user.user_id,
screenName: user.alias,
email: user.email,
refreshToken,
});
};
export default verifyCredentials;

View File

@@ -0,0 +1,20 @@
import crypto from 'crypto';
import { IGlobalVariable } from '@automatisch/types';
import appConfig from '../../../config/app';
const verifyWebhook = async ($: IGlobalVariable) => {
const signature = $.request.headers['typeform-signature'] as string;
const isValid = verifySignature(signature, $.request.rawBody.toString());
return isValid;
};
const verifySignature = function (receivedSignature: string, payload: string) {
const hash = crypto
.createHmac('sha256', appConfig.webhookSecretKey)
.update(payload)
.digest('base64');
return receivedSignature === `sha256=${hash}`;
};
export default verifyWebhook;

View File

@@ -0,0 +1,12 @@
import { TBeforeRequest } from '@automatisch/types';
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
if ($.auth.data?.accessToken) {
const authorizationHeader = `Bearer ${$.auth.data.accessToken}`;
requestConfig.headers.Authorization = authorizationHeader;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -0,0 +1,12 @@
const authScope: string[] = [
'forms:read',
'forms:write',
'webhooks:read',
'webhooks:write',
'responses:read',
'accounts:read',
'workspaces:read',
'offline',
];
export default authScope;

View File

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

View File

@@ -0,0 +1,25 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
export default {
name: 'List forms',
key: 'listForms',
async run($: IGlobalVariable) {
const forms: {
data: IJSONObject[];
} = {
data: [],
};
const response = await $.http.get('/forms');
forms.data = response.data.items.map((form: IJSONObject) => {
return {
value: form.id,
name: form.title,
};
});
return forms;
},
};

View File

View File

@@ -0,0 +1,20 @@
import defineApp from '../../helpers/define-app';
import addAuthHeader from './common/add-auth-header';
import auth from './auth';
import triggers from './triggers';
import dynamicData from './dynamic-data';
export default defineApp({
name: 'Typeform',
key: 'typeform',
iconUrl: '{BASE_URL}/apps/typeform/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/typeform/connection',
supportsConnections: true,
baseUrl: 'https://typeform.com',
apiBaseUrl: 'https://api.typeform.com',
primaryColor: '262627',
beforeRequest: [addAuthHeader],
auth,
triggers,
dynamicData,
});

View File

@@ -0,0 +1,3 @@
import newEntry from './new-entry';
export default [newEntry];

View File

@@ -0,0 +1,89 @@
import appConfig from '../../../../config/app';
import defineTrigger from '../../../../helpers/define-trigger';
export default defineTrigger({
name: 'New entry',
key: 'newEntry',
type: 'webhook',
description: 'Triggers when a new form is submitted.',
arguments: [
{
label: 'Form',
key: 'formId',
type: 'dropdown' as const,
required: true,
description: 'Pick a form to receive submissions.',
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listForms',
},
],
},
},
],
async testRun($) {
const { data: form } = await $.http.get(
`/forms/${$.step.parameters.formId}`
);
const { data: responses } = await $.http.get(
`/forms/${$.step.parameters.formId}/responses`
);
const lastResponse = responses.items[0];
if (!lastResponse) {
return;
}
const computedWebhookEvent = {
event_type: 'form_response',
form_response: {
form_id: form.id,
token: lastResponse.token,
landed_at: lastResponse.landed_at,
submitted_at: lastResponse.submitted_at,
definition: {
id: $.step.parameters.formId,
title: form.title,
fields: form?.fields,
},
answers: lastResponse.answers,
},
};
const dataItem = {
raw: computedWebhookEvent,
meta: {
internalId: computedWebhookEvent.form_response.token,
},
};
$.pushTriggerItem(dataItem);
},
async registerHook($) {
const subscriptionPayload = {
enabled: true,
url: $.webhookUrl,
secret: appConfig.webhookSecretKey,
};
await $.http.put(
`/forms/${$.step.parameters.formId}/webhooks/${$.flow.id}`,
subscriptionPayload
);
},
async unregisterHook($) {
await $.http.delete(
`/forms/${$.step.parameters.formId}/webhooks/${$.flow.id}`
);
},
});

View 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

View File

View 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,
});

View File

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

View File

@@ -0,0 +1,3 @@
import catchRawWebhook from './catch-raw-webhook';
export default [catchRawWebhook];

View File

@@ -6,6 +6,7 @@ type AppConfig = {
protocol: string; protocol: string;
port: string; port: string;
webAppUrl: string; webAppUrl: string;
webhookUrl: string;
appEnv: string; appEnv: string;
isDev: boolean; isDev: boolean;
postgresDatabase: string; postgresDatabase: string;
@@ -17,10 +18,14 @@ type AppConfig = {
postgresEnableSsl: boolean; postgresEnableSsl: boolean;
baseUrl: string; baseUrl: string;
encryptionKey: string; encryptionKey: string;
webhookSecretKey: string;
appSecretKey: string; appSecretKey: string;
serveWebAppSeparately: boolean; serveWebAppSeparately: boolean;
redisHost: string; redisHost: string;
redisPort: number; redisPort: number;
redisUsername: string;
redisPassword: string;
redisTls: boolean;
enableBullMQDashboard: boolean; enableBullMQDashboard: boolean;
bullMQDashboardUsername: string; bullMQDashboardUsername: string;
bullMQDashboardPassword: string; bullMQDashboardPassword: string;
@@ -34,6 +39,8 @@ const serveWebAppSeparately =
process.env.SERVE_WEB_APP_SEPARATELY === 'true' ? true : false; process.env.SERVE_WEB_APP_SEPARATELY === 'true' ? true : false;
let webAppUrl = `${protocol}://${host}:${port}`; let webAppUrl = `${protocol}://${host}:${port}`;
const webhookUrl = process.env.WEBHOOK_URL || webAppUrl;
if (serveWebAppSeparately) { if (serveWebAppSeparately) {
webAppUrl = process.env.WEB_APP_URL || 'http://localhost:3001'; webAppUrl = process.env.WEB_APP_URL || 'http://localhost:3001';
} }
@@ -55,18 +62,22 @@ const appConfig: AppConfig = {
postgresUsername: postgresUsername:
process.env.POSTGRES_USERNAME || 'automatisch_development_user', process.env.POSTGRES_USERNAME || 'automatisch_development_user',
postgresPassword: process.env.POSTGRES_PASSWORD, postgresPassword: process.env.POSTGRES_PASSWORD,
postgresEnableSsl: process.env.POSTGRES_ENABLE_SSL === 'true' ? true : false, postgresEnableSsl: process.env.POSTGRES_ENABLE_SSL === 'true',
encryptionKey: process.env.ENCRYPTION_KEY || '', encryptionKey: process.env.ENCRYPTION_KEY || '',
webhookSecretKey: process.env.WEBHOOK_SECRET_KEY || '',
appSecretKey: process.env.APP_SECRET_KEY || '', appSecretKey: process.env.APP_SECRET_KEY || '',
serveWebAppSeparately, serveWebAppSeparately,
redisHost: process.env.REDIS_HOST || '127.0.0.1', redisHost: process.env.REDIS_HOST || '127.0.0.1',
redisPort: parseInt(process.env.REDIS_PORT || '6379'), redisPort: parseInt(process.env.REDIS_PORT || '6379'),
enableBullMQDashboard: redisUsername: process.env.REDIS_USERNAME,
process.env.ENABLE_BULLMQ_DASHBOARD === 'true' ? true : false, redisPassword: process.env.REDIS_PASSWORD,
redisTls: process.env.REDIS_TLS === 'true',
enableBullMQDashboard: process.env.ENABLE_BULLMQ_DASHBOARD === 'true',
bullMQDashboardUsername: process.env.BULLMQ_DASHBOARD_USERNAME, bullMQDashboardUsername: process.env.BULLMQ_DASHBOARD_USERNAME,
bullMQDashboardPassword: process.env.BULLMQ_DASHBOARD_PASSWORD, bullMQDashboardPassword: process.env.BULLMQ_DASHBOARD_PASSWORD,
baseUrl, baseUrl,
webAppUrl, webAppUrl,
webhookUrl,
telemetryEnabled: process.env.TELEMETRY_ENABLED === 'false' ? false : true, telemetryEnabled: process.env.TELEMETRY_ENABLED === 'false' ? false : true,
}; };
@@ -74,4 +85,8 @@ if (!appConfig.encryptionKey) {
throw new Error('ENCRYPTION_KEY environment variable needs to be set!'); 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; export default appConfig;

View File

@@ -1,9 +1,26 @@
import appConfig from './app'; import appConfig from './app';
const redisConfig = { type TRedisConfig = {
host: string,
port: number,
username?: string,
password?: string,
tls?: Record<string, unknown>,
enableReadyCheck?: boolean,
enableOfflineQueue: boolean,
}
const redisConfig: TRedisConfig = {
host: appConfig.redisHost, host: appConfig.redisHost,
port: appConfig.redisPort, port: appConfig.redisPort,
username: appConfig.redisUsername,
password: appConfig.redisPassword,
enableOfflineQueue: false, enableOfflineQueue: false,
enableReadyCheck: true,
}; };
if (appConfig.redisTls) {
redisConfig.tls = {};
}
export default redisConfig; export default redisConfig;

View File

@@ -0,0 +1,93 @@
import { Response } from 'express';
import bcrypt from 'bcrypt';
import { IRequest, ITriggerItem } from '@automatisch/types';
import Flow from '../../models/flow';
import { processTrigger } from '../../services/trigger';
import actionQueue from '../../queues/action';
import globalVariable from '../../helpers/global-variable';
import { REMOVE_AFTER_30_DAYS_OR_150_JOBS, REMOVE_AFTER_7_DAYS_OR_50_JOBS } from '../../helpers/remove-job-configuration';
export default async (request: IRequest, response: Response) => {
const flow = await Flow.query()
.findById(request.params.flowId)
.throwIfNotFound();
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 (testRun && !isWebhookApp) {
return response.sendStatus(404);
}
if (triggerCommand.type !== 'webhook') {
return response.sendStatus(404);
}
if (app.auth?.verifyWebhook) {
const $ = await globalVariable({
flow,
connection: await triggerStep.$relatedQuery('connection'),
app,
step: triggerStep,
request,
});
const verified = await app.auth.verifyWebhook($);
if (!verified) {
return response.sendStatus(401);
}
}
// in case trigger type is 'webhook'
let payload = request.body;
// in case it's our built-in generic webhook trigger
if (isWebhookApp) {
payload = {
headers: request.headers,
body: request.body,
query: request.query,
}
}
const triggerItem: ITriggerItem = {
raw: payload,
meta: {
internalId: await bcrypt.hash(request.rawBody, 1),
},
};
const { flowId, executionId } = await processTrigger({
flowId: flow.id,
stepId: triggerStep.id,
triggerItem,
testRun
});
if (testRun) {
return response.sendStatus(200);
}
const nextStep = await triggerStep.getNextStep();
const jobName = `${executionId}-${nextStep.id}`;
const jobPayload = {
flowId,
executionId,
stepId: nextStep.id,
};
const jobOptions = {
removeOnComplete: REMOVE_AFTER_7_DAYS_OR_50_JOBS,
removeOnFail: REMOVE_AFTER_30_DAYS_OR_150_JOBS,
}
await actionQueue.add(jobName, jobPayload, jobOptions);
return response.sendStatus(200);
};

View File

@@ -1,12 +1,17 @@
import type { AxiosResponse, AxiosError } from 'axios';
import { IJSONObject } from '@automatisch/types'; import { IJSONObject } from '@automatisch/types';
import BaseError from './base'; import BaseError from './base';
export default class HttpError extends BaseError { export default class HttpError extends BaseError {
constructor(error: IJSONObject) { response: AxiosResponse;
constructor(error: AxiosError) {
const computedError = const computedError =
((error.response as IJSONObject)?.data as IJSONObject) || error.response?.data as IJSONObject ||
(error.message as string); error.message as string;
super(computedError); super(computedError);
this.response = error.response;
} }
} }

View File

@@ -1,5 +1,7 @@
import Context from '../../types/express/context'; import Context from '../../types/express/context';
import flowQueue from '../../queues/flow'; import flowQueue from '../../queues/flow';
import { REMOVE_AFTER_30_DAYS_OR_150_JOBS, REMOVE_AFTER_7_DAYS_OR_50_JOBS } from '../../helpers/remove-job-configuration';
import globalVariable from '../../helpers/global-variable';
type Params = { type Params = {
input: { input: {
@@ -38,26 +40,44 @@ const updateFlowStatus = async (
pattern: interval || EVERY_15_MINUTES_CRON, pattern: interval || EVERY_15_MINUTES_CRON,
}; };
if (flow.active) { if (trigger.type === 'webhook') {
flow = await flow.$query().patchAndFetch({ const $ = await globalVariable({
published_at: new Date().toISOString(), flow,
connection: await triggerStep.$relatedQuery('connection'),
app: await triggerStep.getApp(),
step: triggerStep,
testRun: false,
}); });
const jobName = `${JOB_NAME}-${flow.id}`; if (flow.active && trigger.registerHook) {
await trigger.registerHook($);
await flowQueue.add( } else if (!flow.active && trigger.unregisterHook) {
jobName, await trigger.unregisterHook($);
{ flowId: flow.id }, }
{
repeat: repeatOptions,
jobId: flow.id,
}
);
} else { } else {
const repeatableJobs = await flowQueue.getRepeatableJobs(); if (flow.active) {
const job = repeatableJobs.find((job) => job.id === flow.id); flow = await flow.$query().patchAndFetch({
published_at: new Date().toISOString(),
});
await flowQueue.removeRepeatableByKey(job.key); const jobName = `${JOB_NAME}-${flow.id}`;
await flowQueue.add(
jobName,
{ flowId: flow.id },
{
repeat: repeatOptions,
jobId: flow.id,
removeOnComplete: REMOVE_AFTER_7_DAYS_OR_50_JOBS,
removeOnFail: REMOVE_AFTER_30_DAYS_OR_150_JOBS
}
);
} else {
const repeatableJobs = await flowQueue.getRepeatableJobs();
const job = repeatableJobs.find((job) => job.id === flow.id);
await flowQueue.removeRepeatableByKey(job.key);
}
} }
return flow; return flow;

View File

@@ -342,6 +342,7 @@ type Step {
key: String key: String
appKey: String appKey: String
iconUrl: String iconUrl: String
webhookUrl: String
type: StepEnumType type: StepEnumType
parameters: JSONObject parameters: JSONObject
connection: Connection connection: Connection

View 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();
});

View File

@@ -3,12 +3,14 @@ import Connection from '../models/connection';
import Flow from '../models/flow'; import Flow from '../models/flow';
import Step from '../models/step'; import Step from '../models/step';
import Execution from '../models/execution'; import Execution from '../models/execution';
import appConfig from '../config/app';
import { import {
IJSONObject, IJSONObject,
IApp, IApp,
IGlobalVariable, IGlobalVariable,
ITriggerItem, ITriggerItem,
IActionItem, IActionItem,
IRequest,
} from '@automatisch/types'; } from '@automatisch/types';
import EarlyExitError from '../errors/early-exit'; import EarlyExitError from '../errors/early-exit';
@@ -19,12 +21,21 @@ type GlobalVariableOptions = {
step?: Step; step?: Step;
execution?: Execution; execution?: Execution;
testRun?: boolean; testRun?: boolean;
request?: IRequest;
}; };
const globalVariable = async ( const globalVariable = async (
options: GlobalVariableOptions options: GlobalVariableOptions
): Promise<IGlobalVariable> => { ): Promise<IGlobalVariable> => {
const { connection, app, flow, step, execution, testRun = false } = options; const {
connection,
app,
flow,
step,
execution,
request,
testRun = false,
} = options;
const lastInternalId = testRun ? undefined : await flow?.lastInternalId(); const lastInternalId = testRun ? undefined : await flow?.lastInternalId();
const nextStep = await step?.getNextStep(); const nextStep = await step?.getNextStep();
@@ -66,6 +77,7 @@ const globalVariable = async (
id: execution?.id, id: execution?.id,
testRun, testRun,
}, },
lastExecutionStep: (await step?.getLastExecutionStep())?.toJSON(),
triggerOutput: { triggerOutput: {
data: [], data: [],
}, },
@@ -95,12 +107,22 @@ const globalVariable = async (
}, },
}; };
if (request) {
$.request = request;
}
$.http = createHttpClient({ $.http = createHttpClient({
$, $,
baseURL: app.apiBaseUrl, baseURL: app.apiBaseUrl,
beforeRequest: app.beforeRequest, beforeRequest: app.beforeRequest,
}); });
if (flow) {
const webhookUrl = appConfig.webhookUrl + '/webhooks/' + flow.id;
$.webhookUrl = webhookUrl;
}
const lastInternalIds = const lastInternalIds =
testRun || (flow && step.isAction) ? [] : await flow?.lastInternalIds(2000); testRun || (flow && step.isAction) ? [] : await flow?.lastInternalIds(2000);

View File

@@ -1,12 +1,13 @@
import { graphqlHTTP } from 'express-graphql';
import logger from '../helpers/logger';
import { applyMiddleware } from 'graphql-middleware';
import authentication from '../helpers/authentication';
import { join } from 'path'; import { join } from 'path';
import { graphqlHTTP } from 'express-graphql';
import { loadSchemaSync } from '@graphql-tools/load'; import { loadSchemaSync } from '@graphql-tools/load';
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader'; import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
import { addResolversToSchema } from '@graphql-tools/schema'; import { addResolversToSchema } from '@graphql-tools/schema';
import { applyMiddleware } from 'graphql-middleware';
import logger from '../helpers/logger';
import authentication from '../helpers/authentication';
import resolvers from '../graphql/resolvers'; import resolvers from '../graphql/resolvers';
import HttpError from '../errors/http';
const schema = loadSchemaSync(join(__dirname, '../graphql/schema.graphql'), { const schema = loadSchemaSync(join(__dirname, '../graphql/schema.graphql'), {
loaders: [new GraphQLFileLoader()], loaders: [new GraphQLFileLoader()],
@@ -23,6 +24,10 @@ const graphQLInstance = graphqlHTTP({
customFormatErrorFn: (error) => { customFormatErrorFn: (error) => {
logger.error(error.path + ' : ' + error.message + '\n' + error.stack); logger.error(error.path + ' : ' + error.message + '\n' + error.stack);
if (error.originalError instanceof HttpError) {
delete (error.originalError as HttpError).response;
}
return error.originalError; return error.originalError;
}, },
}); });

View File

@@ -39,7 +39,25 @@ export default function createHttpClient({
instance.interceptors.response.use( instance.interceptors.response.use(
(response) => response, (response) => response,
(error) => { async (error) => {
const { config } = error;
const { status } = error.response;
if (
status === 401 &&
$.app.auth.refreshToken &&
!$.app.auth.isRefreshTokenRequested
) {
$.app.auth.isRefreshTokenRequested = true;
await $.app.auth.refreshToken($);
// retry the previous request before the expired token error
const newResponse = await instance.request(config);
$.app.auth.isRefreshTokenRequested = false;
return newResponse;
}
throw new HttpError(error); throw new HttpError(error);
} }
); );

View File

@@ -0,0 +1,10 @@
export const REMOVE_AFTER_30_DAYS_OR_150_JOBS = {
age: 30 * 24 * 3600,
count: 150,
};
export const REMOVE_AFTER_7_DAYS_OR_50_JOBS = {
age: 7 * 24 * 3600,
count: 50,
};

View File

@@ -1,10 +1,11 @@
import { URL } from 'node:url';
import { QueryContext, ModelOptions } from 'objection'; import { QueryContext, ModelOptions } from 'objection';
import type { IJSONObject, IStep } from '@automatisch/types';
import Base from './base'; import Base from './base';
import App from './app'; import App from './app';
import Flow from './flow'; import Flow from './flow';
import Connection from './connection'; import Connection from './connection';
import ExecutionStep from './execution-step'; import ExecutionStep from './execution-step';
import type { IJSONObject, IStep } from '@automatisch/types';
import Telemetry from '../helpers/telemetry'; import Telemetry from '../helpers/telemetry';
import appConfig from '../config/app'; import appConfig from '../config/app';
@@ -46,7 +47,7 @@ class Step extends Base {
}; };
static get virtualAttributes() { static get virtualAttributes() {
return ['iconUrl']; return ['iconUrl', 'webhookUrl'];
} }
static relationMappings = () => ({ static relationMappings = () => ({
@@ -82,6 +83,13 @@ class Step extends Base {
return `${appConfig.baseUrl}/apps/${this.appKey}/assets/favicon.svg`; 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) { async $afterInsert(queryContext: QueryContext) {
await super.$afterInsert(queryContext); await super.$afterInsert(queryContext);
Telemetry.stepCreated(this); Telemetry.stepCreated(this);
@@ -106,6 +114,14 @@ class Step extends Base {
return await App.findOneByKey(this.appKey); return await App.findOneByKey(this.appKey);
} }
async getLastExecutionStep() {
const lastExecutionStep = await this.$relatedQuery('executionSteps')
.orderBy('created_at', 'desc')
.first();
return lastExecutionStep;
}
async getNextStep() { async getNextStep() {
const flow = await this.$relatedQuery('flow'); const flow = await this.$relatedQuery('flow');

View File

@@ -0,0 +1,10 @@
import { Router } from 'express';
import graphQLInstance from '../helpers/graphql-instance';
import webhooksRouter from './webhooks';
const router = Router();
router.use('/graphql', graphQLInstance);
router.use('/webhooks', webhooksRouter);
export default router;

View File

@@ -0,0 +1,11 @@
import { Router } from 'express';
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;

View File

@@ -23,7 +23,11 @@ export const processFlow = async (options: ProcessFlowOptions) => {
}); });
try { try {
await triggerCommand.run($); if (triggerCommand.type === 'webhook' && !flow.active) {
await triggerCommand.testRun($);
} else {
await triggerCommand.run($);
}
} catch (error) { } catch (error) {
if (error instanceof EarlyExitError === false) { if (error instanceof EarlyExitError === false) {
if (error instanceof HttpError) { if (error instanceof HttpError) {

View File

@@ -1,4 +1,5 @@
import './config/orm'; import './config/orm';
import './helpers/check-worker-readiness';
import './workers/flow'; import './workers/flow';
import './workers/trigger'; import './workers/trigger';
import './workers/action'; import './workers/action';

View File

@@ -4,6 +4,7 @@ import logger from '../helpers/logger';
import Step from '../models/step'; import Step from '../models/step';
import actionQueue from '../queues/action'; import actionQueue from '../queues/action';
import { processAction } from '../services/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';
type JobData = { type JobData = {
flowId: string; flowId: string;
@@ -31,7 +32,12 @@ export const worker = new Worker(
stepId: nextStep.id, stepId: nextStep.id,
}; };
await actionQueue.add(jobName, jobPayload); const jobOptions = {
removeOnComplete: REMOVE_AFTER_7_DAYS_OR_50_JOBS,
removeOnFail: REMOVE_AFTER_30_DAYS_OR_150_JOBS,
}
await actionQueue.add(jobName, jobPayload, jobOptions);
}, },
{ connection: redisConfig } { connection: redisConfig }
); );
@@ -42,7 +48,7 @@ worker.on('completed', (job) => {
worker.on('failed', (job, err) => { worker.on('failed', (job, err) => {
logger.info( logger.info(
`JOB ID: ${job.id} - FLOW ID: ${job.data.flowId} has failed22 to start with ${err.message}` `JOB ID: ${job.id} - FLOW ID: ${job.data.flowId} has failed to start with ${err.message}`
); );
}); });

View File

@@ -4,6 +4,7 @@ import logger from '../helpers/logger';
import triggerQueue from '../queues/trigger'; import triggerQueue from '../queues/trigger';
import { processFlow } from '../services/flow'; import { processFlow } from '../services/flow';
import Flow from '../models/flow'; import Flow from '../models/flow';
import { REMOVE_AFTER_30_DAYS_OR_150_JOBS, REMOVE_AFTER_7_DAYS_OR_50_JOBS } from '../helpers/remove-job-configuration';
export const worker = new Worker( export const worker = new Worker(
'flow', 'flow',
@@ -17,6 +18,11 @@ export const worker = new Worker(
const reversedData = data.reverse(); const reversedData = data.reverse();
const jobOptions = {
removeOnComplete: REMOVE_AFTER_7_DAYS_OR_50_JOBS,
removeOnFail: REMOVE_AFTER_30_DAYS_OR_150_JOBS,
}
for (const triggerItem of reversedData) { for (const triggerItem of reversedData) {
const jobName = `${triggerStep.id}-${triggerItem.meta.internalId}`; const jobName = `${triggerStep.id}-${triggerItem.meta.internalId}`;
@@ -26,7 +32,7 @@ export const worker = new Worker(
triggerItem, triggerItem,
}; };
await triggerQueue.add(jobName, jobPayload); await triggerQueue.add(jobName, jobPayload, jobOptions);
} }
if (error) { if (error) {
@@ -38,7 +44,7 @@ export const worker = new Worker(
error, error,
}; };
await triggerQueue.add(jobName, jobPayload); await triggerQueue.add(jobName, jobPayload, jobOptions);
} }
}, },
{ connection: redisConfig } { connection: redisConfig }

View File

@@ -5,6 +5,7 @@ import { IJSONObject, ITriggerItem } from '@automatisch/types';
import actionQueue from '../queues/action'; import actionQueue from '../queues/action';
import Step from '../models/step'; import Step from '../models/step';
import { processTrigger } from '../services/trigger'; import { processTrigger } from '../services/trigger';
import { REMOVE_AFTER_30_DAYS_OR_150_JOBS, REMOVE_AFTER_7_DAYS_OR_50_JOBS } from '../helpers/remove-job-configuration';
type JobData = { type JobData = {
flowId: string; flowId: string;
@@ -32,7 +33,12 @@ export const worker = new Worker(
stepId: nextStep.id, stepId: nextStep.id,
}; };
await actionQueue.add(jobName, jobPayload); const jobOptions = {
removeOnComplete: REMOVE_AFTER_7_DAYS_OR_50_JOBS,
removeOnFail: REMOVE_AFTER_30_DAYS_OR_150_JOBS,
}
await actionQueue.add(jobName, jobPayload, jobOptions);
}, },
{ connection: redisConfig } { connection: redisConfig }
); );

View File

@@ -1,6 +1,6 @@
{ {
"name": "@automatisch/cli", "name": "@automatisch/cli",
"version": "0.2.0", "version": "0.3.0",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.", "description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
"contributors": [ "contributors": [
@@ -33,7 +33,7 @@
"version": "oclif readme && git add README.md" "version": "oclif readme && git add README.md"
}, },
"dependencies": { "dependencies": {
"@automatisch/backend": "^0.2.0", "@automatisch/backend": "^0.3.0",
"@oclif/core": "^1", "@oclif/core": "^1",
"@oclif/plugin-help": "^5", "@oclif/plugin-help": "^5",
"@oclif/plugin-plugins": "^2.0.1", "@oclif/plugin-plugins": "^2.0.1",

View File

@@ -6463,10 +6463,10 @@ decimal.js@^10.2.1:
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.1.tgz#be75eeac4a2281aace80c1a8753587c27ef053e7" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.1.tgz#be75eeac4a2281aace80c1a8753587c27ef053e7"
integrity sha512-F29o+vci4DodHYT9UrR5IEbfBw9pE5eSapIJdTqXK5+6hq+t8VRxwQyKlW2i+KDKFkkJQRvFyI/QXD83h8LyQw== integrity sha512-F29o+vci4DodHYT9UrR5IEbfBw9pE5eSapIJdTqXK5+6hq+t8VRxwQyKlW2i+KDKFkkJQRvFyI/QXD83h8LyQw==
decode-uri-component@^0.2.0: decode-uri-component@0.2.2, decode-uri-component@^0.2.0:
version "0.2.0" version "0.2.2"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og== integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==
decompress-response@^6.0.0: decompress-response@^6.0.0:
version "6.0.0" version "6.0.0"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@automatisch/docs", "name": "@automatisch/docs",
"version": "0.2.0", "version": "0.3.0",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.", "description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
"private": true, "private": true,

View File

@@ -35,6 +35,7 @@ export default defineConfig({
{ {
text: 'DeepL', text: 'DeepL',
collapsible: true, collapsible: true,
collapsed: true,
items: [ items: [
{ text: 'Actions', link: '/apps/deepl/actions' }, { text: 'Actions', link: '/apps/deepl/actions' },
{ text: 'Connection', link: '/apps/deepl/connection' }, { text: 'Connection', link: '/apps/deepl/connection' },
@@ -43,6 +44,7 @@ export default defineConfig({
{ {
text: 'Discord', text: 'Discord',
collapsible: true, collapsible: true,
collapsed: true,
items: [ items: [
{ text: 'Actions', link: '/apps/discord/actions' }, { text: 'Actions', link: '/apps/discord/actions' },
{ text: 'Connection', link: '/apps/discord/connection' }, { text: 'Connection', link: '/apps/discord/connection' },
@@ -51,6 +53,7 @@ export default defineConfig({
{ {
text: 'Flickr', text: 'Flickr',
collapsible: true, collapsible: true,
collapsed: true,
items: [ items: [
{ text: 'Triggers', link: '/apps/flickr/triggers' }, { text: 'Triggers', link: '/apps/flickr/triggers' },
{ text: 'Connection', link: '/apps/flickr/connection' }, { text: 'Connection', link: '/apps/flickr/connection' },
@@ -59,15 +62,26 @@ export default defineConfig({
{ {
text: 'Github', text: 'Github',
collapsible: true, collapsible: true,
collapsed: true,
items: [ items: [
{ text: 'Triggers', link: '/apps/github/triggers' }, { text: 'Triggers', link: '/apps/github/triggers' },
{ text: 'Actions', link: '/apps/github/actions' }, { text: 'Actions', link: '/apps/github/actions' },
{ text: 'Connection', link: '/apps/github/connection' }, { 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', text: 'RSS',
collapsible: true, collapsible: true,
collapsed: true,
items: [ items: [
{ text: 'Triggers', link: '/apps/rss/triggers' }, { text: 'Triggers', link: '/apps/rss/triggers' },
{ text: 'Connection', link: '/apps/rss/connection' }, { text: 'Connection', link: '/apps/rss/connection' },
@@ -76,6 +90,7 @@ export default defineConfig({
{ {
text: 'Salesforce', text: 'Salesforce',
collapsible: true, collapsible: true,
collapsed: true,
items: [ items: [
{ text: 'Triggers', link: '/apps/salesforce/triggers' }, { text: 'Triggers', link: '/apps/salesforce/triggers' },
{ text: 'Connection', link: '/apps/salesforce/connection' }, { text: 'Connection', link: '/apps/salesforce/connection' },
@@ -84,6 +99,7 @@ export default defineConfig({
{ {
text: 'Scheduler', text: 'Scheduler',
collapsible: true, collapsible: true,
collapsed: true,
items: [ items: [
{ text: 'Triggers', link: '/apps/scheduler/triggers' }, { text: 'Triggers', link: '/apps/scheduler/triggers' },
{ text: 'Connection', link: '/apps/scheduler/connection' }, { text: 'Connection', link: '/apps/scheduler/connection' },
@@ -92,6 +108,7 @@ export default defineConfig({
{ {
text: 'Slack', text: 'Slack',
collapsible: true, collapsible: true,
collapsed: true,
items: [ items: [
{ text: 'Actions', link: '/apps/slack/actions' }, { text: 'Actions', link: '/apps/slack/actions' },
{ text: 'Connection', link: '/apps/slack/connection' }, { text: 'Connection', link: '/apps/slack/connection' },
@@ -100,14 +117,34 @@ export default defineConfig({
{ {
text: 'SMTP', text: 'SMTP',
collapsible: true, collapsible: true,
collapsed: true,
items: [ items: [
{ text: 'Actions', link: '/apps/smtp/actions' }, { text: 'Actions', link: '/apps/smtp/actions' },
{ text: 'Connection', link: '/apps/smtp/connection' }, { text: 'Connection', link: '/apps/smtp/connection' },
], ],
}, },
{
text: 'Stripe',
collapsible: true,
collapsed: true,
items: [
{ text: 'Triggers', link: '/apps/stripe/triggers' },
{ 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', text: 'Twilio',
collapsible: true, collapsible: true,
collapsed: true,
items: [ items: [
{ text: 'Triggers', link: '/apps/twilio/triggers' }, { text: 'Triggers', link: '/apps/twilio/triggers' },
{ text: 'Actions', link: '/apps/twilio/actions' }, { text: 'Actions', link: '/apps/twilio/actions' },
@@ -117,12 +154,31 @@ export default defineConfig({
{ {
text: 'Twitter', text: 'Twitter',
collapsible: true, collapsible: true,
collapsed: true,
items: [ items: [
{ text: 'Triggers', link: '/apps/twitter/triggers' }, { text: 'Triggers', link: '/apps/twitter/triggers' },
{ text: 'Actions', link: '/apps/twitter/actions' }, { text: 'Actions', link: '/apps/twitter/actions' },
{ text: 'Connection', link: '/apps/twitter/connection' }, { text: 'Connection', link: '/apps/twitter/connection' },
], ],
}, },
{
text: 'Typeform',
collapsible: true,
collapsed: true,
items: [
{ text: 'Triggers', link: '/apps/typeform/triggers' },
{ 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' },
],
},
], ],
'/': [ '/': [
{ {
@@ -255,7 +311,7 @@ export default defineConfig({
// Added for logging purposes to check if there is something // Added for logging purposes to check if there is something
// wrong with the canonical URL in the deployment pipeline. // wrong with the canonical URL in the deployment pipeline.
console.log(''); console.log('');
console.log('File path : ', ctx.pageData.relativePath); console.log('File path: ', ctx.pageData.relativePath);
console.log('Canonical URL: ', canonicalUrl); console.log('Canonical URL: ', canonicalUrl);
return [ return [
@@ -266,6 +322,15 @@ export default defineConfig({
href: canonicalUrl, href: canonicalUrl,
}, },
], ],
[
'script',
{
defer: true,
'data-domain': 'automatisch.io',
'data-api': 'https://automatisch.io/data/api/event',
src: 'https://automatisch.io/data/js/script.js',
},
],
]; ];
}, },

View File

@@ -11,25 +11,29 @@ The default values for some environment variables might be different in our deve
::: :::
:::danger :::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 | | Variable Name | Type | Default Value | Description |
| --------------------------- | ------- | ------------------ | ----------------------------------- | | --------------------------- | ------- | ------------------ | --------------------------------------------- |
| `HOST` | string | `localhost` | HTTP Host | | `HOST` | string | `localhost` | HTTP Host |
| `PROTOCOL` | string | `http` | HTTP Protocol | | `PROTOCOL` | string | `http` | HTTP Protocol |
| `PORT` | string | `3000` | HTTP Port | | `PORT` | string | `3000` | HTTP Port |
| `APP_ENV` | string | `production` | Automatisch Environment | | `APP_ENV` | string | `production` | Automatisch Environment |
| `POSTGRES_DATABASE` | string | `automatisch` | Database Name | | `POSTGRES_DATABASE` | string | `automatisch` | Database Name |
| `POSTGRES_PORT` | number | `5432` | Database Port | | `POSTGRES_PORT` | number | `5432` | Database Port |
| `POSTGRES_HOST` | string | `postgres` | Database Host | | `POSTGRES_HOST` | string | `postgres` | Database Host |
| `POSTGRES_USERNAME` | string | `automatisch_user` | Database User | | `POSTGRES_USERNAME` | string | `automatisch_user` | Database User |
| `POSTGRES_PASSWORD` | string | | Password of Database User | | `POSTGRES_PASSWORD` | string | | Password of Database User |
| `ENCRYPTION_KEY` | string | | Encryption Key to store credentials | | `ENCRYPTION_KEY` | string | | Encryption Key to store credentials |
| `APP_SECRET_KEY` | string | | Secret Key to authenticate the user | | `WEBHOOK_SECRET_KEY` | string | | Webhook Secret Key to verify webhook requests |
| `REDIS_HOST` | string | `redis` | Redis Host | | `APP_SECRET_KEY` | string | | Secret Key to authenticate the user |
| `REDIS_PORT` | number | `6379` | Redis Port | | `REDIS_HOST` | string | `redis` | Redis Host |
| `TELEMETRY_ENABLED` | boolean | `true` | Enable/Disable Telemetry | | `REDIS_PORT` | number | `6379` | Redis Port |
| `ENABLE_BULLMQ_DASHBOARD` | boolean | `false` | Enable BullMQ Dashboard | | `REDIS_USERNAME` | string | `` | Redis Username |
| `BULLMQ_DASHBOARD_USERNAME` | string | | Username to login BullMQ Dashboard | | `REDIS_PASSWORD` | string | `` | Redis Password |
| `BULLMQ_DASHBOARD_PASSWORD` | string | | Password to login BullMQ Dashboard | | `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 |

View File

@@ -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. 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 :::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.
::: :::

View 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 />

View 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.

View File

@@ -0,0 +1,14 @@
# Stripe
:::info
This page explains the steps you need to follow to set up the Stripe connection in Automatisch. If any of the steps are outdated, please let us know!
:::
:::info
You are free to use the **Testing secret key** instead of the productive secret key as well.
:::
1. Go to the [Stripe Dashboard > Developer > API keys](https://dashboard.stripe.com/apikeys)
2. Click on **Reveal live key** in the table row **Secret key** and copy the now shown secret key
3. Paste the **Secret key** in the named field in Automatisch and assign a display name for the connection.
4. Congrats! You can start using the new Stripe connection!

View File

@@ -0,0 +1,18 @@
---
favicon: /favicons/stripe.svg
items:
- name: New Payouts
desc: Triggers when stripe sent a payout to a third-party bank account or vice versa.
org: Stripe Documentation
orgLink: https://stripe.com/docs/api/payouts/object
- name: New Balance Transactions
desc: Triggers when a fund has been moved through your stripe account.
org: Stripe Documentation
orgLink: https://stripe.com/docs/api/balance_transactions/object
---
<script setup>
import CustomListing from '../../components/CustomListing.vue'
</script>
<CustomListing />

View 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 />

View 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.

View File

@@ -0,0 +1,14 @@
# Typeform
:::info
This page explains the steps you need to follow to set up the Typeform connection in Automatisch. If any of the steps are outdated, please let us know!
:::
1. Go to the [link](https://admin.typeform.com/user) and click on **Developer apps** in the sidebar.
2. Click on the **Register a new app** button.
3. Fill **App name** and **App website**, and **Developer email** fields.
4. Copy **OAuth Redirect URL** from Automatisch to **Redirect URI(s)** field on the Typeform page.
5. Click on the **Register app** button.
6. Copy **Client ID** and **Client Secret** values from Typeform to Automatisch.
7. Click **Submit** button on Automatisch.
8. Congrats! Typeform connection is created.

Some files were not shown because too many files have changed in this diff Show More