Compare commits
107 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fedbd66f8e | ||
![]() |
2be8e14f66 | ||
![]() |
9cfaa9e603 | ||
![]() |
48653c7590 | ||
![]() |
72450b6305 | ||
![]() |
7d22a1c105 | ||
![]() |
08ac3fb984 | ||
![]() |
2f64a074c6 | ||
![]() |
f98c797311 | ||
![]() |
d81fb14b04 | ||
![]() |
01bb1259eb | ||
![]() |
1118d025bf | ||
![]() |
146f0604c3 | ||
![]() |
f90afe8b37 | ||
![]() |
fff5306fa6 | ||
![]() |
078ea24cd2 | ||
![]() |
85a3558074 | ||
![]() |
73a9ae1c9c | ||
![]() |
2222ede420 | ||
![]() |
c6d80a9b0e | ||
![]() |
23c4414398 | ||
![]() |
8b9320bbf9 | ||
![]() |
a8ac288f17 | ||
![]() |
ffe7de6774 | ||
![]() |
d87a826493 | ||
![]() |
645fe29060 | ||
![]() |
4db738a7f6 | ||
![]() |
253c2c4317 | ||
![]() |
49755303f7 | ||
![]() |
1e137f0bd8 | ||
![]() |
c2579b1850 | ||
![]() |
577d5215cd | ||
![]() |
0870fa7e8f | ||
![]() |
ddb3795985 | ||
![]() |
45e98cd5b7 | ||
![]() |
4a99b7a3f2 | ||
![]() |
985132da78 | ||
![]() |
315edea5ca | ||
![]() |
27e0ef4e38 | ||
![]() |
8090d8be5a | ||
![]() |
8c8fd98bd5 | ||
![]() |
6f724b5bec | ||
![]() |
909cbd1f0c | ||
![]() |
094de0ca21 | ||
![]() |
af8ca4af9e | ||
![]() |
709e788f5f | ||
![]() |
1a4a1f7f8b | ||
![]() |
ff0bde059a | ||
![]() |
ac671110a3 | ||
![]() |
30c95da90e | ||
![]() |
0b52a1bd01 | ||
![]() |
3fc7fce9ca | ||
![]() |
f8df7987b8 | ||
![]() |
697594b63e | ||
![]() |
7be3923224 | ||
![]() |
5b913aa30a | ||
![]() |
da9cb7d6eb | ||
![]() |
aca19f7f73 | ||
![]() |
faf151cd62 | ||
![]() |
675cd4de2a | ||
![]() |
74ebe492c5 | ||
![]() |
254fcecf33 | ||
![]() |
a334864980 | ||
![]() |
b6b377c209 | ||
![]() |
5f76aa02dd | ||
![]() |
0ba0c998e9 | ||
![]() |
2e54216469 | ||
![]() |
56dbe62cad | ||
![]() |
048db60a10 | ||
![]() |
f3b1b2254f | ||
![]() |
e705aaa2f6 | ||
![]() |
1f80f843ba | ||
![]() |
02a76418fc | ||
![]() |
d1245705de | ||
![]() |
6d74f7c64d | ||
![]() |
bf1076b7d2 | ||
![]() |
7175d92eaf | ||
![]() |
f30ac46672 | ||
![]() |
0970db3295 | ||
![]() |
5d24216124 | ||
![]() |
9a55333ce8 | ||
![]() |
cb58ab5fb7 | ||
![]() |
5d9ed13003 | ||
![]() |
5b51cb30f5 | ||
![]() |
d8c5850eaa | ||
![]() |
4839b100cb | ||
![]() |
469050914b | ||
![]() |
8036d20bf9 | ||
![]() |
f04597335f | ||
![]() |
3d26ca6cc2 | ||
![]() |
ec3454291b | ||
![]() |
21e2f394a9 | ||
![]() |
dd9f69e4cd | ||
![]() |
f7e8cb24ee | ||
![]() |
3b5a77ec22 | ||
![]() |
16715129d1 | ||
![]() |
3aafaabda6 | ||
![]() |
279abfedfd | ||
![]() |
e77580dd35 | ||
![]() |
9cde662092 | ||
![]() |
862734c572 | ||
![]() |
61dc611588 | ||
![]() |
03368e2cca | ||
![]() |
6382325280 | ||
![]() |
a5b0910b60 | ||
![]() |
ba1e1ef20c | ||
![]() |
dd8bab299d |
1
.devcontainer/Dockerfile
Normal file
1
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1 @@
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/base:ubuntu-22.04
|
48
.devcontainer/boot.sh
Normal file
48
.devcontainer/boot.sh
Normal file
@@ -0,0 +1,48 @@
|
||||
#!/bin/bash
|
||||
|
||||
CURRENT_DIR="$(pwd)"
|
||||
BACKEND_PORT=3000
|
||||
WEB_PORT=3001
|
||||
|
||||
echo "Configuring backend environment variables..."
|
||||
cd packages/backend
|
||||
rm -rf .env
|
||||
echo "
|
||||
HOST=localhost
|
||||
PROTOCOL=http
|
||||
PORT=$BACKEND_PORT
|
||||
WEB_APP_URL=https://$CODESPACE_NAME-$WEB_PORT.$GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN
|
||||
APP_ENV=development
|
||||
POSTGRES_DATABASE=automatisch
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_HOST=postgres
|
||||
POSTGRES_USERNAME=automatisch_user
|
||||
POSTGRES_PASSWORD=automatisch_password
|
||||
ENCRYPTION_KEY=sample_encryption_key
|
||||
WEBHOOK_SECRET_KEY=sample_webhook_secret_key
|
||||
APP_SECRET_KEY=sample_app_secret_key
|
||||
REDIS_HOST=redis
|
||||
SERVE_WEB_APP_SEPARATELY=true" >> .env
|
||||
cd $CURRENT_DIR
|
||||
|
||||
echo "Configuring web environment variables..."
|
||||
cd packages/web
|
||||
rm -rf .env
|
||||
echo "
|
||||
PORT=$WEB_PORT
|
||||
REACT_APP_GRAPHQL_URL=https://$CODESPACE_NAME-$BACKEND_PORT.$GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN/graphql
|
||||
REACT_APP_BASE_URL=https://$CODESPACE_NAME-$WEB_PORT.$GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN
|
||||
REACT_APP_NOTIFICATIONS_URL=https://notifications.automatisch.io
|
||||
" >> .env
|
||||
cd $CURRENT_DIR
|
||||
|
||||
echo "Installing and linking dependencies..."
|
||||
yarn
|
||||
yarn lerna bootstrap
|
||||
|
||||
echo "Migrating database..."
|
||||
cd packages/backend
|
||||
yarn db:migrate
|
||||
yarn db:seed:user
|
||||
|
||||
echo "Done!"
|
53
.devcontainer/devcontainer.json
Normal file
53
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "Automatisch",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspace",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/git:1": {
|
||||
"version": "latest"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": 16
|
||||
},
|
||||
"ghcr.io/devcontainers/features/common-utils:1": {
|
||||
"username": "vscode",
|
||||
"uid": 1000,
|
||||
"gid": 1000,
|
||||
"installZsh": true,
|
||||
"installOhMyZsh": true,
|
||||
"configureZshAsDefaultShell": true,
|
||||
"upgradePackages": true
|
||||
}
|
||||
},
|
||||
|
||||
"hostRequirements": {
|
||||
"cpus": 4,
|
||||
"memory": "8gb",
|
||||
"storage": "32gb"
|
||||
},
|
||||
|
||||
"portsAttributes": {
|
||||
"3000": {
|
||||
"label": "Backend",
|
||||
"onAutoForward": "silent",
|
||||
"protocol": "http"
|
||||
},
|
||||
"3001": {
|
||||
"label": "Frontend",
|
||||
"onAutoForward": "silent",
|
||||
"protocol": "http"
|
||||
}
|
||||
},
|
||||
|
||||
"forwardPorts": [3000, 3001],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": ["bash", ".devcontainer/boot.sh"]
|
||||
|
||||
// Configure tool-specific properties.
|
||||
// "customizations": {},
|
||||
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "root"
|
||||
}
|
31
.devcontainer/docker-compose.yml
Normal file
31
.devcontainer/docker-compose.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: .devcontainer/Dockerfile
|
||||
volumes:
|
||||
- ..:/workspace:cached
|
||||
command: sleep infinity
|
||||
postgres:
|
||||
image: 'postgres:14.5-alpine'
|
||||
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-alpine'
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
@@ -10,7 +10,7 @@
|
||||
|
||||
There are other existing solutions in the market, like Zapier and Integromat, so you might be wondering why you should use Automatisch.
|
||||
|
||||
✅ The most significant advantage of having Automatisch is keeping your data on your own servers. Not all companies want to use an automation service in the cloud, and the current open-source or self-hosted solutions mainly focus on developers rather than a user without a technical background.
|
||||
✅ One of the main benefits of using Automatisch is that it allows you to store your data on your own servers, which is essential for businesses that handle sensitive user information and cannot risk sharing it with external cloud services. This is especially relevant for industries such as healthcare and finance, as well as for European companies that must adhere to the General Data Protection Regulation (GDPR).
|
||||
|
||||
🤓 Your contributions are vital to the development of Automatisch. As an open-source software, anyone can have an impact on how it is being developed.
|
||||
|
||||
@@ -24,7 +24,7 @@ The official documentation can be found here: [https://automatisch.io/docs](http
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone git@github.com:automatisch/automatisch.git
|
||||
git clone https://github.com/automatisch/automatisch.git
|
||||
|
||||
# Go to the repository folder
|
||||
cd automatisch
|
||||
@@ -35,6 +35,8 @@ docker compose up
|
||||
|
||||
You can use `user@automatisch.io` email address and `sample` password to login to Automatisch. Please do not forget to change your email and password from the settings page.
|
||||
|
||||
For other installation types, you can check the [installation](https://automatisch.io/docs/guide/installation) guide.
|
||||
|
||||
## Community Links
|
||||
|
||||
- [Discord](https://discord.gg/dJSah9CVrC)
|
||||
|
@@ -2,9 +2,13 @@
|
||||
FROM node:16-alpine
|
||||
WORKDIR /automatisch
|
||||
|
||||
RUN apk --no-cache add --virtual build-dependencies python3 build-base
|
||||
|
||||
COPY ./entrypoint.sh /entrypoint.sh
|
||||
|
||||
RUN yarn global add @automatisch/cli@0.2.0
|
||||
RUN yarn global add @automatisch/cli@0.4.0 --network-timeout 1000000
|
||||
|
||||
RUN apk del build-dependencies python3 build-base
|
||||
|
||||
EXPOSE 3000
|
||||
ENTRYPOINT ["sh", "/entrypoint.sh"]
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM automatischio/automatisch:0.2.0
|
||||
FROM automatischio/automatisch:0.4.0
|
||||
WORKDIR /automatisch
|
||||
|
||||
RUN apk add --no-cache openssl dos2unix
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "0.3.0",
|
||||
"version": "0.5.0",
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"command": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@automatisch/backend",
|
||||
"version": "0.3.0",
|
||||
"version": "0.5.0",
|
||||
"license": "AGPL-3.0",
|
||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||
"scripts": {
|
||||
@@ -22,7 +22,7 @@
|
||||
"prebuild": "rm -rf ./dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@automatisch/web": "^0.3.0",
|
||||
"@automatisch/web": "^0.5.0",
|
||||
"@bull-board/express": "^3.10.1",
|
||||
"@graphql-tools/graphql-file-loader": "^7.3.4",
|
||||
"@graphql-tools/load": "^7.5.2",
|
||||
@@ -37,7 +37,7 @@
|
||||
"crypto-js": "^4.1.1",
|
||||
"debug": "~2.6.9",
|
||||
"dotenv": "^10.0.0",
|
||||
"express": "~4.16.1",
|
||||
"express": "~4.18.2",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"express-graphql": "^0.12.0",
|
||||
"fast-xml-parser": "^4.0.11",
|
||||
@@ -46,11 +46,12 @@
|
||||
"graphql-tools": "^8.2.0",
|
||||
"graphql-type-json": "^0.3.2",
|
||||
"http-errors": "~1.6.3",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"knex": "^0.95.11",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"knex": "^2.4.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"luxon": "2.3.1",
|
||||
"luxon": "2.5.2",
|
||||
"morgan": "^1.10.0",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"nodemailer": "6.7.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"objection": "^3.0.0",
|
||||
@@ -93,16 +94,17 @@
|
||||
"url": "https://github.com/automatisch/automatisch/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@automatisch/types": "^0.3.0",
|
||||
"@automatisch/types": "^0.5.0",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/bull": "^3.15.8",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/crypto-js": "^4.0.2",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/express": "^4.17.15",
|
||||
"@types/http-errors": "^1.8.1",
|
||||
"@types/jsonwebtoken": "^8.5.8",
|
||||
"@types/lodash.get": "^4.4.6",
|
||||
"@types/morgan": "^1.9.3",
|
||||
"@types/multer": "1.4.7",
|
||||
"@types/node": "^16.10.2",
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"@types/pg": "^8.6.1",
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import createError from 'http-errors';
|
||||
import express from 'express';
|
||||
import appConfig from './config/app';
|
||||
import cors from 'cors';
|
||||
import corsOptions from './config/cors-options';
|
||||
import morgan from './helpers/morgan';
|
||||
@@ -26,12 +27,19 @@ appAssetsHandler(app);
|
||||
app.use(morgan);
|
||||
app.use(
|
||||
express.json({
|
||||
verify: (req, res, buf) => {
|
||||
limit: appConfig.requestBodySizeLimit,
|
||||
verify(req, res, buf) {
|
||||
(req as IRequest).rawBody = buf;
|
||||
},
|
||||
})
|
||||
);
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
app.use(express.urlencoded({
|
||||
extended: false,
|
||||
limit: appConfig.requestBodySizeLimit,
|
||||
verify(req, res, buf) {
|
||||
(req as IRequest).rawBody = buf;
|
||||
},
|
||||
}));
|
||||
app.use(cors(corsOptions));
|
||||
app.use('/', router);
|
||||
|
||||
|
56
packages/backend/src/apps/delay/actions/delay-for/index.ts
Normal file
56
packages/backend/src/apps/delay/actions/delay-for/index.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Delay For',
|
||||
key: 'delayFor',
|
||||
description:
|
||||
'Delays the execution of the next action by a specified amount of time.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Delay for unit',
|
||||
key: 'delayForUnit',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
value: null,
|
||||
description: 'Delay for unit, e.g. minutes, hours, days, weeks.',
|
||||
variables: false,
|
||||
options: [
|
||||
{
|
||||
label: 'Minutes',
|
||||
value: 'minutes',
|
||||
},
|
||||
{
|
||||
label: 'Hours',
|
||||
value: 'hours',
|
||||
},
|
||||
{
|
||||
label: 'Days',
|
||||
value: 'days',
|
||||
},
|
||||
{
|
||||
label: 'Weeks',
|
||||
value: 'weeks',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Delay for value',
|
||||
key: 'delayForValue',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Delay for value, use a number, e.g. 1, 2, 3.',
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const { delayForUnit, delayForValue } = $.step.parameters;
|
||||
|
||||
const dataItem = {
|
||||
delayForUnit,
|
||||
delayForValue,
|
||||
};
|
||||
|
||||
$.setActionItem({ raw: dataItem });
|
||||
},
|
||||
});
|
28
packages/backend/src/apps/delay/actions/delay-until/index.ts
Normal file
28
packages/backend/src/apps/delay/actions/delay-until/index.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Delay Until',
|
||||
key: 'delayUntil',
|
||||
description:
|
||||
'Delays the execution of the next action until a specified date.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Delay until (Date)',
|
||||
key: 'delayUntil',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Delay until the date. E.g. 2022-12-18',
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const { delayUntil } = $.step.parameters;
|
||||
|
||||
const dataItem = {
|
||||
delayUntil,
|
||||
};
|
||||
|
||||
$.setActionItem({ raw: dataItem });
|
||||
},
|
||||
});
|
4
packages/backend/src/apps/delay/actions/index.ts
Normal file
4
packages/backend/src/apps/delay/actions/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import delayFor from './delay-for';
|
||||
import delayUntil from './delay-until';
|
||||
|
||||
export default [delayFor, delayUntil];
|
7
packages/backend/src/apps/delay/assets/favicon.svg
Normal file
7
packages/backend/src/apps/delay/assets/favicon.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 122.88 100.6" style="enable-background:new 0 0 122.88 100.6" xml:space="preserve">
|
||||
<style type="text/css">.st0{fill:#272727;} .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#000000;}</style>
|
||||
<g>
|
||||
<path class="st0" d="M72.58,0c6.8,0,13.3,1.36,19.23,3.81c6.16,2.55,11.7,6.29,16.33,10.92l0,0c4.63,4.63,8.37,10.17,10.92,16.34 c2.46,5.93,3.81,12.43,3.81,19.23c0,6.8-1.36,13.3-3.81,19.23c-2.55,6.16-6.29,11.7-10.92,16.33l0,0 c-4.63,4.63-10.17,8.37-16.34,10.92c-5.93,2.46-12.43,3.81-19.23,3.81c-6.8,0-13.3-1.36-19.23-3.81 c-6.15-2.55-11.69-6.28-16.33-10.92l-0.01-0.01c-4.64-4.64-8.37-10.17-10.92-16.33c-0.79-1.91-1.47-3.87-2.02-5.89 c1.05,0.1,2.12,0.15,3.2,0.15c2.05,0,4.05-0.19,6-0.54c0.32,0.97,0.67,1.93,1.06,2.87c2.09,5.05,5.17,9.6,8.99,13.43 c3.82,3.82,8.38,6.9,13.43,8.99c4.87,2.02,10.21,3.13,15.83,3.13c5.62,0,10.96-1.11,15.83-3.13c5.05-2.09,9.6-5.17,13.43-8.99 c3.82-3.82,6.9-8.38,8.99-13.43c2.02-4.87,3.13-10.21,3.13-15.83c0-5.62-1.11-10.96-3.13-15.83c-2.09-5.05-5.17-9.6-8.99-13.43 c-3.82-3.82-8.38-6.9-13.43-8.99c-4.87-2.02-10.21-3.13-15.83-3.13c-5.62,0-10.96,1.11-15.83,3.13c-0.44,0.18-0.87,0.37-1.3,0.56 c-1.65-2.61-3.66-4.97-5.95-7.02c1.25-0.65,2.53-1.24,3.84-1.79C59.28,1.36,65.78,0,72.58,0L72.58,0z M66.8,26.39 c0-1.23,0.5-2.35,1.31-3.16c0.81-0.81,1.93-1.31,3.16-1.31c1.23,0,2.35,0.5,3.16,1.31c0.81,0.81,1.31,1.93,1.31,3.16v23.47 l17.54,10.4c1.05,0.62,1.76,1.62,2.05,2.73c0.28,1.1,0.15,2.31-0.47,3.37l0,0.01l0,0c-0.62,1.05-1.62,1.76-2.73,2.05 c-1.1,0.28-2.31,0.15-3.37-0.47l-0.01,0l0,0L69.1,56.29c-0.67-0.38-1.24-0.92-1.64-1.57c-0.42-0.68-0.66-1.48-0.66-2.32V26.39 L66.8,26.39z"/>
|
||||
<path class="st1" d="M27.27,3.18c15.06,0,27.27,12.21,27.27,27.27c0,15.06-12.21,27.27-27.27,27.27C12.21,57.73,0,45.52,0,30.45 C0,15.39,12.21,3.18,27.27,3.18L27.27,3.18z M24.35,41.34h5.82v5.16h-5.82V41.34L24.35,41.34L24.35,41.34z M30.17,37.77h-5.82 c-0.58-7.07-1.8-11.56-1.8-18.63c0-2.61,2.12-4.72,4.72-4.72c2.61,0,4.72,2.12,4.72,4.72C32,26.2,30.76,30.7,30.17,37.77 L30.17,37.77L30.17,37.77L30.17,37.77z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
0
packages/backend/src/apps/delay/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/delay/index.d.ts
vendored
Normal file
14
packages/backend/src/apps/delay/index.ts
Normal file
14
packages/backend/src/apps/delay/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import actions from './actions';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Delay',
|
||||
key: 'delay',
|
||||
iconUrl: '{BASE_URL}/apps/delay/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/delay/connection',
|
||||
supportsConnections: false,
|
||||
baseUrl: '',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '001F52',
|
||||
actions,
|
||||
});
|
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="762.564" height="769.312" viewBox="0 0 772772" fill="none">
|
||||
<g clip-path="url(#clip0_493_7535)">
|
||||
<path d="M747.782 474.701C725.051 423.2 683.614 383.657 631.048 363.294L413.561 279.117L429.189 369.569L602.633 436.697C670.591 462.98 704.451 539.58 678.168 607.537C651.885 675.494 575.285 709.354 507.328 683.071C473.231 669.811 446.711 643.883 432.741 610.023C429.426 601.854 426.939 593.33 425.164 584.569L358.509 636.07C358.982 637.372 359.338 638.674 359.811 639.858C382.069 693.963 424.216 735.282 478.795 756.356C503.776 765.946 529.349 770.563 554.566 770.563C639.098 770.563 719.131 719.299 751.334 635.833C771.697 583.267 770.277 526.083 747.545 474.583L747.782 474.701Z" fill="#02AFC7"/>
|
||||
<path d="M440.91 419.881L294.459 533.064C287.119 538.628 279.187 543.482 270.781 547.507C237.631 563.254 200.693 564.556 166.477 551.414C133.564 538.628 107.636 513.884 93.3109 481.563C78.9854 449.242 78.1567 413.369 90.943 380.456C103.729 347.543 128.473 321.615 160.794 307.29C190.866 294.03 224.134 292.491 255.271 302.672L241.064 219.206C147.061 207.13 53.2943 259.815 17.6583 352.042C-2.70517 404.608 -1.28446 461.792 21.4468 513.292C44.1781 564.793 85.6154 604.336 138.181 624.699C162.57 634.171 188.38 638.788 214.189 638.788C245.208 638.788 276.227 632.039 304.641 618.543C318.019 612.268 330.687 604.454 342.29 595.575L527.81 452.083L440.91 419.762V419.881Z" fill="#31DEA4"/>
|
||||
<path d="M624.42 193.396C629.629 229.624 619.921 265.379 597.19 294.148C594.822 297.108 592.335 299.831 589.731 302.554L665.62 333.454C696.402 289.649 709.899 235.307 702.203 182.149C694.153 126.386 665.028 77.135 619.921 43.3933C574.932 9.76989 519.406 -4.43717 463.643 3.49511C407.88 11.5458 358.629 40.6703 324.888 85.7777C291.146 130.885 276.939 186.411 284.989 242.055L323.23 457.529L393.673 404.371L362.773 230.334C357.919 195.646 366.798 161.075 387.872 133.016C409.064 104.72 439.846 86.488 474.89 81.3972C509.935 76.4247 544.623 85.3041 572.801 106.378C601.096 127.57 619.329 158.47 624.42 193.396V193.396Z" fill="#FF5986"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_493_7535">
|
||||
<rect width="762.564" height="769.312" fill="white" transform="translate(3.33301 1.36719)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
43
packages/backend/src/apps/flowers-software/auth/index.ts
Normal file
43
packages/backend/src/apps/flowers-software/auth/index.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import verifyCredentials from './verify-credentials';
|
||||
import isStillVerified from './is-still-verified';
|
||||
|
||||
export default {
|
||||
fields: [
|
||||
{
|
||||
key: 'username',
|
||||
label: 'Username',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: null,
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'password',
|
||||
label: 'Password',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: null,
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'apiKey',
|
||||
label: 'API Key',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: null,
|
||||
clickToCopy: false,
|
||||
},
|
||||
],
|
||||
|
||||
verifyCredentials,
|
||||
isStillVerified,
|
||||
};
|
@@ -0,0 +1,10 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import verifyCredentials from './verify-credentials';
|
||||
|
||||
const isStillVerified = async ($: IGlobalVariable) => {
|
||||
await verifyCredentials($);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export default isStillVerified;
|
@@ -0,0 +1,20 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import getWebhooks from '../common/get-webhooks';
|
||||
|
||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
const response = await getWebhooks($);
|
||||
const successful = Array.isArray(response.data);
|
||||
|
||||
if (!successful) {
|
||||
throw new Error('Failed while authorizing!');
|
||||
}
|
||||
|
||||
await $.auth.set({
|
||||
screenName: $.auth.data.username,
|
||||
username: $.auth.data.username,
|
||||
password: $.auth.data.password,
|
||||
apiKey: $.auth.data.apiKey,
|
||||
});
|
||||
};
|
||||
|
||||
export default verifyCredentials;
|
@@ -0,0 +1,18 @@
|
||||
import { TBeforeRequest } from '@automatisch/types';
|
||||
|
||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||
const { data } = $.auth;
|
||||
|
||||
if (data?.username && data.password && data.apiKey) {
|
||||
requestConfig.headers['x-api-key'] = data.apiKey as string;
|
||||
|
||||
requestConfig.auth = {
|
||||
username: data.username as string,
|
||||
password: data.password as string,
|
||||
};
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default addAuthHeader;
|
@@ -0,0 +1,5 @@
|
||||
import type { IGlobalVariable } from "@automatisch/types";
|
||||
|
||||
export default async function getWebhooks($: IGlobalVariable) {
|
||||
return await $.http.get('/v2/public/api/webhooks');
|
||||
}
|
@@ -0,0 +1,488 @@
|
||||
const webhookFilters = [
|
||||
{
|
||||
label: "Contact Company Created",
|
||||
value: "CONTACT_COMPANY_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Contact Company Deleted",
|
||||
value: "CONTACT_COMPANY_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Contact Company Updated",
|
||||
value: "CONTACT_COMPANY_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Contact Created",
|
||||
value: "CONTACT_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Contact Deleted",
|
||||
value: "CONTACT_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Contact Updated",
|
||||
value: "CONTACT_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Customer Created",
|
||||
value: "CUSTOMER_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Customer Updated",
|
||||
value: "CUSTOMER_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Document Deleted",
|
||||
value: "DOCUMENT_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Document Downloaded",
|
||||
value: "DOCUMENT_DOWNLOADED"
|
||||
},
|
||||
{
|
||||
label: "Document Saved",
|
||||
value: "DOCUMENT_SAVED"
|
||||
},
|
||||
{
|
||||
label: "Document Updated",
|
||||
value: "DOCUMENT_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Archived",
|
||||
value: "FLOW_ARCHIVED"
|
||||
},
|
||||
{
|
||||
label: "Flow Created",
|
||||
value: "FLOW_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Automation Action Created",
|
||||
value: "FLOW_OBJECT_AUTOMATION_ACTION_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Automation Action Deleted",
|
||||
value: "FLOW_OBJECT_AUTOMATION_ACTION_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Automation Created",
|
||||
value: "FLOW_OBJECT_AUTOMATION_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Automation Deleted",
|
||||
value: "FLOW_OBJECT_AUTOMATION_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Automation Updated",
|
||||
value: "FLOW_OBJECT_AUTOMATION_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Automation Webdav Created",
|
||||
value: "FLOW_OBJECT_AUTOMATION_WEBDAV_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Automation Webdav Deleted",
|
||||
value: "FLOW_OBJECT_AUTOMATION_WEBDAV_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Automation Webdav Updated",
|
||||
value: "FLOW_OBJECT_AUTOMATION_WEBDAV_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Created",
|
||||
value: "FLOW_OBJECT_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Deleted",
|
||||
value: "FLOW_OBJECT_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Document Added",
|
||||
value: "FLOW_OBJECT_DOCUMENT_ADDED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Document Removed",
|
||||
value: "FLOW_OBJECT_DOCUMENT_REMOVED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Resource Created",
|
||||
value: "FLOW_OBJECT_RESOURCE_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Resource Deleted",
|
||||
value: "FLOW_OBJECT_RESOURCE_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Resource Updated",
|
||||
value: "FLOW_OBJECT_RESOURCE_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Task Condition Created",
|
||||
value: "FLOW_OBJECT_TASK_CONDITION_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Task Condition Deleted",
|
||||
value: "FLOW_OBJECT_TASK_CONDITION_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Task Condition Updated",
|
||||
value: "FLOW_OBJECT_TASK_CONDITION_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Task Created",
|
||||
value: "FLOW_OBJECT_TASK_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Task Deleted",
|
||||
value: "FLOW_OBJECT_TASK_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Task Updated",
|
||||
value: "FLOW_OBJECT_TASK_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Object Updated",
|
||||
value: "FLOW_OBJECT_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Objects Connection Created",
|
||||
value: "FLOW_OBJECTS_CONNECTION_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Objects Connection Deleted",
|
||||
value: "FLOW_OBJECTS_CONNECTION_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Flow Objects Connection Updated",
|
||||
value: "FLOW_OBJECTS_CONNECTION_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Objects External Connection Created",
|
||||
value: "FLOW_OBJECTS_EXTERNAL_CONNECTION_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Objects External Connection Deleted",
|
||||
value: "FLOW_OBJECTS_EXTERNAL_CONNECTION_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Flow Objects External Connection Updated",
|
||||
value: "FLOW_OBJECTS_EXTERNAL_CONNECTION_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Objects External Connections Group Created",
|
||||
value: "FLOW_OBJECTS_EXTERNAL_CONNECTIONS_GROUP_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Objects External Connections Group Deleted",
|
||||
value: "FLOW_OBJECTS_EXTERNAL_CONNECTIONS_GROUP_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Flow Objects External Connections Group Updated",
|
||||
value: "FLOW_OBJECTS_EXTERNAL_CONNECTIONS_GROUP_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Flow Unarchived",
|
||||
value: "FLOW_UNARCHIVED"
|
||||
},
|
||||
{
|
||||
label: "Flow Updated",
|
||||
value: "FLOW_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Note Created",
|
||||
value: "NOTE_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Note Deleted",
|
||||
value: "NOTE_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Note Updated",
|
||||
value: "NOTE_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Team Created",
|
||||
value: "TEAM_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Team Deleted",
|
||||
value: "TEAM_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Team Updated",
|
||||
value: "TEAM_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "User Added To Team",
|
||||
value: "USER_ADDED_TO_TEAM"
|
||||
},
|
||||
{
|
||||
label: "User Added To Teamleads",
|
||||
value: "USER_ADDED_TO_TEAMLEADS"
|
||||
},
|
||||
{
|
||||
label: "User Archived",
|
||||
value: "USER_ARCHIVED"
|
||||
},
|
||||
{
|
||||
label: "User Changed Password",
|
||||
value: "USER_CHANGED_PASSWORD"
|
||||
},
|
||||
{
|
||||
label: "User Created",
|
||||
value: "USER_CREATED"
|
||||
},
|
||||
{
|
||||
label: "User Forgot Password",
|
||||
value: "USER_FORGOT_PASSWORD"
|
||||
},
|
||||
{
|
||||
label: "User Invited",
|
||||
value: "USER_INVITED"
|
||||
},
|
||||
{
|
||||
label: "User Logged In",
|
||||
value: "USER_LOGGED_IN"
|
||||
},
|
||||
{
|
||||
label: "User Notification Settings Changed",
|
||||
value: "USER_NOTIFICATION_SETTINGS_CHANGED"
|
||||
},
|
||||
{
|
||||
label: "User Profile Updated",
|
||||
value: "USER_PROFILE_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "User Removed From Team",
|
||||
value: "USER_REMOVED_FROM_TEAM"
|
||||
},
|
||||
{
|
||||
label: "User Removed From Teamleads",
|
||||
value: "USER_REMOVED_FROM_TEAMLEADS"
|
||||
},
|
||||
{
|
||||
label: "User Unarchived",
|
||||
value: "USER_UNARCHIVED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Archived",
|
||||
value: "WORKFLOW_ARCHIVED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Completed",
|
||||
value: "WORKFLOW_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Created",
|
||||
value: "WORKFLOW_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Creation Failed",
|
||||
value: "WORKFLOW_CREATION_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Api Get Completed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_API_GET_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Api Get Failed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_API_GET_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Api Post Completed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_API_POST_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Api Post Failed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_API_POST_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Datev Completed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_DATEV_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Datev Failed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_DATEV_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Email Completed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_EMAIL_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Email Failed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_EMAIL_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Lexoffice Completed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_LEXOFFICE_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Lexoffice Failed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_LEXOFFICE_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Rejected",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_REJECTED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Retried",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_RETRIED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Sevdesk Completed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_SEVDESK_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Sevdesk Failed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_SEVDESK_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Stamp Completed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_STAMP_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Stamp Failed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_STAMP_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Task Completed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_TASK_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Task Failed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_TASK_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Template Completed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_TEMPLATE_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Template Failed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_TEMPLATE_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Webdav Document Uploaded",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_WEBDAV_DOCUMENT_UPLOADED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Zapier Completed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_ZAPIER_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Automation Zapier Failed",
|
||||
value: "WORKFLOW_OBJECT_AUTOMATION_ZAPIER_FAILED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Combination Task Group Created",
|
||||
value: "WORKFLOW_OBJECT_COMBINATION_TASK_GROUP_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Combination Task Group Deleted",
|
||||
value: "WORKFLOW_OBJECT_COMBINATION_TASK_GROUP_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Completed Automations Finished",
|
||||
value: "WORKFLOW_OBJECT_COMPLETED_AUTOMATIONS_FINISHED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Completed",
|
||||
value: "WORKFLOW_OBJECT_COMPLETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Created",
|
||||
value: "WORKFLOW_OBJECT_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Document Added",
|
||||
value: "WORKFLOW_OBJECT_DOCUMENT_ADDED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Document Lock Added",
|
||||
value: "WORKFLOW_OBJECT_DOCUMENT_LOCK_ADDED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Document Lock Deleted",
|
||||
value: "WORKFLOW_OBJECT_DOCUMENT_LOCK_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Document Removed",
|
||||
value: "WORKFLOW_OBJECT_DOCUMENT_REMOVED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Email Added",
|
||||
value: "WORKFLOW_OBJECT_EMAIL_ADDED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Email Removed",
|
||||
value: "WORKFLOW_OBJECT_EMAIL_REMOVED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object External User Created",
|
||||
value: "WORKFLOW_OBJECT_EXTERNAL_USER_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object External User Deleted",
|
||||
value: "WORKFLOW_OBJECT_EXTERNAL_USER_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Note Added",
|
||||
value: "WORKFLOW_OBJECT_NOTE_ADDED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Note Removed",
|
||||
value: "WORKFLOW_OBJECT_NOTE_REMOVED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Resource Created",
|
||||
value: "WORKFLOW_OBJECT_RESOURCE_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Snapshot Created",
|
||||
value: "WORKFLOW_OBJECT_SNAPSHOT_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Task Condition Created",
|
||||
value: "WORKFLOW_OBJECT_TASK_CONDITION_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Task Created",
|
||||
value: "WORKFLOW_OBJECT_TASK_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Task Deleted",
|
||||
value: "WORKFLOW_OBJECT_TASK_DELETED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Task Snapshot Created",
|
||||
value: "WORKFLOW_OBJECT_TASK_SNAPSHOT_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Task Updated",
|
||||
value: "WORKFLOW_OBJECT_TASK_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Object Updated",
|
||||
value: "WORKFLOW_OBJECT_UPDATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Objects Connection Created",
|
||||
value: "WORKFLOW_OBJECTS_CONNECTION_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Objects External Connection Created",
|
||||
value: "WORKFLOW_OBJECTS_EXTERNAL_CONNECTION_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Objects External Connection Group Created",
|
||||
value: "WORKFLOW_OBJECTS_EXTERNAL_CONNECTION_GROUP_CREATED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Unarchived",
|
||||
value: "WORKFLOW_UNARCHIVED"
|
||||
},
|
||||
{
|
||||
label: "Workflow Updated",
|
||||
value: "WORKFLOW_UPDATED"
|
||||
}
|
||||
];
|
||||
|
||||
export default webhookFilters;
|
0
packages/backend/src/apps/flowers-software/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/flowers-software/index.d.ts
vendored
Normal file
18
packages/backend/src/apps/flowers-software/index.ts
Normal file
18
packages/backend/src/apps/flowers-software/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import addAuthHeader from './common/add-auth-header';
|
||||
import auth from './auth';
|
||||
import triggers from './triggers';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Flowers Software',
|
||||
key: 'flowers-software',
|
||||
iconUrl: '{BASE_URL}/apps/flowers-software/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/flowers-software/connection',
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://flowers-software.com',
|
||||
apiBaseUrl: 'https://webapp.flowers-software.com/api',
|
||||
primaryColor: '02AFC7',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
});
|
@@ -0,0 +1,3 @@
|
||||
import newActivity from './new-activity';
|
||||
|
||||
export default [newActivity];
|
@@ -0,0 +1,54 @@
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
import webhookFilters from '../../common/webhook-filters';
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'New activity',
|
||||
key: 'newActivity',
|
||||
type: 'webhook',
|
||||
description: 'Triggers when a new activity occurs.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Activity type',
|
||||
key: 'filters',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
description: 'Pick an activity type to receive events for.',
|
||||
variables: false,
|
||||
options: webhookFilters,
|
||||
},
|
||||
],
|
||||
|
||||
async testRun($) {
|
||||
if (!isEmpty($.lastExecutionStep?.dataOut)) {
|
||||
$.pushTriggerItem({
|
||||
raw: $.lastExecutionStep.dataOut,
|
||||
meta: {
|
||||
internalId: '',
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async registerHook($) {
|
||||
const payload = {
|
||||
name: $.flow.id,
|
||||
type: 'POST',
|
||||
url: $.webhookUrl,
|
||||
filters: [$.step.parameters.filters]
|
||||
};
|
||||
|
||||
const { data } = await $.http.post(
|
||||
`/v2/public/api/webhooks`,
|
||||
payload
|
||||
);
|
||||
|
||||
await $.flow.setRemoteWebhookId(data.id);
|
||||
},
|
||||
|
||||
async unregisterHook($) {
|
||||
await $.http.delete(
|
||||
`/v2/public/api/webhooks/${$.flow.remoteWebhookId}`
|
||||
);
|
||||
},
|
||||
});
|
@@ -13,9 +13,8 @@ export default async function generateAuthUrl($: IGlobalVariable) {
|
||||
scope: scopes.join(','),
|
||||
});
|
||||
|
||||
const url = `${
|
||||
$.app.baseUrl
|
||||
}/login/oauth/authorize?${searchParams.toString()}`;
|
||||
const url = `${$.app.baseUrl
|
||||
}/login/oauth/authorize?${searchParams.toString()}`;
|
||||
|
||||
await $.auth.set({
|
||||
url,
|
||||
|
41
packages/backend/src/apps/google-forms/assets/favicon.svg
Normal file
41
packages/backend/src/apps/google-forms/assets/favicon.svg
Normal file
@@ -0,0 +1,41 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1904.8 2500" style="enable-background:new 0 0 1904.8 2500;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#673AB7;}
|
||||
.st1{fill:#F1F1F1;}
|
||||
.st2{fill:url(#SVGID_1_);}
|
||||
.st3{fill:#B39DDB;}
|
||||
.st4{fill:#FFFFFF;fill-opacity:0.2;}
|
||||
.st5{fill:#311B92;fill-opacity:0.2;}
|
||||
.st6{fill:#311B92;fill-opacity:0.1;}
|
||||
.st7{fill:url(#SVGID_2_);}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M1190.5,0H178.6C83.3,0,0,83.3,0,178.6v2142.9c0,95.2,83.3,178.6,178.6,178.6h1547.6
|
||||
c95.2,0,178.6-83.3,178.6-178.6V714.3l-416.7-297.6L1190.5,0z"/>
|
||||
<path class="st1" d="M714.3,1845.2h714.3v-119H714.3V1845.2z M714.3,1071.4v119h714.3v-119H714.3z M607.1,1131
|
||||
c0,47.6-35.7,95.2-95.2,95.2s-95.2-35.7-95.2-95.2c0-59.5,35.7-95.2,95.2-95.2S607.1,1083.3,607.1,1131z M607.1,1464.3
|
||||
c0,47.6-35.7,95.2-95.2,95.2s-95.2-35.7-95.2-95.2c0-59.5,35.7-95.2,95.2-95.2S607.1,1416.7,607.1,1464.3z M607.1,1785.7
|
||||
c0,47.6-35.7,95.2-95.2,95.2s-95.2-35.7-95.2-95.2c0-59.5,35.7-95.2,95.2-95.2S607.1,1738.1,607.1,1785.7z M714.3,1547.6h714.3
|
||||
v-119.1H714.3L714.3,1547.6L714.3,1547.6z"/>
|
||||
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="122.5479" y1="1635.2429" x2="122.5479" y2="1634.3186" gradientTransform="matrix(666.67 0 0 -654.7559 -80127.6016 1071403.75)">
|
||||
<stop offset="0" style="stop-color:#311B92;stop-opacity:0.2"/>
|
||||
<stop offset="1" style="stop-color:#311B92;stop-opacity:2.000000e-02"/>
|
||||
</linearGradient>
|
||||
<path class="st2" d="M1238.1,666.7l666.7,654.8V714.3L1238.1,666.7z"/>
|
||||
<path class="st3" d="M1190.5,0v535.7c0,95.2,83.3,178.6,178.6,178.6h535.7L1190.5,0z"/>
|
||||
<path class="st4" d="M178.6,0C83.3,0,0,83.3,0,178.6v11.9C0,95.2,83.3,11.9,178.6,11.9h1011.9V0H178.6L178.6,0z"/>
|
||||
<path class="st5" d="M1726.2,2488.1H178.6C83.3,2488.1,0,2404.8,0,2309.5v11.9c0,95.2,83.3,178.6,178.6,178.6h1547.6
|
||||
c95.2,0,178.6-83.3,178.6-178.6v-11.9C1904.8,2404.8,1821.4,2488.1,1726.2,2488.1z"/>
|
||||
<path class="st6" d="M1369,714.3c-95.2,0-178.6-83.3-178.6-178.6v11.9c0,95.2,83.3,178.6,178.6,178.6h535.7v-11.9L1369,714.3
|
||||
L1369,714.3z"/>
|
||||
|
||||
<radialGradient id="SVGID_2_" cx="122.6324" cy="1634.4275" r="12.899" gradientTransform="matrix(1904.7655 0 0 -1904.75 -233525.7031 3113242.5)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.1"/>
|
||||
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
|
||||
</radialGradient>
|
||||
<path class="st7" d="M1190.5,0H178.6C83.3,0,0,83.3,0,178.6v2142.9c0,95.2,83.3,178.6,178.6,178.6h1547.6
|
||||
c95.2,0,178.6-83.3,178.6-178.6V714.3L1190.5,0z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,24 @@
|
||||
import { IField, IGlobalVariable } from '@automatisch/types';
|
||||
import { URLSearchParams } from 'url';
|
||||
import authScope from '../common/auth-scope';
|
||||
|
||||
export default async function generateAuthUrl($: IGlobalVariable) {
|
||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
||||
);
|
||||
const redirectUri = oauthRedirectUrlField.value as string;
|
||||
const searchParams = new URLSearchParams({
|
||||
client_id: $.auth.data.clientId as string,
|
||||
redirect_uri: redirectUri,
|
||||
prompt: 'select_account',
|
||||
scope: authScope.join(' '),
|
||||
response_type: 'code',
|
||||
access_type: 'offline',
|
||||
});
|
||||
|
||||
const url = `https://accounts.google.com/o/oauth2/v2/auth?${searchParams.toString()}`;
|
||||
|
||||
await $.auth.set({
|
||||
url,
|
||||
});
|
||||
}
|
48
packages/backend/src/apps/google-forms/auth/index.ts
Normal file
48
packages/backend/src/apps/google-forms/auth/index.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import generateAuthUrl from './generate-auth-url';
|
||||
import verifyCredentials from './verify-credentials';
|
||||
import refreshToken from './refresh-token';
|
||||
import isStillVerified from './is-still-verified';
|
||||
|
||||
export default {
|
||||
fields: [
|
||||
{
|
||||
key: 'oAuthRedirectUrl',
|
||||
label: 'OAuth Redirect URL',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: true,
|
||||
value: '{WEB_APP_URL}/app/google-forms/connections/add',
|
||||
placeholder: null,
|
||||
description:
|
||||
'When asked to input a redirect URL in Google Cloud, 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,
|
||||
};
|
@@ -0,0 +1,9 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import getCurrentUser from '../common/get-current-user';
|
||||
|
||||
const isStillVerified = async ($: IGlobalVariable) => {
|
||||
const currentUser = await getCurrentUser($);
|
||||
return !!currentUser.resourceName;
|
||||
};
|
||||
|
||||
export default isStillVerified;
|
26
packages/backend/src/apps/google-forms/auth/refresh-token.ts
Normal file
26
packages/backend/src/apps/google-forms/auth/refresh-token.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { URLSearchParams } from 'node:url';
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import authScope from '../common/auth-scope';
|
||||
|
||||
const refreshToken = async ($: IGlobalVariable) => {
|
||||
const params = new URLSearchParams({
|
||||
client_id: $.auth.data.clientId as string,
|
||||
client_secret: $.auth.data.clientSecret as string,
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: $.auth.data.refreshToken as string,
|
||||
});
|
||||
|
||||
const { data } = await $.http.post(
|
||||
'https://oauth2.googleapis.com/token',
|
||||
params.toString()
|
||||
);
|
||||
|
||||
await $.auth.set({
|
||||
accessToken: data.access_token,
|
||||
expiresIn: data.expires_in,
|
||||
scope: authScope.join(' '),
|
||||
tokenType: data.token_type,
|
||||
});
|
||||
};
|
||||
|
||||
export default refreshToken;
|
@@ -0,0 +1,57 @@
|
||||
import { IField, IGlobalVariable } from '@automatisch/types';
|
||||
import getCurrentUser from '../common/get-current-user';
|
||||
|
||||
type TUser = {
|
||||
displayName: string;
|
||||
metadata: {
|
||||
primary: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
type TEmailAddress = {
|
||||
value: string;
|
||||
metadata: {
|
||||
primary: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
||||
);
|
||||
const redirectUri = oauthRedirectUrlField.value as string;
|
||||
const { data } = await $.http.post(`https://oauth2.googleapis.com/token`, {
|
||||
client_id: $.auth.data.clientId,
|
||||
client_secret: $.auth.data.clientSecret,
|
||||
code: $.auth.data.code,
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri: redirectUri,
|
||||
});
|
||||
|
||||
await $.auth.set({
|
||||
accessToken: data.access_token,
|
||||
tokenType: data.token_type,
|
||||
});
|
||||
|
||||
const currentUser = await getCurrentUser($);
|
||||
|
||||
const { displayName } = currentUser.names.find(
|
||||
(name: TUser) => name.metadata.primary
|
||||
);
|
||||
const { value: email } = currentUser.emailAddresses.find(
|
||||
(emailAddress: TEmailAddress) => emailAddress.metadata.primary
|
||||
);
|
||||
|
||||
await $.auth.set({
|
||||
clientId: $.auth.data.clientId,
|
||||
clientSecret: $.auth.data.clientSecret,
|
||||
scope: $.auth.data.scope,
|
||||
idToken: data.id_token,
|
||||
expiresIn: data.expires_in,
|
||||
refreshToken: data.refresh_token,
|
||||
resourceName: currentUser.resourceName,
|
||||
screenName: `${displayName} - ${email}`,
|
||||
});
|
||||
};
|
||||
|
||||
export default verifyCredentials;
|
@@ -0,0 +1,11 @@
|
||||
import { TBeforeRequest } from '@automatisch/types';
|
||||
|
||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||
if (requestConfig.headers && $.auth.data?.accessToken) {
|
||||
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default addAuthHeader;
|
@@ -0,0 +1,9 @@
|
||||
const authScope: string[] = [
|
||||
'https://www.googleapis.com/auth/forms.body.readonly',
|
||||
'https://www.googleapis.com/auth/forms.responses.readonly',
|
||||
'https://www.googleapis.com/auth/drive.readonly',
|
||||
'https://www.googleapis.com/auth/userinfo.email',
|
||||
'profile',
|
||||
];
|
||||
|
||||
export default authScope;
|
@@ -0,0 +1,10 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const getCurrentUser = async ($: IGlobalVariable) => {
|
||||
const { data: currentUser } = await $.http.get(
|
||||
'https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses'
|
||||
);
|
||||
return currentUser;
|
||||
};
|
||||
|
||||
export default getCurrentUser;
|
@@ -0,0 +1,3 @@
|
||||
import listForms from './list-forms';
|
||||
|
||||
export default [listForms];
|
@@ -0,0 +1,34 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
|
||||
export default {
|
||||
name: 'List forms',
|
||||
key: 'listForms',
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
const forms: {
|
||||
data: IJSONObject[];
|
||||
} = {
|
||||
data: [],
|
||||
};
|
||||
|
||||
const params = {
|
||||
q: `mimeType='application/vnd.google-apps.form'`,
|
||||
spaces: 'drive',
|
||||
pageToken: undefined as unknown as string,
|
||||
};
|
||||
|
||||
do {
|
||||
const { data } = await $.http.get(`https://www.googleapis.com/drive/v3/files`, { params });
|
||||
params.pageToken = data.nextPageToken;
|
||||
|
||||
for (const file of data.files) {
|
||||
forms.data.push({
|
||||
value: file.id,
|
||||
name: file.name,
|
||||
});
|
||||
}
|
||||
} while (params.pageToken);
|
||||
|
||||
return forms;
|
||||
},
|
||||
};
|
0
packages/backend/src/apps/google-forms/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/google-forms/index.d.ts
vendored
Normal file
20
packages/backend/src/apps/google-forms/index.ts
Normal file
20
packages/backend/src/apps/google-forms/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: 'Google Forms',
|
||||
key: 'google-forms',
|
||||
baseUrl: 'https://docs.google.com/forms',
|
||||
apiBaseUrl: 'https://forms.googleapis.com',
|
||||
iconUrl: '{BASE_URL}/apps/google-forms/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/google-forms/connection',
|
||||
primaryColor: '673AB7',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
dynamicData,
|
||||
});
|
3
packages/backend/src/apps/google-forms/triggers/index.ts
Normal file
3
packages/backend/src/apps/google-forms/triggers/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import newFormResponses from './new-form-responses';
|
||||
|
||||
export default [newFormResponses];
|
@@ -0,0 +1,33 @@
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
import newFormResponses from './new-form-responses';
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'New Form Responses',
|
||||
key: 'newFormResponses',
|
||||
pollInterval: 15,
|
||||
description: 'Triggers when a new form response is submitted.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Form',
|
||||
key: 'formId',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
description: 'Pick a form to receive form responses.',
|
||||
variables: false,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listForms',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
await newFormResponses($);
|
||||
},
|
||||
});
|
@@ -0,0 +1,28 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const newResponses = async ($: IGlobalVariable) => {
|
||||
const params = {
|
||||
pageToken: undefined as unknown as string,
|
||||
};
|
||||
|
||||
do {
|
||||
const pathname = `/v1/forms/${$.step.parameters.formId}/responses`;
|
||||
const { data } = await $.http.get(pathname, { params });
|
||||
params.pageToken = data.nextPageToken;
|
||||
|
||||
if (data.responses?.length) {
|
||||
for (const formResponse of data.responses) {
|
||||
const dataItem = {
|
||||
raw: formResponse,
|
||||
meta: {
|
||||
internalId: formResponse.responseId,
|
||||
},
|
||||
};
|
||||
|
||||
$.pushTriggerItem(dataItem);
|
||||
}
|
||||
}
|
||||
} while (params.pageToken);
|
||||
};
|
||||
|
||||
export default newResponses;
|
@@ -0,0 +1,74 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
type TMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Custom Request',
|
||||
key: 'customRequest',
|
||||
description: 'Makes a custom HTTP request by providing raw details.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Method',
|
||||
key: 'method',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
description: `The HTTP method we'll use to perform the request.`,
|
||||
value: 'GET',
|
||||
options: [
|
||||
{ label: 'DELETE', value: 'DELETE' },
|
||||
{ label: 'GET', value: 'GET' },
|
||||
{ label: 'PATCH', value: 'PATCH' },
|
||||
{ label: 'POST', value: 'POST' },
|
||||
{ label: 'PUT', value: 'PUT' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'URL',
|
||||
key: 'url',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Any URL with a querystring will be re-encoded properly.',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Data',
|
||||
key: 'data',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
description: 'Place raw JSON data here.',
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const method = $.step.parameters.method as TMethod;
|
||||
const data = $.step.parameters.data as string;
|
||||
const url = $.step.parameters.url as string;
|
||||
const maxFileSize = 25 * 1024 * 1024; // 25MB
|
||||
|
||||
const metadataResponse = await $.http.head(url);
|
||||
|
||||
if (Number(metadataResponse.headers['content-length']) > maxFileSize) {
|
||||
throw new Error(
|
||||
`Response is too large. Maximum size is 25MB. Actual size is ${metadataResponse.headers['content-length']}`
|
||||
);
|
||||
}
|
||||
|
||||
const response = await $.http.request({
|
||||
url,
|
||||
method,
|
||||
data,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
let responseData = response.data;
|
||||
|
||||
if (typeof response.data === 'string') {
|
||||
responseData = response.data.replaceAll('\u0000', '');
|
||||
}
|
||||
|
||||
$.setActionItem({ raw: { data: responseData } });
|
||||
},
|
||||
});
|
3
packages/backend/src/apps/http-request/actions/index.ts
Normal file
3
packages/backend/src/apps/http-request/actions/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import customRequest from './custom-request';
|
||||
|
||||
export default [customRequest];
|
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="100px" height="100px"><path d="M 37.09375 0.09375 C 36.316406 0.167969 35.652344 0.691406 35.398438 1.429688 C 35.140625 2.171875 35.339844 2.992188 35.90625 3.53125 L 42.375 10 L 2 10 C 1.9375 9.996094 1.875 9.996094 1.8125 10 C 0.707031 10.050781 -0.144531 10.988281 -0.09375 12.09375 C -0.0429688 13.199219 0.894531 14.050781 2 14 L 42.375 14 L 35.90625 20.46875 C 35.382813 20.96875 35.167969 21.710938 35.347656 22.414063 C 35.527344 23.113281 36.070313 23.664063 36.769531 23.851563 C 37.46875 24.039063 38.214844 23.832031 38.71875 23.3125 L 50.03125 12 L 38.71875 0.6875 C 38.296875 0.253906 37.699219 0.0351563 37.09375 0.09375 Z M 12.5 26.09375 C 12.046875 26.152344 11.628906 26.359375 11.3125 26.6875 L 0 38 L 11.3125 49.3125 C 11.816406 49.832031 12.5625 50.039063 13.261719 49.851563 C 13.960938 49.664063 14.503906 49.113281 14.683594 48.414063 C 14.863281 47.710938 14.648438 46.96875 14.125 46.46875 L 7.65625 40 L 48 40 C 48.722656 40.011719 49.390625 39.632813 49.753906 39.007813 C 50.121094 38.386719 50.121094 37.613281 49.753906 36.992188 C 49.390625 36.367188 48.722656 35.988281 48 36 L 7.65625 36 L 14.125 29.53125 C 14.753906 28.9375 14.929688 28.003906 14.558594 27.222656 C 14.1875 26.441406 13.359375 25.984375 12.5 26.09375 Z"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
0
packages/backend/src/apps/http-request/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/http-request/index.d.ts
vendored
Normal file
14
packages/backend/src/apps/http-request/index.ts
Normal file
14
packages/backend/src/apps/http-request/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import actions from './actions';
|
||||
|
||||
export default defineApp({
|
||||
name: 'HTTP Request',
|
||||
key: 'http-request',
|
||||
iconUrl: '{BASE_URL}/apps/http-request/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/http-request/connection',
|
||||
supportsConnections: false,
|
||||
baseUrl: '',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '000000',
|
||||
actions,
|
||||
});
|
@@ -0,0 +1,29 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Check moderation',
|
||||
key: 'checkModeration',
|
||||
description: 'Checks for hate, hate/threatening, self-harm, sexual, sexual/minors, violence, or violence/graphic content in the given text.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Input',
|
||||
key: 'input',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
description: 'The text to analyze.'
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const { data } = await $.http.post('/v1/moderations', {
|
||||
input: $.step.parameters.input as string,
|
||||
});
|
||||
|
||||
const result = data?.results[0];
|
||||
|
||||
$.setActionItem({
|
||||
raw: result,
|
||||
});
|
||||
},
|
||||
});
|
4
packages/backend/src/apps/openai/actions/index.ts
Normal file
4
packages/backend/src/apps/openai/actions/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import checkModeration from './check-moderation';
|
||||
import sendPrompt from './send-prompt';
|
||||
|
||||
export default [checkModeration, sendPrompt];
|
104
packages/backend/src/apps/openai/actions/send-prompt/index.ts
Normal file
104
packages/backend/src/apps/openai/actions/send-prompt/index.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
const castFloatOrUndefined = (value: string | null) => {
|
||||
return value === '' ? undefined : parseFloat(value);
|
||||
}
|
||||
|
||||
export default defineAction({
|
||||
name: 'Send prompt',
|
||||
key: 'sendPrompt',
|
||||
description: 'Creates a completion for the provided prompt and parameters.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Model',
|
||||
key: 'model',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
variables: false,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listModels',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Prompt',
|
||||
key: 'prompt',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
description: 'The text to analyze.'
|
||||
},
|
||||
{
|
||||
label: 'Temperature',
|
||||
key: 'temperature',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: 'What sampling temperature to use. Higher values mean the model will take more risk. Try 0.9 for more creative applications, and 0 for ones with a well-defined answer. We generally recommend altering this or Top P but not both.'
|
||||
},
|
||||
{
|
||||
label: 'Maximum tokens',
|
||||
key: 'maxTokens',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: 'The maximum number of tokens to generate in the completion.'
|
||||
},
|
||||
{
|
||||
label: 'Stop Sequence',
|
||||
key: 'stopSequence',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: 'Single stop sequence where the API will stop generating further tokens. The returned text will not contain the stop sequence.'
|
||||
},
|
||||
{
|
||||
label: 'Top P',
|
||||
key: 'topP',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: 'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with Top P probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.'
|
||||
},
|
||||
{
|
||||
label: 'Frequency Penalty',
|
||||
key: 'frequencyPenalty',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: `Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.`
|
||||
},
|
||||
{
|
||||
label: 'presencePenalty',
|
||||
key: 'presencePenalty',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description: `Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.`
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const payload = {
|
||||
model: $.step.parameters.model as string,
|
||||
prompt: $.step.parameters.prompt as string,
|
||||
temperature: castFloatOrUndefined($.step.parameters.temperature as string),
|
||||
max_tokens: castFloatOrUndefined($.step.parameters.maxTokens as string),
|
||||
stop: ($.step.parameters.stopSequence as string || null),
|
||||
top_p: castFloatOrUndefined($.step.parameters.topP as string),
|
||||
frequency_penalty: castFloatOrUndefined($.step.parameters.frequencyPenalty as string),
|
||||
presence_penalty: castFloatOrUndefined($.step.parameters.presencePenalty as string),
|
||||
};
|
||||
const { data } = await $.http.post('/v1/completions', payload);
|
||||
|
||||
$.setActionItem({
|
||||
raw: data,
|
||||
});
|
||||
},
|
||||
});
|
6
packages/backend/src/apps/openai/assets/favicon.svg
Normal file
6
packages/backend/src/apps/openai/assets/favicon.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="256px" height="260px" viewBox="0 0 256 260" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<title>OpenAI</title>
|
||||
<g>
|
||||
<path d="M239.183914,106.202783 C245.054304,88.5242096 243.02228,69.1733805 233.607599,53.0998864 C219.451678,28.4588021 190.999703,15.7836129 163.213007,21.739505 C147.554077,4.32145883 123.794909,-3.42398554 100.87901,1.41873898 C77.9631105,6.26146349 59.3690093,22.9572536 52.0959621,45.2214219 C33.8436494,48.9644867 18.0901721,60.392749 8.86672513,76.5818033 C-5.443491,101.182962 -2.19544431,132.215255 16.8986662,153.320094 C11.0060865,170.990656 13.0197283,190.343991 22.4238231,206.422991 C36.5975553,231.072344 65.0680342,243.746566 92.8695738,237.783372 C105.235639,251.708249 123.001113,259.630942 141.623968,259.52692 C170.105359,259.552169 195.337611,241.165718 204.037777,214.045661 C222.28734,210.296356 238.038489,198.869783 247.267014,182.68528 C261.404453,158.127515 258.142494,127.262775 239.183914,106.202783 L239.183914,106.202783 Z M141.623968,242.541207 C130.255682,242.559177 119.243876,238.574642 110.519381,231.286197 L112.054146,230.416496 L163.724595,200.590881 C166.340648,199.056444 167.954321,196.256818 167.970781,193.224005 L167.970781,120.373788 L189.815614,133.010026 C190.034132,133.121423 190.186235,133.330564 190.224885,133.572774 L190.224885,193.940229 C190.168603,220.758427 168.442166,242.484864 141.623968,242.541207 Z M37.1575749,197.93062 C31.456498,188.086359 29.4094818,176.546984 31.3766237,165.342426 L32.9113895,166.263285 L84.6329973,196.088901 C87.2389349,197.618207 90.4682717,197.618207 93.0742093,196.088901 L156.255402,159.663793 L156.255402,184.885111 C156.243557,185.149771 156.111725,185.394602 155.89729,185.550176 L103.561776,215.733903 C80.3054953,229.131632 50.5924954,221.165435 37.1575749,197.93062 Z M23.5493181,85.3811273 C29.2899861,75.4733097 38.3511911,67.9162648 49.1287482,64.0478825 L49.1287482,125.438515 C49.0891492,128.459425 50.6965386,131.262556 53.3237748,132.754232 L116.198014,169.025864 L94.3531808,181.662102 C94.1132325,181.789434 93.8257461,181.789434 93.5857979,181.662102 L41.3526015,151.529534 C18.1419426,138.076098 10.1817681,108.385562 23.5493181,85.125333 L23.5493181,85.3811273 Z M203.0146,127.075598 L139.935725,90.4458545 L161.7294,77.8607748 C161.969348,77.7334434 162.256834,77.7334434 162.496783,77.8607748 L214.729979,108.044502 C231.032329,117.451747 240.437294,135.426109 238.871504,154.182739 C237.305714,172.939368 225.050719,189.105572 207.414262,195.67963 L207.414262,134.288998 C207.322521,131.276867 205.650697,128.535853 203.0146,127.075598 Z M224.757116,94.3850867 L223.22235,93.4642272 L171.60306,63.3828173 C168.981293,61.8443751 165.732456,61.8443751 163.110689,63.3828173 L99.9806554,99.8079259 L99.9806554,74.5866077 C99.9533004,74.3254088 100.071095,74.0701869 100.287609,73.9215426 L152.520805,43.7889738 C168.863098,34.3743518 189.174256,35.2529043 204.642579,46.0434841 C220.110903,56.8340638 227.949269,75.5923959 224.757116,94.1804513 L224.757116,94.3850867 Z M88.0606409,139.097931 L66.2158076,126.512851 C65.9950399,126.379091 65.8450965,126.154176 65.8065367,125.898945 L65.8065367,65.684966 C65.8314495,46.8285367 76.7500605,29.6846032 93.8270852,21.6883055 C110.90411,13.6920079 131.063833,16.2835462 145.5632,28.338998 L144.028434,29.2086986 L92.3579852,59.0343142 C89.7419327,60.5687513 88.1282597,63.3683767 88.1117998,66.4011901 L88.0606409,139.097931 Z M99.9294965,113.5185 L128.06687,97.3011417 L156.255402,113.5185 L156.255402,145.953218 L128.169187,162.170577 L99.9806554,145.953218 L99.9294965,113.5185 Z" fill="#000000"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.6 KiB |
34
packages/backend/src/apps/openai/auth/index.ts
Normal file
34
packages/backend/src/apps/openai/auth/index.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import verifyCredentials from './verify-credentials';
|
||||
import isStillVerified from './is-still-verified';
|
||||
|
||||
export default {
|
||||
fields: [
|
||||
{
|
||||
key: 'screenName',
|
||||
label: 'Screen Name',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description:
|
||||
'Screen name of your connection to be used on Automatisch UI.',
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'apiKey',
|
||||
label: 'API Key',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: 'OpenAI API key of your account.',
|
||||
docUrl: 'https://automatisch.io/docs/openai#api-key',
|
||||
clickToCopy: false,
|
||||
},
|
||||
],
|
||||
|
||||
verifyCredentials,
|
||||
isStillVerified,
|
||||
};
|
@@ -0,0 +1,8 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const isStillVerified = async ($: IGlobalVariable) => {
|
||||
await $.http.get('/v1/models');
|
||||
return true;
|
||||
};
|
||||
|
||||
export default isStillVerified;
|
@@ -0,0 +1,7 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
await $.http.get('/v1/models');
|
||||
};
|
||||
|
||||
export default verifyCredentials;
|
11
packages/backend/src/apps/openai/common/add-auth-header.ts
Normal file
11
packages/backend/src/apps/openai/common/add-auth-header.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { TBeforeRequest } from '@automatisch/types';
|
||||
|
||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||
if ($.auth.data?.apiKey) {
|
||||
requestConfig.headers.Authorization = `Bearer ${$.auth.data.apiKey}`;
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default addAuthHeader;
|
3
packages/backend/src/apps/openai/dynamic-data/index.ts
Normal file
3
packages/backend/src/apps/openai/dynamic-data/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import listModels from './list-models';
|
||||
|
||||
export default [listModels];
|
@@ -0,0 +1,19 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
export default {
|
||||
name: 'List models',
|
||||
key: 'listModels',
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
const response = await $.http.get('/v1/models');
|
||||
|
||||
const models = response.data.data.map((model: { id: string }) => {
|
||||
return {
|
||||
value: model.id,
|
||||
name: model.id,
|
||||
};
|
||||
});
|
||||
|
||||
return { data: models };
|
||||
},
|
||||
};
|
0
packages/backend/src/apps/openai/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/openai/index.d.ts
vendored
Normal file
20
packages/backend/src/apps/openai/index.ts
Normal file
20
packages/backend/src/apps/openai/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 actions from './actions';
|
||||
import dynamicData from './dynamic-data';
|
||||
|
||||
export default defineApp({
|
||||
name: 'OpenAI',
|
||||
key: 'openai',
|
||||
baseUrl: 'https://openai.com',
|
||||
apiBaseUrl: 'https://api.openai.com',
|
||||
iconUrl: '{BASE_URL}/apps/openai/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/openai/connection',
|
||||
primaryColor: '000000',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
actions,
|
||||
dynamicData,
|
||||
});
|
@@ -1,16 +1,34 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
import { XMLParser } from 'fast-xml-parser';
|
||||
import bcrypt from 'bcrypt';
|
||||
|
||||
const getInternalId = async (item: IJSONObject): Promise<string> => {
|
||||
if (item.guid) {
|
||||
return item.guid.toString();
|
||||
} else if (item.id) {
|
||||
return item.id.toString();
|
||||
}
|
||||
|
||||
return await hashItem(JSON.stringify(item));
|
||||
};
|
||||
|
||||
const hashItem = async (value: string) => {
|
||||
return await bcrypt.hash(value, 1);
|
||||
};
|
||||
|
||||
const newItemsInFeed = async ($: IGlobalVariable) => {
|
||||
const { data } = await $.http.get($.step.parameters.feedUrl as string);
|
||||
const parser = new XMLParser();
|
||||
const parsedData = parser.parse(data);
|
||||
|
||||
for (const item of parsedData.rss.channel.item) {
|
||||
// naive implementation to cover atom and rss feeds
|
||||
const items = parsedData.rss?.channel?.item || parsedData.feed?.entry || [];
|
||||
|
||||
for (const item of items) {
|
||||
const dataItem = {
|
||||
raw: item,
|
||||
meta: {
|
||||
internalId: item.guid,
|
||||
internalId: await getInternalId(item),
|
||||
},
|
||||
};
|
||||
|
||||
|
@@ -0,0 +1,52 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Create attachment',
|
||||
key: 'createAttachment',
|
||||
description:
|
||||
'Creates an attachment of a specified object by given parent ID.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Parent ID',
|
||||
key: 'parentId',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
description:
|
||||
'ID of the parent object of the attachment. The following objects are supported as parents of attachments: Account, Asset, Campaign, Case, Contact, Contract, Custom objects, EmailMessage, EmailTemplate, Event, Lead, Opportunity, Product2, Solution, Task',
|
||||
},
|
||||
{
|
||||
label: 'Name',
|
||||
key: 'name',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
description: 'Name of the attached file. Maximum size is 255 characters.',
|
||||
},
|
||||
{
|
||||
label: 'Body',
|
||||
key: 'body',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
description: 'File data. (Max size is 25MB)',
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const { parentId, name, body } = $.step.parameters;
|
||||
|
||||
const options = {
|
||||
ParentId: parentId,
|
||||
Name: name,
|
||||
Body: body,
|
||||
};
|
||||
|
||||
const { data } = await $.http.post(
|
||||
'/services/data/v56.0/sobjects/Attachment/',
|
||||
options
|
||||
);
|
||||
|
||||
$.setActionItem({ raw: data });
|
||||
},
|
||||
});
|
@@ -0,0 +1,79 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Find record',
|
||||
key: 'findRecord',
|
||||
description: 'Finds a record of a specified object by a field and value.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Object',
|
||||
key: 'object',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
description: 'Pick which type of object you want to search for.',
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listObjects',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Field',
|
||||
key: 'field',
|
||||
type: 'dropdown' as const,
|
||||
description: 'Pick which field to search by',
|
||||
required: true,
|
||||
variables: false,
|
||||
dependsOn: ['parameters.object'],
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listFields',
|
||||
},
|
||||
{
|
||||
name: 'parameters.object',
|
||||
value: '{parameters.object}',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Search value',
|
||||
key: 'searchValue',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const query = `
|
||||
SELECT
|
||||
FIELDS(ALL)
|
||||
FROM
|
||||
${$.step.parameters.object}
|
||||
WHERE
|
||||
${$.step.parameters.field} = '${$.step.parameters.searchValue}'
|
||||
LIMIT 1
|
||||
`;
|
||||
|
||||
const options = {
|
||||
params: {
|
||||
q: query,
|
||||
},
|
||||
};
|
||||
|
||||
const { data } = await $.http.get('/services/data/v56.0/query', options);
|
||||
const record = data.records[0];
|
||||
|
||||
$.setActionItem({ raw: record });
|
||||
},
|
||||
});
|
4
packages/backend/src/apps/salesforce/actions/index.ts
Normal file
4
packages/backend/src/apps/salesforce/actions/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import findRecord from './find-record';
|
||||
import createAttachment from './create-attachment';
|
||||
|
||||
export default [findRecord, createAttachment];
|
@@ -2,6 +2,7 @@ import defineApp from '../../helpers/define-app';
|
||||
import addAuthHeader from './common/add-auth-header';
|
||||
import auth from './auth';
|
||||
import triggers from './triggers';
|
||||
import actions from './actions';
|
||||
import dynamicData from './dynamic-data';
|
||||
|
||||
export default defineApp({
|
||||
@@ -16,5 +17,6 @@ export default defineApp({
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
actions,
|
||||
dynamicData,
|
||||
});
|
||||
|
@@ -0,0 +1,30 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Find user by email',
|
||||
key: 'findUserByEmail',
|
||||
description: 'Finds a user by email.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Email',
|
||||
key: 'email',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const params = {
|
||||
email: $.step.parameters.email as string,
|
||||
};
|
||||
|
||||
const { data } = await $.http.get('/users.lookupByEmail', {
|
||||
params
|
||||
});
|
||||
|
||||
if (data.ok) {
|
||||
$.setActionItem({ raw: data.user });
|
||||
}
|
||||
},
|
||||
});
|
@@ -1,4 +1,5 @@
|
||||
import findMessage from './find-message';
|
||||
import findUserByEmail from './find-user-by-email';
|
||||
import sendMessageToChannel from './send-a-message-to-channel';
|
||||
|
||||
export default [findMessage, sendMessageToChannel];
|
||||
export default [findMessage, findUserByEmail, sendMessageToChannel];
|
||||
|
@@ -13,7 +13,13 @@ export default {
|
||||
error: null,
|
||||
};
|
||||
|
||||
const response = await $.http.get('/conversations.list');
|
||||
const response = await $.http.get('/conversations.list', {
|
||||
params: {
|
||||
types: 'public_channel,private_channel',
|
||||
limit: 1000,
|
||||
exclude_archived: true,
|
||||
}
|
||||
});
|
||||
|
||||
if (response.data.ok === false) {
|
||||
throw new Error(response.data);
|
||||
|
100
packages/backend/src/apps/todoist/actions/create-task/index.ts
Normal file
100
packages/backend/src/apps/todoist/actions/create-task/index.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Create Task',
|
||||
key: 'createTask',
|
||||
description: 'Creates a Task in Todoist',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Project ID',
|
||||
key: 'projectId',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
variables: false,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listProjects',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Section ID',
|
||||
key: 'sectionId',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
variables: false,
|
||||
dependsOn: ['parameters.projectId'],
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listSections',
|
||||
},
|
||||
{
|
||||
name: 'parameters.projectId',
|
||||
value: '{parameters.projectId}',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Labels',
|
||||
key: 'labels',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description:
|
||||
'Labels to add to task (comma separated). Examples: "work" "work,imported"',
|
||||
},
|
||||
{
|
||||
label: 'Content',
|
||||
key: 'content',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
variables: true,
|
||||
description:
|
||||
'Task content, may be markdown. Example: "Foo"',
|
||||
},
|
||||
{
|
||||
label: 'Description',
|
||||
key: 'description',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: true,
|
||||
description:
|
||||
'Task description, may be markdown. Example: "Foo"',
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const requestPath = `/tasks`;
|
||||
const {
|
||||
projectId,
|
||||
sectionId,
|
||||
labels,
|
||||
content,
|
||||
description
|
||||
} = $.step.parameters;
|
||||
|
||||
const labelsArray = (labels as string).split(',')
|
||||
|
||||
const payload = {
|
||||
content,
|
||||
description: description || null,
|
||||
project_id: projectId || null,
|
||||
labels: labelsArray || null,
|
||||
section_id: sectionId || null,
|
||||
}
|
||||
|
||||
const response = await $.http.post(requestPath, payload);
|
||||
|
||||
$.setActionItem({ raw: response.data });
|
||||
},
|
||||
});
|
3
packages/backend/src/apps/todoist/actions/index.ts
Normal file
3
packages/backend/src/apps/todoist/actions/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import createTask from './create-task';
|
||||
|
||||
export default [createTask];
|
14
packages/backend/src/apps/todoist/assets/favicon.svg
Normal file
14
packages/backend/src/apps/todoist/assets/favicon.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g>
|
||||
<path d="M224.001997,0 L31.9980026,0 C14.3579381,0.0394964443 0.0614809418,14.336846 0,32 L0,224 C0,241.6 14.3971038,256 31.9980026,256 L224.001997,256 C241.602896,256 256,241.6 256,224 L256,32 C256,14.4 241.602896,0 224.001997,0" fill="#E44332">
|
||||
|
||||
</path>
|
||||
<path d="M54.132778,120.802491 C58.5960224,118.196275 154.476075,62.477451 156.667847,61.1862981 C158.859619,59.9110855 158.97917,55.9898065 156.508446,54.5711324 C154.053661,53.1604284 149.391165,50.4824817 147.661658,49.4543415 C145.192242,48.0957707 142.191169,48.132074 139.755339,49.5499825 C138.527947,50.2672896 56.6035026,97.8486625 53.8697654,99.4107981 C50.5781227,101.291737 46.5372925,101.323617 43.2695601,99.4107981 L0,74.0181257 L0,95.6011002 C10.5205046,101.801822 36.7181549,117.200015 43.062338,120.826401 C46.8481256,122.978322 50.4745117,122.930502 54.1407481,120.802491" fill="#FFFFFF">
|
||||
|
||||
</path>
|
||||
<path d="M54.132778,161.609296 C58.5960224,159.00308 154.476075,103.284257 156.667847,101.993104 C158.859619,100.717891 158.97917,96.7966121 156.508446,95.377938 C154.053661,93.9672339 149.391165,91.2892873 147.661658,90.2611471 C145.192242,88.9025763 142.191169,88.9388796 139.755339,90.3567881 C138.527947,91.0740952 56.6035026,138.655468 53.8697654,140.217604 C50.5781227,142.098542 46.5372925,142.130423 43.2695601,140.217604 L0,114.824931 L0,136.407906 C10.5205046,142.608627 36.7181549,158.00682 43.062338,161.633206 C46.8481256,163.785128 50.4745117,163.737307 54.1407481,161.609296" fill="#FFFFFF">
|
||||
|
||||
</path>
|
||||
<path d="M54.132778,204.966527 C58.5960224,202.360311 154.476075,146.641487 156.667847,145.350335 C158.859619,144.075122 158.97917,140.153843 156.508446,138.735169 C154.053661,137.324465 149.391165,134.646518 147.661658,133.618378 C145.192242,132.259807 142.191169,132.29611 139.755339,133.714019 C138.527947,134.431326 56.6035026,182.012699 53.8697654,183.574835 C50.5781227,185.455773 46.5372925,185.487654 43.2695601,183.574835 L0,158.182162 L0,179.765137 C10.5205046,185.965858 36.7181549,201.364051 43.062338,204.990437 C46.8481256,207.142359 50.4745117,207.094538 54.1407481,204.966527" fill="#FFFFFF">
|
After Width: | Height: | Size: 2.4 KiB |
17
packages/backend/src/apps/todoist/auth/generate-auth-url.ts
Normal file
17
packages/backend/src/apps/todoist/auth/generate-auth-url.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
import { URLSearchParams } from 'url';
|
||||
|
||||
export default async function generateAuthUrl($: IGlobalVariable) {
|
||||
const scopes = ['data:read_write'];
|
||||
const searchParams = new URLSearchParams({
|
||||
client_id: $.auth.data.clientId as string,
|
||||
scope: scopes.join(','),
|
||||
});
|
||||
|
||||
const url = `${$.app.baseUrl
|
||||
}/oauth/authorize?${searchParams.toString()}`;
|
||||
|
||||
await $.auth.set({
|
||||
url,
|
||||
});
|
||||
}
|
58
packages/backend/src/apps/todoist/auth/index.ts
Normal file
58
packages/backend/src/apps/todoist/auth/index.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import generateAuthUrl from './generate-auth-url';
|
||||
import verifyCredentials from './verify-credentials';
|
||||
import isStillVerified from './is-still-verified';
|
||||
|
||||
export default {
|
||||
fields: [
|
||||
{
|
||||
key: 'oAuthRedirectUrl',
|
||||
label: 'OAuth Redirect URL',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: true,
|
||||
value: '{WEB_APP_URL}/app/todoist/connections/add',
|
||||
placeholder: null,
|
||||
description:
|
||||
'When asked to input an OAuth callback or redirect URL in Todoist OAuth, enter the URL above.',
|
||||
docUrl: 'https://automatisch.io/docs/todoist#oauth-redirect-url',
|
||||
clickToCopy: true,
|
||||
},
|
||||
{
|
||||
key: 'screenName',
|
||||
label: 'Screen Name',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: 'Name your connection (only used for Automatisch UI).',
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
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,
|
||||
};
|
@@ -0,0 +1,8 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const isStillVerified = async ($: IGlobalVariable) => {
|
||||
await $.http.get('/projects');
|
||||
return true;
|
||||
};
|
||||
|
||||
export default isStillVerified;
|
19
packages/backend/src/apps/todoist/auth/verify-credentials.ts
Normal file
19
packages/backend/src/apps/todoist/auth/verify-credentials.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
const { data } = await $.http.post(
|
||||
`${$.app.baseUrl}/oauth/access_token`,
|
||||
{
|
||||
client_id: $.auth.data.clientId,
|
||||
client_secret: $.auth.data.clientSecret,
|
||||
code: $.auth.data.code,
|
||||
},
|
||||
);
|
||||
|
||||
await $.auth.set({
|
||||
tokenType: data.token_type,
|
||||
accessToken: data.access_token,
|
||||
});
|
||||
};
|
||||
|
||||
export default verifyCredentials;
|
13
packages/backend/src/apps/todoist/common/add-auth-header.ts
Normal file
13
packages/backend/src/apps/todoist/common/add-auth-header.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { TBeforeRequest } from '@automatisch/types';
|
||||
|
||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||
const authData = $.auth.data;
|
||||
if (authData?.accessToken && authData?.tokenType) {
|
||||
const authorizationHeader = `${authData.tokenType} ${authData.accessToken}`;
|
||||
requestConfig.headers.Authorization = authorizationHeader;
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default addAuthHeader;
|
5
packages/backend/src/apps/todoist/dynamic-data/index.ts
Normal file
5
packages/backend/src/apps/todoist/dynamic-data/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import listProjects from './list-projects';
|
||||
import listSections from './list-sections';
|
||||
import listLabels from './list-labels';
|
||||
|
||||
export default [listProjects, listSections, listLabels];
|
@@ -0,0 +1,19 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
export default {
|
||||
name: 'List labels',
|
||||
key: 'listLabels',
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
const response = await $.http.get('/labels');
|
||||
|
||||
response.data = response.data.map((label: { name: string }) => {
|
||||
return {
|
||||
value: label.name,
|
||||
name: label.name,
|
||||
};
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
};
|
@@ -0,0 +1,19 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
export default {
|
||||
name: 'List projects',
|
||||
key: 'listProjects',
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
const response = await $.http.get('/projects');
|
||||
|
||||
response.data = response.data.map((project: { id: string, name: string }) => {
|
||||
return {
|
||||
value: project.id,
|
||||
name: project.name,
|
||||
};
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
};
|
@@ -0,0 +1,23 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
export default {
|
||||
name: 'List sections',
|
||||
key: 'listSections',
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
const params = {
|
||||
project_id: ($.step.parameters.projectId as string),
|
||||
};
|
||||
|
||||
const response = await $.http.get('/sections', {params});
|
||||
|
||||
response.data = response.data.map((section: { id: string, name: string }) => {
|
||||
return {
|
||||
value: section.id,
|
||||
name: section.name,
|
||||
};
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
};
|
0
packages/backend/src/apps/todoist/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/todoist/index.d.ts
vendored
Normal file
22
packages/backend/src/apps/todoist/index.ts
Normal file
22
packages/backend/src/apps/todoist/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import addAuthHeader from './common/add-auth-header';
|
||||
import auth from './auth';
|
||||
import triggers from './triggers';
|
||||
import actions from './actions';
|
||||
import dynamicData from './dynamic-data';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Todoist',
|
||||
key: 'todoist',
|
||||
iconUrl: '{BASE_URL}/apps/todoist/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/todoist/connection',
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://todoist.com',
|
||||
apiBaseUrl: 'https://api.todoist.com/rest/v2',
|
||||
primaryColor: 'e44332',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
actions,
|
||||
dynamicData,
|
||||
});
|
@@ -0,0 +1,30 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const getActiveTasks = async ($: IGlobalVariable) => {
|
||||
|
||||
const params = {
|
||||
project_id: ($.step.parameters.projectId as string)?.trim(),
|
||||
section_id: ($.step.parameters.sectionId as string)?.trim(),
|
||||
label: ($.step.parameters.label as string)?.trim(),
|
||||
filter: ($.step.parameters.filter as string)?.trim(),
|
||||
};
|
||||
|
||||
const response = await $.http.get('/tasks', { params });
|
||||
|
||||
// todoist api doesn't offer sorting, so we inverse sort on id here
|
||||
response.data.sort((a: { id: number; }, b: { id: number; }) => {
|
||||
return b.id - a.id;
|
||||
})
|
||||
|
||||
for (const task of response.data) {
|
||||
$.pushTriggerItem({
|
||||
raw: task,
|
||||
meta:{
|
||||
internalId: task.id as string,
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export default getActiveTasks;
|
@@ -0,0 +1,80 @@
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
import getActiveTasks from './get-tasks';
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'Get Active Tasks',
|
||||
key: 'getActiveTasks',
|
||||
pollInterval: 15,
|
||||
description: 'Triggers when new Task(s) are found',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Project ID',
|
||||
key: 'projectId',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
variables: false,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listProjects',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Section ID',
|
||||
key: 'sectionId',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
variables: false,
|
||||
dependsOn: ['parameters.projectId'],
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listSections',
|
||||
},
|
||||
{
|
||||
name: 'parameters.projectId',
|
||||
value: '{parameters.projectId}',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Label',
|
||||
key: 'label',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
variables: false,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listLabels',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Filter',
|
||||
key: 'filter',
|
||||
type: 'string' as const,
|
||||
required: false,
|
||||
variables: false,
|
||||
description:
|
||||
'Limit queried tasks to this filter. Example: "Meeting & today"',
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
await getActiveTasks($);
|
||||
},
|
||||
});
|
3
packages/backend/src/apps/todoist/triggers/index.ts
Normal file
3
packages/backend/src/apps/todoist/triggers/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import getTasks from './get-tasks';
|
||||
|
||||
export default [getTasks];
|
@@ -1,3 +1,4 @@
|
||||
import createTweet from './create-tweet';
|
||||
import searchUser from './search-user';
|
||||
|
||||
export default [createTweet];
|
||||
export default [createTweet, searchUser];
|
||||
|
@@ -0,0 +1,30 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Search user',
|
||||
key: 'searchUser',
|
||||
description: 'Search a user on Twitter',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Username',
|
||||
key: 'username',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'The username of the Twitter user you want to search for',
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const { data } = await $.http.get(`/2/users/by/username/${$.step.parameters.username}`, {
|
||||
params: {
|
||||
expansions: 'pinned_tweet_id',
|
||||
'tweet.fields': 'attachments,author_id,context_annotations,conversation_id,created_at,edit_controls,entities,geo,id,in_reply_to_user_id,lang,non_public_metrics,public_metrics,organic_metrics,promoted_metrics,possibly_sensitive,referenced_tweets,reply_settings,source,text,withheld',
|
||||
'user.fields': 'created_at,description,entities,id,location,name,pinned_tweet_id,profile_image_url,protected,public_metrics,url,username,verified,verified_type,withheld'
|
||||
}
|
||||
});
|
||||
$.setActionItem({
|
||||
raw: data.data
|
||||
});
|
||||
},
|
||||
});
|
@@ -1,4 +1,5 @@
|
||||
import { Token } from 'oauth-1.0a';
|
||||
import { URLSearchParams } from 'node:url';
|
||||
import { IJSONObject, TBeforeRequest } from '@automatisch/types';
|
||||
import oauthClient from './oauth-client';
|
||||
|
||||
@@ -9,15 +10,24 @@ type RequestDataType = {
|
||||
};
|
||||
|
||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||
const { url, method, data } = requestConfig;
|
||||
const { baseURL, url, method, data, params } = requestConfig;
|
||||
|
||||
const token: Token = {
|
||||
key: $.auth.data?.accessToken as string,
|
||||
secret: $.auth.data?.accessSecret as string,
|
||||
};
|
||||
|
||||
const searchParams = new URLSearchParams(params);
|
||||
const stringifiedParams = searchParams.toString();
|
||||
let fullUrl = `${baseURL}${url}`;
|
||||
|
||||
// append the search params
|
||||
if (stringifiedParams) {
|
||||
fullUrl = `${fullUrl}?${stringifiedParams}`;
|
||||
}
|
||||
|
||||
const requestData: RequestDataType = {
|
||||
url: `${requestConfig.baseURL}${url}`,
|
||||
url: fullUrl,
|
||||
method,
|
||||
};
|
||||
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { URL } from 'node:url';
|
||||
import * as dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
@@ -30,6 +31,7 @@ type AppConfig = {
|
||||
bullMQDashboardUsername: string;
|
||||
bullMQDashboardPassword: string;
|
||||
telemetryEnabled: boolean;
|
||||
requestBodySizeLimit: string;
|
||||
};
|
||||
|
||||
const host = process.env.HOST || 'localhost';
|
||||
@@ -38,14 +40,23 @@ const port = process.env.PORT || '3000';
|
||||
const serveWebAppSeparately =
|
||||
process.env.SERVE_WEB_APP_SEPARATELY === 'true' ? true : false;
|
||||
|
||||
let webAppUrl = `${protocol}://${host}:${port}`;
|
||||
const webhookUrl = process.env.WEBHOOK_URL || webAppUrl;
|
||||
let apiUrl = (new URL(`${protocol}://${host}:${port}`)).toString();
|
||||
apiUrl = apiUrl.substring(0, apiUrl.length - 1);
|
||||
|
||||
if (serveWebAppSeparately) {
|
||||
webAppUrl = process.env.WEB_APP_URL || 'http://localhost:3001';
|
||||
// use apiUrl by default, which has less priority over the following cases
|
||||
let webAppUrl = apiUrl;
|
||||
|
||||
if (process.env.WEB_APP_URL) {
|
||||
// use env. var. if provided
|
||||
webAppUrl = (new URL(process.env.WEB_APP_URL)).toString();
|
||||
webAppUrl = webAppUrl.substring(0, webAppUrl.length - 1);
|
||||
} else if (serveWebAppSeparately) {
|
||||
// no env. var. and serving separately, sign of development
|
||||
webAppUrl = 'http://localhost:3001'
|
||||
}
|
||||
|
||||
const baseUrl = `${protocol}://${host}:${port}`;
|
||||
let webhookUrl = (new URL(process.env.WEBHOOK_URL || apiUrl)).toString();
|
||||
webhookUrl = webhookUrl.substring(0, webhookUrl.length - 1);
|
||||
|
||||
const appEnv = process.env.APP_ENV || 'development';
|
||||
|
||||
@@ -75,10 +86,11 @@ const appConfig: AppConfig = {
|
||||
enableBullMQDashboard: process.env.ENABLE_BULLMQ_DASHBOARD === 'true',
|
||||
bullMQDashboardUsername: process.env.BULLMQ_DASHBOARD_USERNAME,
|
||||
bullMQDashboardPassword: process.env.BULLMQ_DASHBOARD_PASSWORD,
|
||||
baseUrl,
|
||||
baseUrl: apiUrl,
|
||||
webAppUrl,
|
||||
webhookUrl,
|
||||
telemetryEnabled: process.env.TELEMETRY_ENABLED === 'false' ? false : true,
|
||||
requestBodySizeLimit: '1mb',
|
||||
};
|
||||
|
||||
if (!appConfig.encryptionKey) {
|
||||
|
@@ -45,6 +45,7 @@ export default async (request: IRequest, response: Response) => {
|
||||
|
||||
// in case trigger type is 'webhook'
|
||||
let payload = request.body;
|
||||
let rawInternalId: string | Buffer = request.rawBody;
|
||||
|
||||
// in case it's our built-in generic webhook trigger
|
||||
if (isWebhookApp) {
|
||||
@@ -53,12 +54,14 @@ export default async (request: IRequest, response: Response) => {
|
||||
body: request.body,
|
||||
query: request.query,
|
||||
}
|
||||
|
||||
rawInternalId = JSON.stringify(payload);
|
||||
}
|
||||
|
||||
const triggerItem: ITriggerItem = {
|
||||
raw: payload,
|
||||
meta: {
|
||||
internalId: await bcrypt.hash(request.rawBody, 1),
|
||||
internalId: await bcrypt.hash(rawInternalId, 1),
|
||||
},
|
||||
};
|
||||
|
||||
|
@@ -0,0 +1,13 @@
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
return knex.schema.table('flows', (table) => {
|
||||
table.string('remote_webhook_id');
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
return knex.schema.table('flows', (table) => {
|
||||
table.dropColumn('remote_webhook_id');
|
||||
});
|
||||
}
|
@@ -381,6 +381,7 @@ type Trigger {
|
||||
key: String
|
||||
description: String
|
||||
pollInterval: Int
|
||||
type: String
|
||||
substeps: [TriggerSubstep]
|
||||
}
|
||||
|
||||
|
25
packages/backend/src/helpers/delay-as-milliseconds.ts
Normal file
25
packages/backend/src/helpers/delay-as-milliseconds.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import Step from '../models/step';
|
||||
import delayForAsMilliseconds, {
|
||||
TDelayForUnit,
|
||||
} from './delay-for-as-milliseconds';
|
||||
import delayUntilAsMilliseconds from './delay-until-as-milliseconds';
|
||||
|
||||
const delayAsMilliseconds = (step: Step) => {
|
||||
let delayDuration = 0;
|
||||
|
||||
if (step.key === 'delayFor') {
|
||||
const { delayForUnit, delayForValue } = step.parameters;
|
||||
|
||||
delayDuration = delayForAsMilliseconds(
|
||||
delayForUnit as TDelayForUnit,
|
||||
Number(delayForValue)
|
||||
);
|
||||
} else if (step.key === 'delayUntil') {
|
||||
const { delayUntil } = step.parameters;
|
||||
delayDuration = delayUntilAsMilliseconds(delayUntil as string);
|
||||
}
|
||||
|
||||
return delayDuration;
|
||||
};
|
||||
|
||||
export default delayAsMilliseconds;
|
21
packages/backend/src/helpers/delay-for-as-milliseconds.ts
Normal file
21
packages/backend/src/helpers/delay-for-as-milliseconds.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export type TDelayForUnit = 'minutes' | 'hours' | 'days' | 'weeks';
|
||||
|
||||
const delayAsMilliseconds = (
|
||||
delayForUnit: TDelayForUnit,
|
||||
delayForValue: number
|
||||
) => {
|
||||
switch (delayForUnit) {
|
||||
case 'minutes':
|
||||
return delayForValue * 60 * 1000;
|
||||
case 'hours':
|
||||
return delayForValue * 60 * 60 * 1000;
|
||||
case 'days':
|
||||
return delayForValue * 24 * 60 * 60 * 1000;
|
||||
case 'weeks':
|
||||
return delayForValue * 7 * 24 * 60 * 60 * 1000;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
export default delayAsMilliseconds;
|
@@ -0,0 +1,8 @@
|
||||
const delayUntilAsMilliseconds = (delayUntil: string) => {
|
||||
const delayUntilDate = new Date(delayUntil);
|
||||
const now = new Date();
|
||||
|
||||
return delayUntilDate.getTime() - now.getTime();
|
||||
};
|
||||
|
||||
export default delayUntilAsMilliseconds;
|
@@ -37,6 +37,7 @@ const globalVariable = async (
|
||||
testRun = false,
|
||||
} = options;
|
||||
|
||||
const isTrigger = step?.isTrigger;
|
||||
const lastInternalId = testRun ? undefined : await flow?.lastInternalId();
|
||||
const nextStep = await step?.getNextStep();
|
||||
|
||||
@@ -123,6 +124,18 @@ const globalVariable = async (
|
||||
$.webhookUrl = webhookUrl;
|
||||
}
|
||||
|
||||
if (isTrigger && (await step.getTriggerCommand()).type === 'webhook') {
|
||||
$.flow.setRemoteWebhookId = async (remoteWebhookId) => {
|
||||
await flow.$query().patchAndFetch({
|
||||
remoteWebhookId,
|
||||
});
|
||||
|
||||
$.flow.remoteWebhookId = remoteWebhookId;
|
||||
};
|
||||
|
||||
$.flow.remoteWebhookId = flow.remoteWebhookId;
|
||||
}
|
||||
|
||||
const lastInternalIds =
|
||||
testRun || (flow && step.isAction) ? [] : await flow?.lastInternalIds(2000);
|
||||
|
||||
|
@@ -44,7 +44,8 @@ export default function createHttpClient({
|
||||
const { status } = error.response;
|
||||
|
||||
if (
|
||||
status === 401 &&
|
||||
// TODO: provide a `shouldRefreshToken` function in the app
|
||||
(status === 401 || status === 403) &&
|
||||
$.app.auth.refreshToken &&
|
||||
!$.app.auth.isRefreshTokenRequested
|
||||
) {
|
||||
|
@@ -13,6 +13,7 @@ class Flow extends Base {
|
||||
active: boolean;
|
||||
steps: Step[];
|
||||
published_at: string;
|
||||
remoteWebhookId: string;
|
||||
executions?: Execution[];
|
||||
|
||||
static tableName = 'flows';
|
||||
@@ -25,6 +26,7 @@ class Flow extends Base {
|
||||
id: { type: 'string', format: 'uuid' },
|
||||
name: { type: 'string', minLength: 1 },
|
||||
userId: { type: 'string', format: 'uuid' },
|
||||
remoteWebhookId: { type: 'string' },
|
||||
active: { type: 'boolean' },
|
||||
},
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user