Compare commits
111 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1125f31810 | ||
![]() |
ef3c93a907 | ||
![]() |
89bcaa96ed | ||
![]() |
ce1eecf82e | ||
![]() |
3164663b4f | ||
![]() |
4f99bc36d0 | ||
![]() |
179c0a39d4 | ||
![]() |
0fefa4e43a | ||
![]() |
d4e8eb276d | ||
![]() |
fd3c26ad73 | ||
![]() |
d9f9056515 | ||
![]() |
d326b5a36a | ||
![]() |
8c4970b550 | ||
![]() |
51ccdf3577 | ||
![]() |
f79245d24a | ||
![]() |
e8d8a5e89d | ||
![]() |
b1dddcb511 | ||
![]() |
af45b8a1e4 | ||
![]() |
2c8b60ab9b | ||
![]() |
75196cbf84 | ||
![]() |
5d80fd523c | ||
![]() |
22dc61f39b | ||
![]() |
f4611d88cd | ||
![]() |
346a706e41 | ||
![]() |
3c62f182ab | ||
![]() |
67964192de | ||
![]() |
c6f62fd078 | ||
![]() |
8ea547c1d7 | ||
![]() |
c5fedda195 | ||
![]() |
8300a4b6df | ||
![]() |
32e77de8bb | ||
![]() |
e1c080f237 | ||
![]() |
6995323a45 | ||
![]() |
46bfc597ec | ||
![]() |
0abc614a78 | ||
![]() |
7518456fe8 | ||
![]() |
4ab76e7507 | ||
![]() |
1cf5a88582 | ||
![]() |
14a8551e82 | ||
![]() |
4b0916a214 | ||
![]() |
e451dd2262 | ||
![]() |
2e4ecfab16 | ||
![]() |
8c343abac5 | ||
![]() |
39ac3ff4c2 | ||
![]() |
677880c633 | ||
![]() |
bd2f2cbf64 | ||
![]() |
4129264d1d | ||
![]() |
f9133a6e96 | ||
![]() |
57a23946cc | ||
![]() |
6faa45ac16 | ||
![]() |
bf62ebd20a | ||
![]() |
687c6a09bc | ||
![]() |
77a7df1cff | ||
![]() |
05ce3edb80 | ||
![]() |
2564d7d976 | ||
![]() |
2f06cda82b | ||
![]() |
945b777c3c | ||
![]() |
d2a6c45fd6 | ||
![]() |
b5436fe7fa | ||
![]() |
82e3b40e7c | ||
![]() |
0f4f36c654 | ||
![]() |
d83e8dabf8 | ||
![]() |
397926f994 | ||
![]() |
8d8a9f7c78 | ||
![]() |
40b8860e7c | ||
![]() |
9978241329 | ||
![]() |
666a1ab81e | ||
![]() |
1d6ed8d9d0 | ||
![]() |
34e2c77934 | ||
![]() |
f47d912074 | ||
![]() |
f25b67c81c | ||
![]() |
08e60e6961 | ||
![]() |
c393a80185 | ||
![]() |
8e28ec491c | ||
![]() |
2e391cc651 | ||
![]() |
2e0b2191c0 | ||
![]() |
4240849a2a | ||
![]() |
a0f0db1dd4 | ||
![]() |
e455497596 | ||
![]() |
bb99dd82ba | ||
![]() |
48ccddb555 | ||
![]() |
23f56e4deb | ||
![]() |
ef27c9348b | ||
![]() |
cb664b563d | ||
![]() |
53f38c583a | ||
![]() |
12622e2045 | ||
![]() |
36dfedbaab | ||
![]() |
d3a059d759 | ||
![]() |
9ba1351a3b | ||
![]() |
399f8ed1db | ||
![]() |
d36b5e8091 | ||
![]() |
fc616e818a | ||
![]() |
edd438c37f | ||
![]() |
92e5ae0ebd | ||
![]() |
d3e13c30a6 | ||
![]() |
6bf0e799a1 | ||
![]() |
166dda4a4b | ||
![]() |
901605fd9c | ||
![]() |
e3e8f570f8 | ||
![]() |
6fdfdefe5c | ||
![]() |
5cc1afa6de | ||
![]() |
4398570583 | ||
![]() |
2e5b354f49 | ||
![]() |
11f96e8039 | ||
![]() |
7ae1a079ea | ||
![]() |
b6d2624c4a | ||
![]() |
51d6362863 | ||
![]() |
e30bfe67ed | ||
![]() |
c5bcebad9a | ||
![]() |
5deead7f94 | ||
![]() |
2cff284122 |
@@ -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
70
docker-compose.yml
Normal 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
10
docker/Dockerfile
Normal 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
11
docker/Dockerfile.compose
Normal 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
26
docker/compose-entrypoint.sh
Executable 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
|
@@ -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
9
docker/entrypoint.sh
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ -n "$WORKER" ]; then
|
||||||
|
automatisch start-worker
|
||||||
|
else
|
||||||
|
automatisch start
|
||||||
|
fi
|
@@ -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
|
|
@@ -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 "$@"
|
|
@@ -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
|
|
@@ -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 "$@"
|
|
@@ -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": {
|
||||||
|
@@ -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
|
||||||
|
@@ -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",
|
||||||
|
@@ -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));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
3
packages/backend/src/apps/ntfy/actions/index.ts
Normal file
3
packages/backend/src/apps/ntfy/actions/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import sendMessage from './send-message';
|
||||||
|
|
||||||
|
export default [sendMessage];
|
103
packages/backend/src/apps/ntfy/actions/send-message/index.ts
Normal file
103
packages/backend/src/apps/ntfy/actions/send-message/index.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import qs from 'qs';
|
||||||
|
import defineAction from '../../../../helpers/define-action';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Send message',
|
||||||
|
key: 'sendMessage',
|
||||||
|
description: 'Sends a message to a topic you specify.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Topic',
|
||||||
|
key: 'topic',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
description: 'Target topic name.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Message body',
|
||||||
|
key: 'message',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
description: 'Message body to be sent, set to triggered if empty or not passed.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Title',
|
||||||
|
key: 'title',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: false,
|
||||||
|
description: 'Message title.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Email',
|
||||||
|
key: 'email',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: false,
|
||||||
|
description: 'E-mail address for e-mail notifications.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Click URL',
|
||||||
|
key: 'click',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: false,
|
||||||
|
description: 'Website opened when notification is clicked.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Attach file by URL',
|
||||||
|
key: 'attach',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: false,
|
||||||
|
description: 'URL of an attachment.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Filename',
|
||||||
|
key: 'filename',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: false,
|
||||||
|
description: 'File name of the attachment.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Delay',
|
||||||
|
key: 'delay',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: false,
|
||||||
|
description: 'Timestamp or duration for delayed delivery. For example, 30min or 9am.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const {
|
||||||
|
topic,
|
||||||
|
message,
|
||||||
|
title,
|
||||||
|
email,
|
||||||
|
click,
|
||||||
|
attach,
|
||||||
|
filename,
|
||||||
|
delay
|
||||||
|
} = $.step.parameters;
|
||||||
|
const payload = {
|
||||||
|
topic,
|
||||||
|
message,
|
||||||
|
title,
|
||||||
|
email,
|
||||||
|
click,
|
||||||
|
attach,
|
||||||
|
filename,
|
||||||
|
delay
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await $.http.post('/', payload);
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: response.data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
1
packages/backend/src/apps/ntfy/assets/favicon.svg
Normal file
1
packages/backend/src/apps/ntfy/assets/favicon.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg width="50mm" height="50mm" viewBox="0 0 50 50" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="a"><stop style="stop-color:#348878;stop-opacity:1" offset="0"/><stop style="stop-color:#56bda8;stop-opacity:1" offset="1"/></linearGradient><linearGradient xlink:href="#a" id="b" x1="160.722" y1="128.533" x2="168.412" y2="134.326" gradientUnits="userSpaceOnUse" gradientTransform="translate(-845.726 -630.598) scale(5.59448)"/></defs><g style="display:inline"><path style="color:#000;fill:url(#b);stroke:none;stroke-width:3.72347;-inkscape-stroke:none" d="M94.237 89.912H59.499c-2.388 0-4.342 1.844-4.342 4.098l.033 27.754-.648 3.738 9.297-2.806h30.396c2.388 0 4.342-1.845 4.342-4.099V94.01c0-2.254-1.954-4.098-4.342-4.098z" transform="translate(-51.147 -81.516)"/><path style="color:#000;fill:#fff;stroke:none;stroke-width:.762343;-inkscape-stroke:none" d="M58.849 86.79c-3.62 0-6.72 2.848-6.72 6.47v.002l.035 30.273-.91 6.708 12.362-3.284h30.729c3.62 0 6.72-2.852 6.72-6.473V93.26c0-3.62-3.099-6.469-6.717-6.469h-.003zm0 4.566h35.499c1.272 0 2.151.927 2.151 1.903v27.227c0 .977-.88 1.924-2.154 1.903h-31.4l-6.28 1.898.065-.37-.035-30.658c0-.977.88-1.903 2.154-1.903z" transform="translate(-51.147 -81.516)"/><g style="font-size:8.48274px;font-family:sans-serif;letter-spacing:0;word-spacing:0;fill:#000;stroke:none;stroke-width:.525121"><path style="color:#000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#fff;stroke:none;-inkscape-stroke:none" d="M62.57 116.77v-1.312l3.28-1.459q.159-.068.306-.102.158-.045.283-.068l.271-.022v-.09q-.136-.012-.271-.046-.125-.023-.283-.057-.147-.045-.306-.113l-3.28-1.459v-1.323l5.068 2.319v1.413z" transform="matrix(2.1689 0 0 2.57844 -124.28 -268.742)"/><path style="color:#000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#fff;stroke:none;-inkscape-stroke:none" d="M62.309 110.31v1.903l3.437 1.53.022.007-.022.008-3.437 1.53v1.892l.37-.17 5.221-2.39v-1.75zm.525.817 4.541 2.08v1.076l-4.541 2.078v-.732l3.12-1.389.003-.002a1.56 1.56 0 0 1 .258-.086h.006l.008-.002c.094-.027.176-.047.246-.06l.498-.041v-.574l-.24-.02a1.411 1.411 0 0 1-.231-.04l-.008-.001-.008-.002a9.077 9.077 0 0 1-.263-.053 2.781 2.781 0 0 1-.266-.097l-.004-.002-3.119-1.39z" transform="matrix(2.1689 0 0 2.57844 -124.28 -268.742)"/></g><g style="font-size:8.48274px;font-family:sans-serif;letter-spacing:0;word-spacing:0;fill:#000;stroke:none;stroke-width:.525121"><path style="color:#000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#fff;stroke:none;-inkscape-stroke:none" d="M69.171 117.754h5.43v1.278h-5.43Z" transform="matrix(2.16247 0 0 2.48294 -122.76 -261.211)"/><path style="color:#000;-inkscape-font-specification:'JetBrains Mono, Bold';fill:#fff;stroke:none;-inkscape-stroke:none" d="M68.908 117.492v1.802h5.955v-1.802zm.526.524h4.904v.754h-4.904z" transform="matrix(2.16247 0 0 2.48294 -122.76 -261.211)"/></g></g></svg>
|
After Width: | Height: | Size: 2.9 KiB |
41
packages/backend/src/apps/ntfy/auth/index.ts
Normal file
41
packages/backend/src/apps/ntfy/auth/index.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import verifyCredentials from './verify-credentials';
|
||||||
|
import isStillVerified from './is-still-verified';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'serverUrl',
|
||||||
|
label: 'Server URL',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: 'https://ntfy.sh',
|
||||||
|
placeholder: null,
|
||||||
|
description: 'ntfy server to use.',
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'username',
|
||||||
|
label: 'Username',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: false,
|
||||||
|
readOnly: false,
|
||||||
|
placeholder: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
description: 'You may need to provide your username if your installation requires authentication.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'password',
|
||||||
|
label: 'Password',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: false,
|
||||||
|
readOnly: false,
|
||||||
|
placeholder: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
description: 'You may need to provide your password if your installation requires authentication.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
verifyCredentials,
|
||||||
|
isStillVerified,
|
||||||
|
};
|
9
packages/backend/src/apps/ntfy/auth/is-still-verified.ts
Normal file
9
packages/backend/src/apps/ntfy/auth/is-still-verified.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
import verifyCredentials from './verify-credentials';
|
||||||
|
|
||||||
|
const isStillVerified = async ($: IGlobalVariable) => {
|
||||||
|
await verifyCredentials($);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isStillVerified;
|
16
packages/backend/src/apps/ntfy/auth/verify-credentials.ts
Normal file
16
packages/backend/src/apps/ntfy/auth/verify-credentials.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
|
||||||
|
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||||
|
await $.http.post('/', { topic: 'automatisch' });
|
||||||
|
let screenName = $.auth.data.serverUrl;
|
||||||
|
|
||||||
|
if ($.auth.data.username) {
|
||||||
|
screenName = `${$.auth.data.username} @ ${screenName}`
|
||||||
|
}
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
screenName,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default verifyCredentials;
|
18
packages/backend/src/apps/ntfy/common/add-auth-header.ts
Normal file
18
packages/backend/src/apps/ntfy/common/add-auth-header.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { TBeforeRequest } from '@automatisch/types';
|
||||||
|
|
||||||
|
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||||
|
if ($.auth.data.apiBaseUrl) {
|
||||||
|
requestConfig.baseURL = $.auth.data.apiBaseUrl as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($.auth.data?.username && $.auth.data?.password) {
|
||||||
|
requestConfig.auth = {
|
||||||
|
username: $.auth.data.username as string,
|
||||||
|
password: $.auth.data.password as string,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default addAuthHeader;
|
0
packages/backend/src/apps/ntfy/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/ntfy/index.d.ts
vendored
Normal file
18
packages/backend/src/apps/ntfy/index.ts
Normal file
18
packages/backend/src/apps/ntfy/index.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import defineApp from '../../helpers/define-app';
|
||||||
|
import addAuthHeader from './common/add-auth-header';
|
||||||
|
import auth from './auth';
|
||||||
|
import actions from './actions';
|
||||||
|
|
||||||
|
export default defineApp({
|
||||||
|
name: 'Ntfy',
|
||||||
|
key: 'ntfy',
|
||||||
|
iconUrl: '{BASE_URL}/apps/ntfy/assets/favicon.svg',
|
||||||
|
authDocUrl: 'https://automatisch.io/docs/apps/ntfy/connection',
|
||||||
|
supportsConnections: true,
|
||||||
|
baseUrl: 'https://ntfy.sh',
|
||||||
|
apiBaseUrl: 'https://ntfy.sh',
|
||||||
|
primaryColor: '56bda8',
|
||||||
|
beforeRequest: [addAuthHeader],
|
||||||
|
auth,
|
||||||
|
actions,
|
||||||
|
});
|
@@ -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,
|
||||||
|
25
packages/backend/src/apps/salesforce/auth/refresh-token.ts
Normal file
25
packages/backend/src/apps/salesforce/auth/refresh-token.ts
Normal 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;
|
@@ -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,
|
||||||
|
10
packages/backend/src/apps/stripe/assets/favicon.svg
Normal file
10
packages/backend/src/apps/stripe/assets/favicon.svg
Normal 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 |
31
packages/backend/src/apps/stripe/auth/index.ts
Normal file
31
packages/backend/src/apps/stripe/auth/index.ts
Normal 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
|
||||||
|
};
|
@@ -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;
|
12
packages/backend/src/apps/stripe/auth/verify-credentials.ts
Normal file
12
packages/backend/src/apps/stripe/auth/verify-credentials.ts
Normal 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;
|
@@ -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;
|
0
packages/backend/src/apps/stripe/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/stripe/index.d.ts
vendored
Normal file
19
packages/backend/src/apps/stripe/index.ts
Normal file
19
packages/backend/src/apps/stripe/index.ts
Normal 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: [],
|
||||||
|
})
|
@@ -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;
|
@@ -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($)
|
||||||
|
}
|
||||||
|
})
|
4
packages/backend/src/apps/stripe/triggers/index.ts
Normal file
4
packages/backend/src/apps/stripe/triggers/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import balanceTransaction from "./balance-transaction";
|
||||||
|
import payouts from "./payouts";
|
||||||
|
|
||||||
|
export default [balanceTransaction, payouts];
|
@@ -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;
|
12
packages/backend/src/apps/stripe/triggers/payouts/index.ts
Normal file
12
packages/backend/src/apps/stripe/triggers/payouts/index.ts
Normal 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($)
|
||||||
|
}
|
||||||
|
})
|
3
packages/backend/src/apps/telegram-bot/actions/index.ts
Normal file
3
packages/backend/src/apps/telegram-bot/actions/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import sendMessage from './send-message';
|
||||||
|
|
||||||
|
export default [sendMessage];
|
@@ -0,0 +1,59 @@
|
|||||||
|
import qs from 'qs';
|
||||||
|
import defineAction from '../../../../helpers/define-action';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Send message',
|
||||||
|
key: 'sendMessage',
|
||||||
|
description: 'Sends a message to a chat you specify.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Chat ID',
|
||||||
|
key: 'chatId',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
description: 'Unique identifier for the target chat or username of the target channel (in the format @channelusername).',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Message text',
|
||||||
|
key: 'text',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
description: 'Text of the message to be sent, 1-4096 characters.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Disable notification?',
|
||||||
|
key: 'disableNotification',
|
||||||
|
type: 'dropdown' as const,
|
||||||
|
required: false,
|
||||||
|
value: false,
|
||||||
|
description: 'Sends the message silently. Users will receive a notification with no sound.',
|
||||||
|
variables: false,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'Yes',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'No',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const payload = {
|
||||||
|
chat_id: $.step.parameters.chatId,
|
||||||
|
text: $.step.parameters.text,
|
||||||
|
disable_notification: $.step.parameters.disableNotification,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await $.http.post('/sendMessage', payload);
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: response.data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
14
packages/backend/src/apps/telegram-bot/assets/favicon.svg
Normal file
14
packages/backend/src/apps/telegram-bot/assets/favicon.svg
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="256px" height="256px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
|
||||||
|
<title>Telegram</title>
|
||||||
|
<defs>
|
||||||
|
<linearGradient x1="50%" y1="2.77555756e-15%" x2="50%" y2="100%" id="telegramLinearGradient-1">
|
||||||
|
<stop stop-color="#2AABEE" offset="0%"></stop>
|
||||||
|
<stop stop-color="#229ED9" offset="100%"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g>
|
||||||
|
<path d="M128,0 C94.06,0 61.48,13.494 37.5,37.49 C13.5,61.486 0,94.066 0,128 C0,161.934 13.5,194.514 37.5,218.51 C61.48,242.506 94.06,256 128,256 C161.94,256 194.52,242.506 218.5,218.51 C242.5,194.514 256,161.934 256,128 C256,94.066 242.5,61.486 218.5,37.49 C194.52,13.494 161.94,0 128,0 Z" fill="url(#telegramLinearGradient-1)"></path>
|
||||||
|
<path d="M57.94,126.6476 C95.26,110.3916 120.14,99.6736 132.58,94.4956 C168.14,79.7096 175.52,77.1416 180.34,77.0547542 C181.4,77.0376 183.76,77.2996 185.3,78.5456 C186.58,79.5956 186.94,81.0156 187.12,82.0116 C187.28,83.0076 187.5,85.2776 187.32,87.0496 C185.4,107.2896 177.06,156.4056 172.82,179.0756 C171.04,188.668 167.5,191.884 164.08,192.198 C156.64,192.882 151,187.286 143.8,182.5676 C132.54,175.1816 126.18,170.5856 115.24,163.3796 C102.6,155.0516 110.8,150.4736 118,142.9936 C119.88,141.0356 152.64,111.2456 153.26,108.5436 C153.34,108.2056 153.42,106.9456 152.66,106.2816 C151.92,105.6156 150.82,105.8436 150.02,106.0236 C148.88,106.2796 130.9,118.1756 96.02,141.7096 C90.92,145.2176 86.3,146.9276 82.14,146.8376 C77.58,146.7396 68.78,144.2536 62.24,142.1296 C54.24,139.5236 47.86,138.1456 48.42,133.7196 C48.7,131.4156 51.88,129.0576 57.94,126.6476 L57.94,126.6476 Z" fill="#FFFFFF"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
21
packages/backend/src/apps/telegram-bot/auth/index.ts
Normal file
21
packages/backend/src/apps/telegram-bot/auth/index.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import verifyCredentials from './verify-credentials';
|
||||||
|
import isStillVerified from './is-still-verified';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'token',
|
||||||
|
label: 'Bot token',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: 'Bot token which should be retrieved from @botfather.',
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
verifyCredentials,
|
||||||
|
isStillVerified,
|
||||||
|
};
|
@@ -0,0 +1,9 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
import verifyCredentials from './verify-credentials';
|
||||||
|
|
||||||
|
const isStillVerified = async ($: IGlobalVariable) => {
|
||||||
|
await verifyCredentials($);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isStillVerified;
|
@@ -0,0 +1,12 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
|
||||||
|
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||||
|
const { data } = await $.http.get('/getMe');
|
||||||
|
const { result: me } = data;
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
screenName: me.first_name,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default verifyCredentials;
|
@@ -0,0 +1,13 @@
|
|||||||
|
import { TBeforeRequest } from '@automatisch/types';
|
||||||
|
import { URL } from 'node:url';
|
||||||
|
|
||||||
|
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||||
|
if ($.auth.data?.token) {
|
||||||
|
const token = $.auth.data.token as string;
|
||||||
|
requestConfig.baseURL = (new URL(`/bot${token}`, requestConfig.baseURL)).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default addAuthHeader;
|
0
packages/backend/src/apps/telegram-bot/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/telegram-bot/index.d.ts
vendored
Normal file
18
packages/backend/src/apps/telegram-bot/index.ts
Normal file
18
packages/backend/src/apps/telegram-bot/index.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import defineApp from '../../helpers/define-app';
|
||||||
|
import addAuthHeader from './common/add-auth-header';
|
||||||
|
import auth from './auth';
|
||||||
|
import actions from './actions';
|
||||||
|
|
||||||
|
export default defineApp({
|
||||||
|
name: 'Telegram',
|
||||||
|
key: 'telegram-bot',
|
||||||
|
iconUrl: '{BASE_URL}/apps/telegram-bot/assets/favicon.svg',
|
||||||
|
authDocUrl: 'https://automatisch.io/docs/apps/telegram-bot/connection',
|
||||||
|
supportsConnections: true,
|
||||||
|
baseUrl: 'https://telegram.org',
|
||||||
|
apiBaseUrl: 'https://api.telegram.org',
|
||||||
|
primaryColor: '2AABEE',
|
||||||
|
beforeRequest: [addAuthHeader],
|
||||||
|
auth,
|
||||||
|
actions,
|
||||||
|
});
|
4
packages/backend/src/apps/typeform/assets/favicon.svg
Normal file
4
packages/backend/src/apps/typeform/assets/favicon.svg
Normal 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 |
21
packages/backend/src/apps/typeform/auth/generate-auth-url.ts
Normal file
21
packages/backend/src/apps/typeform/auth/generate-auth-url.ts
Normal 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,
|
||||||
|
});
|
||||||
|
}
|
50
packages/backend/src/apps/typeform/auth/index.ts
Normal file
50
packages/backend/src/apps/typeform/auth/index.ts
Normal 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,
|
||||||
|
};
|
@@ -0,0 +1,9 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
|
||||||
|
const isStillVerified = async ($: IGlobalVariable) => {
|
||||||
|
await $.http.get('/me');
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isStillVerified;
|
24
packages/backend/src/apps/typeform/auth/refresh-token.ts
Normal file
24
packages/backend/src/apps/typeform/auth/refresh-token.ts
Normal 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;
|
@@ -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;
|
20
packages/backend/src/apps/typeform/auth/verify-webhook.ts
Normal file
20
packages/backend/src/apps/typeform/auth/verify-webhook.ts
Normal 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;
|
12
packages/backend/src/apps/typeform/common/add-auth-header.ts
Normal file
12
packages/backend/src/apps/typeform/common/add-auth-header.ts
Normal 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;
|
12
packages/backend/src/apps/typeform/common/auth-scope.ts
Normal file
12
packages/backend/src/apps/typeform/common/auth-scope.ts
Normal 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;
|
3
packages/backend/src/apps/typeform/dynamic-data/index.ts
Normal file
3
packages/backend/src/apps/typeform/dynamic-data/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import listForms from './list-forms';
|
||||||
|
|
||||||
|
export default [listForms];
|
@@ -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;
|
||||||
|
},
|
||||||
|
};
|
0
packages/backend/src/apps/typeform/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/typeform/index.d.ts
vendored
Normal file
20
packages/backend/src/apps/typeform/index.ts
Normal file
20
packages/backend/src/apps/typeform/index.ts
Normal 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,
|
||||||
|
});
|
3
packages/backend/src/apps/typeform/triggers/index.ts
Normal file
3
packages/backend/src/apps/typeform/triggers/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import newEntry from './new-entry';
|
||||||
|
|
||||||
|
export default [newEntry];
|
@@ -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}`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
8
packages/backend/src/apps/webhook/assets/favicon.svg
Normal file
8
packages/backend/src/apps/webhook/assets/favicon.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48" width="48px" height="48px"><g id="surface56721297">
|
||||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,12.156863%,32.156864%);fill-opacity:1;" d="M 35 37 C 32.800781 37 31 35.199219 31 33 C 31 30.800781 32.800781 29 35 29 C 37.199219 29 39 30.800781 39 33 C 39 35.199219 37.199219 37 35 37 Z M 35 37 "/>
|
||||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,12.156863%,32.156864%);fill-opacity:1;" d="M 35 43 C 32 43 29.101562 41.601562 27.199219 39.300781 L 30.300781 36.800781 C 31.398438 38.199219 33.199219 39.101562 35 39.101562 C 38.300781 39.101562 41 36.398438 41 33.101562 C 41 29.800781 38.300781 27.101562 35 27.101562 C 34 27.101562 33 27.398438 32.101562 27.800781 L 30.398438 28.800781 L 23.300781 16 L 26.800781 14.101562 L 32.101562 23.5 C 33.101562 23.199219 34.101562 23 35.101562 23 C 40.601562 23 45.101562 27.5 45.101562 33 C 45.101562 38.5 40.5 43 35 43 Z M 35 43 "/>
|
||||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,12.156863%,32.156864%);fill-opacity:1;" d="M 14 43 C 8.5 43 4 38.5 4 33 C 4 28.398438 7.101562 24.5 11.5 23.300781 L 12.5 27.199219 C 9.898438 27.898438 8 30.300781 8 33 C 8 36.300781 10.699219 39 14 39 C 17.300781 39 20 36.300781 20 33 L 20 31 L 35 31 L 35 35 L 23.800781 35 C 22.898438 39.601562 18.800781 43 14 43 Z M 14 43 "/>
|
||||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,34.901962%,96.862745%);fill-opacity:1;" d="M 14 37 C 11.800781 37 10 35.199219 10 33 C 10 30.800781 11.800781 29 14 29 C 16.199219 29 18 30.800781 18 33 C 18 35.199219 16.199219 37 14 37 Z M 14 37 "/>
|
||||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,12.156863%,32.156864%);fill-opacity:1;" d="M 25 19 C 22.800781 19 21 17.199219 21 15 C 21 12.800781 22.800781 11 25 11 C 27.199219 11 29 12.800781 29 15 C 29 17.199219 27.199219 19 25 19 Z M 25 19 "/>
|
||||||
|
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,34.901962%,96.862745%);fill-opacity:1;" d="M 15.699219 34 L 12.300781 32 L 18.199219 22.300781 C 16.199219 20.398438 15 17.800781 15 15 C 15 9.5 19.5 5 25 5 C 30.5 5 35 9.5 35 15 C 35 15.898438 34.898438 16.699219 34.699219 17.5 L 30.800781 16.5 C 30.898438 16 31 15.5 31 15 C 31 11.699219 28.300781 9 25 9 C 21.699219 9 19 11.699219 19 15 C 19 17.101562 20.101562 19 21.898438 20.101562 L 23.601562 21.101562 Z M 15.699219 34 "/>
|
||||||
|
</g></svg>
|
After Width: | Height: | Size: 2.3 KiB |
0
packages/backend/src/apps/webhook/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/webhook/index.d.ts
vendored
Normal file
14
packages/backend/src/apps/webhook/index.ts
Normal file
14
packages/backend/src/apps/webhook/index.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import defineApp from '../../helpers/define-app';
|
||||||
|
import triggers from './triggers';
|
||||||
|
|
||||||
|
export default defineApp({
|
||||||
|
name: 'Webhook',
|
||||||
|
key: 'webhook',
|
||||||
|
iconUrl: '{BASE_URL}/apps/webhook/assets/favicon.svg',
|
||||||
|
authDocUrl: 'https://automatisch.io/docs/apps/webhook/connection',
|
||||||
|
supportsConnections: false,
|
||||||
|
baseUrl: '',
|
||||||
|
apiBaseUrl: '',
|
||||||
|
primaryColor: '0059F7',
|
||||||
|
triggers,
|
||||||
|
});
|
@@ -0,0 +1,20 @@
|
|||||||
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
import defineTrigger from '../../../../helpers/define-trigger';
|
||||||
|
|
||||||
|
export default defineTrigger({
|
||||||
|
name: 'Catch raw webhook',
|
||||||
|
key: 'catchRawWebhook',
|
||||||
|
type: 'webhook',
|
||||||
|
description: 'Triggers when the webhook receives a request.',
|
||||||
|
|
||||||
|
async testRun($) {
|
||||||
|
if (!isEmpty($.lastExecutionStep?.dataOut)) {
|
||||||
|
$.pushTriggerItem({
|
||||||
|
raw: $.lastExecutionStep.dataOut,
|
||||||
|
meta: {
|
||||||
|
internalId: '',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
3
packages/backend/src/apps/webhook/triggers/index.ts
Normal file
3
packages/backend/src/apps/webhook/triggers/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import catchRawWebhook from './catch-raw-webhook';
|
||||||
|
|
||||||
|
export default [catchRawWebhook];
|
@@ -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;
|
||||||
|
@@ -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;
|
||||||
|
93
packages/backend/src/controllers/webhooks/handler.ts
Normal file
93
packages/backend/src/controllers/webhooks/handler.ts
Normal 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);
|
||||||
|
};
|
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
@@ -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
|
||||||
|
11
packages/backend/src/helpers/check-worker-readiness.ts
Normal file
11
packages/backend/src/helpers/check-worker-readiness.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import Redis from 'ioredis';
|
||||||
|
import logger from './logger';
|
||||||
|
import redisConfig from '../config/redis';
|
||||||
|
|
||||||
|
const redisClient = new Redis(redisConfig);
|
||||||
|
|
||||||
|
redisClient.on('ready', () => {
|
||||||
|
logger.info(`Workers are ready!`);
|
||||||
|
|
||||||
|
redisClient.disconnect();
|
||||||
|
});
|
@@ -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);
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
10
packages/backend/src/helpers/remove-job-configuration.ts
Normal file
10
packages/backend/src/helpers/remove-job-configuration.ts
Normal 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,
|
||||||
|
};
|
||||||
|
|
@@ -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');
|
||||||
|
|
||||||
|
10
packages/backend/src/routes/index.ts
Normal file
10
packages/backend/src/routes/index.ts
Normal 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;
|
11
packages/backend/src/routes/webhooks.ts
Normal file
11
packages/backend/src/routes/webhooks.ts
Normal 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;
|
@@ -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) {
|
||||||
|
@@ -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';
|
||||||
|
@@ -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}`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -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 }
|
||||||
|
@@ -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 }
|
||||||
);
|
);
|
||||||
|
@@ -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",
|
||||||
|
@@ -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"
|
||||||
|
@@ -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,
|
||||||
|
@@ -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',
|
||||||
|
},
|
||||||
|
],
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@@ -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 |
|
||||||
|
@@ -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.
|
||||||
:::
|
:::
|
||||||
|
12
packages/docs/pages/apps/ntfy/actions.md
Normal file
12
packages/docs/pages/apps/ntfy/actions.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
favicon: /favicons/ntfy.svg
|
||||||
|
items:
|
||||||
|
- name: Send a message
|
||||||
|
desc: Sends a message to a topic you specify.
|
||||||
|
---
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import CustomListing from '../../components/CustomListing.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CustomListing />
|
10
packages/docs/pages/apps/ntfy/connection.md
Normal file
10
packages/docs/pages/apps/ntfy/connection.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Ntfy
|
||||||
|
|
||||||
|
:::info
|
||||||
|
This page explains the steps you need to follow to set up the Ntfy
|
||||||
|
connection in Automatisch. If any of the steps are outdated, please let us know!
|
||||||
|
:::
|
||||||
|
|
||||||
|
If you use ntfy.sh, the official public server for this service, you do not need to set up a connection with a custom configuration. It's enough to create one with the default server URL.
|
||||||
|
|
||||||
|
However, if you have a ntfy installation, that's different than ntfy.sh, you need to specify your server URL on Automatisch while creating a connection. Additionally, you may need to provide your username and password if your installation requires authentication.
|
14
packages/docs/pages/apps/stripe/connection.md
Normal file
14
packages/docs/pages/apps/stripe/connection.md
Normal 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!
|
18
packages/docs/pages/apps/stripe/triggers.md
Normal file
18
packages/docs/pages/apps/stripe/triggers.md
Normal 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 />
|
12
packages/docs/pages/apps/telegram-bot/actions.md
Normal file
12
packages/docs/pages/apps/telegram-bot/actions.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
favicon: /favicons/telegram-bot.svg
|
||||||
|
items:
|
||||||
|
- name: Send a message
|
||||||
|
desc: Sends a message to a chat you specify.
|
||||||
|
---
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import CustomListing from '../../components/CustomListing.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CustomListing />
|
14
packages/docs/pages/apps/telegram-bot/connection.md
Normal file
14
packages/docs/pages/apps/telegram-bot/connection.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Telegram
|
||||||
|
|
||||||
|
:::info
|
||||||
|
This page explains the steps you need to follow to set up the Telegram
|
||||||
|
connection in Automatisch. If any of the steps are outdated, please let us know!
|
||||||
|
:::
|
||||||
|
|
||||||
|
1. Start a chat with [Botfather](https://telegram.me/BotFather).
|
||||||
|
1. Enter `/newbot`.
|
||||||
|
1. Enter a name for your bot.
|
||||||
|
1. Enter a username for your bot.
|
||||||
|
1. Copy the **token** value from the answer to the **Bot token** field on Automatisch.
|
||||||
|
1. Click **Submit** button on Automatisch.
|
||||||
|
1. Congrats! Start using your new Telegram connection within the flows.
|
14
packages/docs/pages/apps/typeform/connection.md
Normal file
14
packages/docs/pages/apps/typeform/connection.md
Normal 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
Reference in New Issue
Block a user