Compare commits
353 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
142b96beb0 | ||
![]() |
18d07dd3b9 | ||
![]() |
270039d211 | ||
![]() |
fcf3a480be | ||
![]() |
80ee974973 | ||
![]() |
48c6a38205 | ||
![]() |
c9536e58cb | ||
![]() |
a16d783302 | ||
![]() |
b837aecb27 | ||
![]() |
532405048f | ||
![]() |
6cd0427ff8 | ||
![]() |
d2163f180e | ||
![]() |
d1344457dd | ||
![]() |
596be24d92 | ||
![]() |
4e182b49f9 | ||
![]() |
51ac37f1de | ||
![]() |
b578e88d46 | ||
![]() |
9cb4607f69 | ||
![]() |
2c4d602028 | ||
![]() |
a0feb7f309 | ||
![]() |
77e29050c8 | ||
![]() |
df55f746ca | ||
![]() |
2739d2297f | ||
![]() |
dc734b04d8 | ||
![]() |
d4c542168c | ||
![]() |
94a3b66130 | ||
![]() |
c8baf9b0d7 | ||
![]() |
c4dc0509c2 | ||
![]() |
3bfc428dfe | ||
![]() |
026b7da6f8 | ||
![]() |
58352b9f33 | ||
![]() |
59faf593be | ||
![]() |
4aa41773c4 | ||
![]() |
20c5d6aee1 | ||
![]() |
d503dbc103 | ||
![]() |
53624a6379 | ||
![]() |
5b881db19f | ||
![]() |
dece070d28 | ||
![]() |
8be2ed0034 | ||
![]() |
37c6b57a48 | ||
![]() |
8a0b5c24a5 | ||
![]() |
20431da757 | ||
![]() |
f219c87a9e | ||
![]() |
5d2134db56 | ||
![]() |
1dc46dd31c | ||
![]() |
96250ce9a0 | ||
![]() |
691682e09c | ||
![]() |
814c504951 | ||
![]() |
5c2b96a812 | ||
![]() |
3dc1ca8adb | ||
![]() |
a6850f1bc0 | ||
![]() |
5ba6cb9135 | ||
![]() |
500283af6f | ||
![]() |
43184dccda | ||
![]() |
4296a3b5df | ||
![]() |
14ba50f061 | ||
![]() |
9ad7de56a3 | ||
![]() |
5570687957 | ||
![]() |
1406bf3e10 | ||
![]() |
287f299b94 | ||
![]() |
f88f05cd46 | ||
![]() |
c6fb42e2f3 | ||
![]() |
c64ca9d9b7 | ||
![]() |
7d47793afb | ||
![]() |
2840fce856 | ||
![]() |
2791dca412 | ||
![]() |
3e268bf66b | ||
![]() |
f44422a77e | ||
![]() |
4931cbcc34 | ||
![]() |
59e495f7d3 | ||
![]() |
93866d7bc2 | ||
![]() |
ba1cdeaeeb | ||
![]() |
234b27e555 | ||
![]() |
5acfd09819 | ||
![]() |
9923b15ecd | ||
![]() |
def5a6d9d0 | ||
![]() |
17c8a405f5 | ||
![]() |
ff874bfb48 | ||
![]() |
4cbb37b996 | ||
![]() |
f57e35c6b8 | ||
![]() |
312e786e33 | ||
![]() |
e2c75a2daf | ||
![]() |
fce20263ea | ||
![]() |
d841f9cb62 | ||
![]() |
4945240ec2 | ||
![]() |
f1cdc7e422 | ||
![]() |
372cdb10d6 | ||
![]() |
fd24dbee21 | ||
![]() |
7066f7ea76 | ||
![]() |
5ae53c79b6 | ||
![]() |
f3a8ab289f | ||
![]() |
a99609e3da | ||
![]() |
a271189448 | ||
![]() |
251bc28526 | ||
![]() |
05505704e4 | ||
![]() |
4aadcb021e | ||
![]() |
ae9da1b354 | ||
![]() |
a594029541 | ||
![]() |
ddcb894932 | ||
![]() |
708cdbe545 | ||
![]() |
1cbf96dff1 | ||
![]() |
bd55b37d5f | ||
![]() |
7c3a0effee | ||
![]() |
fe3048aab0 | ||
![]() |
9a5a3e879d | ||
![]() |
269902db94 | ||
![]() |
4ba67ea863 | ||
![]() |
3534712478 | ||
![]() |
62b9a8071a | ||
![]() |
cf83c27ca0 | ||
![]() |
8035f81f97 | ||
![]() |
d2375fdc54 | ||
![]() |
f07d9dd813 | ||
![]() |
b5524b18cf | ||
![]() |
310497a5bf | ||
![]() |
47a738d5e6 | ||
![]() |
6ad13c1da0 | ||
![]() |
99454fdc4b | ||
![]() |
799f0ead6c | ||
![]() |
d16d709b72 | ||
![]() |
8e1087b818 | ||
![]() |
139dcb521e | ||
![]() |
b17e431473 | ||
![]() |
b1ee3ef8ba | ||
![]() |
75bbd16b0c | ||
![]() |
71a7943d01 | ||
![]() |
d34785b5b0 | ||
![]() |
af58ef7244 | ||
![]() |
6d3bec8518 | ||
![]() |
0346b157c5 | ||
![]() |
e25aab742b | ||
![]() |
6f2ca00263 | ||
![]() |
e1d26325f3 | ||
![]() |
4202f963c3 | ||
![]() |
d3ef45db1b | ||
![]() |
dec55709a3 | ||
![]() |
b5ed984f05 | ||
![]() |
66d7baa126 | ||
![]() |
a2809a14c5 | ||
![]() |
040ad9edb0 | ||
![]() |
46dbe009f2 | ||
![]() |
3598d43938 | ||
![]() |
f1358c7ad1 | ||
![]() |
40862fcd01 | ||
![]() |
189432c228 | ||
![]() |
280d16f3d9 | ||
![]() |
722c39590f | ||
![]() |
b1138dbf05 | ||
![]() |
08918282a7 | ||
![]() |
5007b0bf1a | ||
![]() |
0a5912eb8e | ||
![]() |
8acd7b03ed | ||
![]() |
ed87df212f | ||
![]() |
56243aa076 | ||
![]() |
41e9f32e1b | ||
![]() |
f7753aa1b4 | ||
![]() |
aebfcc38dd | ||
![]() |
3e0149c058 | ||
![]() |
1e62e09825 | ||
![]() |
956b15a2eb | ||
![]() |
572b457b43 | ||
![]() |
7d40ae009f | ||
![]() |
aec9595dea | ||
![]() |
cc90f19a46 | ||
![]() |
ab486c8ed1 | ||
![]() |
bfb1e817ec | ||
![]() |
ebc7c22388 | ||
![]() |
10b4066c82 | ||
![]() |
42dd67954d | ||
![]() |
d45fdf605f | ||
![]() |
caef9bb8b5 | ||
![]() |
c888934601 | ||
![]() |
95613b595e | ||
![]() |
9247bd9d9e | ||
![]() |
ee3d2489e6 | ||
![]() |
7764c4fbcb | ||
![]() |
a18b524859 | ||
![]() |
9b8ec9b85e | ||
![]() |
7b3f070973 | ||
![]() |
ba27fc12e8 | ||
![]() |
db55912f78 | ||
![]() |
4c4bd267d4 | ||
![]() |
dc1002659b | ||
![]() |
97da370301 | ||
![]() |
571901f333 | ||
![]() |
4d90df9d9a | ||
![]() |
2c18667ffd | ||
![]() |
89157cd606 | ||
![]() |
37e0091ef0 | ||
![]() |
ae3512fecf | ||
![]() |
8b81391e2f | ||
![]() |
54e68f6252 | ||
![]() |
92d1ed65ff | ||
![]() |
8098a7ee5d | ||
![]() |
c24297630c | ||
![]() |
a9d5212602 | ||
![]() |
c81db8ae19 | ||
![]() |
ecd356d42b | ||
![]() |
e67adf87b2 | ||
![]() |
1669708041 | ||
![]() |
12e34013f8 | ||
![]() |
bd2ad1d7a1 | ||
![]() |
934cdb8237 | ||
![]() |
2f6ea8830e | ||
![]() |
b77d08ebbf | ||
![]() |
b735d32cbc | ||
![]() |
472ffd5b5c | ||
![]() |
4567ca8fce | ||
![]() |
07ed24ca7a | ||
![]() |
52575be2a7 | ||
![]() |
c8187e52bb | ||
![]() |
b0b6b72b4c | ||
![]() |
7676bc5836 | ||
![]() |
f98c1725be | ||
![]() |
c6bd599b63 | ||
![]() |
7908779c89 | ||
![]() |
29ad68afab | ||
![]() |
9aaeac6a08 | ||
![]() |
8f074c2131 | ||
![]() |
930653c86d | ||
![]() |
cc31b7c210 | ||
![]() |
bb19e9308c | ||
![]() |
5e7b4bfe45 | ||
![]() |
dbeeb61cc5 | ||
![]() |
26d8e5856a | ||
![]() |
66be6d1e89 | ||
![]() |
677aa232e7 | ||
![]() |
cabbb45031 | ||
![]() |
ba99df645b | ||
![]() |
282e5ba2d8 | ||
![]() |
42d418da58 | ||
![]() |
cbf270fdba | ||
![]() |
bb9abe104f | ||
![]() |
18f0d6dea3 | ||
![]() |
23dc9a1139 | ||
![]() |
5e18ef5830 | ||
![]() |
63f8fc266d | ||
![]() |
4e46b16f7b | ||
![]() |
ba19b50005 | ||
![]() |
fa867387d4 | ||
![]() |
5762cf5dc5 | ||
![]() |
7d9f624805 | ||
![]() |
5b335ccd59 | ||
![]() |
4792853eb6 | ||
![]() |
cc05bc7db8 | ||
![]() |
d198eaa988 | ||
![]() |
f094da6a4b | ||
![]() |
a53961b235 | ||
![]() |
f644113af8 | ||
![]() |
e4a903ec07 | ||
![]() |
9408fe2a07 | ||
![]() |
394e747a88 | ||
![]() |
e91f12729b | ||
![]() |
134e588b14 | ||
![]() |
d061eb7b58 | ||
![]() |
18089a8076 | ||
![]() |
02236e01d9 | ||
![]() |
c1150d50b1 | ||
![]() |
387a849269 | ||
![]() |
399cebda70 | ||
![]() |
72720b3dfe | ||
![]() |
64f7560b3b | ||
![]() |
e06b646f49 | ||
![]() |
b51d9bb17b | ||
![]() |
4e967c5720 | ||
![]() |
179db38fd1 | ||
![]() |
e51930d7e1 | ||
![]() |
74a299dbe6 | ||
![]() |
282863c526 | ||
![]() |
63d794ed3e | ||
![]() |
c6b8f12f9a | ||
![]() |
d16e292231 | ||
![]() |
29a319a850 | ||
![]() |
c11e0db077 | ||
![]() |
b2dafb5dfa | ||
![]() |
e7ccd01427 | ||
![]() |
447f26458a | ||
![]() |
61fb71a080 | ||
![]() |
329fcdf8f4 | ||
![]() |
37cdb34014 | ||
![]() |
5ee7b85cc4 | ||
![]() |
1f7228f95a | ||
![]() |
fa8418adcd | ||
![]() |
d3b1765ffe | ||
![]() |
0a274ebadb | ||
![]() |
0d1b35edc5 | ||
![]() |
d7e4ae53ce | ||
![]() |
61ceffc6f9 | ||
![]() |
0c422bfd21 | ||
![]() |
a0815b06a6 | ||
![]() |
bd02b7574a | ||
![]() |
1048d923d0 | ||
![]() |
9dbfcf4262 | ||
![]() |
8ea176b5f0 | ||
![]() |
90dcbadc52 | ||
![]() |
e4021bf830 | ||
![]() |
b8b453aba0 | ||
![]() |
aeec2377c1 | ||
![]() |
f8b0ffd39b | ||
![]() |
9953c3c823 | ||
![]() |
d227a07fe9 | ||
![]() |
7c394414d8 | ||
![]() |
1dfb22d02e | ||
![]() |
1847ad5622 | ||
![]() |
beb701ceb4 | ||
![]() |
2fbadea821 | ||
![]() |
bac561d8c7 | ||
![]() |
7bd261a02e | ||
![]() |
df99a889f0 | ||
![]() |
16c5892a1d | ||
![]() |
0236bbaf68 | ||
![]() |
2ca5a290c3 | ||
![]() |
66c388a644 | ||
![]() |
74a77ed271 | ||
![]() |
3d98e6cdc0 | ||
![]() |
831ae96e0f | ||
![]() |
113a91a73f | ||
![]() |
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 |
1
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1 @@
|
||||
FROM mcr.microsoft.com/vscode/devcontainers/base:ubuntu-22.04
|
45
.devcontainer/boot.sh
Normal file
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
|
||||
CURRENT_DIR="$(pwd)"
|
||||
BACKEND_PORT=3000
|
||||
WEB_PORT=3001
|
||||
|
||||
echo "Configuring backend environment variables..."
|
||||
cd packages/backend
|
||||
rm -rf .env
|
||||
echo "
|
||||
PORT=$BACKEND_PORT
|
||||
WEB_APP_URL=http://localhost:$WEB_PORT
|
||||
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=http://localhost:$BACKEND_PORT/graphql
|
||||
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
@@ -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"
|
||||
}
|
39
.devcontainer/docker-compose.yml
Normal file
@@ -0,0 +1,39 @@
|
||||
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
|
||||
ports:
|
||||
- '5432:5432'
|
||||
expose:
|
||||
- 5432
|
||||
redis:
|
||||
image: 'redis:7.0.4-alpine'
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
ports:
|
||||
- '6379:6379'
|
||||
expose:
|
||||
- 6379
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
5
CONTRIBUTOR_LICENSE_AGREEMENT.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Automatisch Contributor License Agreement
|
||||
|
||||
I give Automatisch permission to license my contributions on any terms they like. I am giving them this license in order to make it possible for them to accept my contributions into their project.
|
||||
|
||||
**_As far as the law allows, my contributions come as is, without any warranty or condition, and I will not be liable to anyone for any damages related to this software or this license, under any kind of legal claim._**
|
3
LICENSE
Normal file
@@ -0,0 +1,3 @@
|
||||
LICENSE.agpl (AGPL-3.0) applies to all files in this
|
||||
repository, except for files that contain ".ee." in their name
|
||||
which are covered by LICENSE.enterprise.
|
35
LICENSE.enterprise
Normal file
@@ -0,0 +1,35 @@
|
||||
The Automatisch Enterprise license (the “Enterprise License”)
|
||||
Copyright (c) 2023 Ömer Faruk Aydın, Ali Barın.
|
||||
|
||||
With regard to the Automatisch Software:
|
||||
|
||||
This software and associated documentation files (the "Software") may only be
|
||||
used in production, if you (and any entity that you represent) have a valid
|
||||
Automatisch Enterprise license for the correct number of user seats. Subject
|
||||
to the foregoing sentence, you are free to modify this Software and publish
|
||||
patches to the Software. You agree that Automatisch and/or its licensors
|
||||
(as applicable) retain all right, title and interest in and to all such
|
||||
modifications and/or patches, and all such modifications and/or patches may
|
||||
only be used, copied, modified, displayed, distributed, or otherwise exploited
|
||||
with a valid Automatisch Enterprise license for the correct number of user seats.
|
||||
Notwithstanding the foregoing, you may copy and modify the Software for
|
||||
development and testing purposes, without requiring a subscription. You agree
|
||||
that Automatisch and/or its licensors (as applicable) retain all right, title
|
||||
and interest in and to all such modifications. You are not granted any other
|
||||
rights beyond what is expressly stated herein. Subject to the foregoing, it is
|
||||
forbidden to copy, merge, publish, distribute, sublicense, and/or sell the Software.
|
||||
|
||||
The full text of this Enterprise License shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
For all third party components incorporated into the Automatisch Software, those
|
||||
components are licensed under the original license provided by the owner of the
|
||||
applicable component.
|
16
README.md
@@ -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
|
||||
@@ -44,10 +44,18 @@ For other installation types, you can check the [installation](https://automatis
|
||||
|
||||
## Support
|
||||
|
||||
If you have any questions or problems, please visit our GitHub discussions page, and we'll try to help you as soon as possible.
|
||||
If you have any questions or problems, please visit our GitHub issues page, and we'll try to help you as soon as possible.
|
||||
|
||||
[https://github.com/automatisch/automatisch/discussions](https://github.com/automatisch/automatisch/discussions)
|
||||
[https://github.com/automatisch/automatisch/issues](https://github.com/automatisch/automatisch/issues)
|
||||
|
||||
## License
|
||||
|
||||
Automatisch is an open-source software with the [AGPL 3.0 license](https://github.com/automatisch/automatisch/blob/main/LICENSE.md).
|
||||
Automatisch Community Edition (Automatisch CE) is an open-source software with the [AGPL-3.0 license](LICENSE.agpl).
|
||||
|
||||
Automatisch Enterprise Edition (Automatisch EE) is a commercial offering with the [Enterprise license](LICENSE.enterprise).
|
||||
|
||||
The Automatisch repository contains both AGPL-licensed and Enterprise-licensed files. We maintain a single repository to make development easier.
|
||||
|
||||
All files that contain ".ee." in their name fall under the [Enterprise license](LICENSE.enterprise). All other files fall under the [AGPL-3.0 license](LICENSE.agpl).
|
||||
|
||||
See the [LICENSE](LICENSE) file for more information.
|
||||
|
@@ -2,13 +2,13 @@
|
||||
FROM node:16-alpine
|
||||
WORKDIR /automatisch
|
||||
|
||||
RUN apk --no-cache add --virtual build-dependencies python3 build-base
|
||||
RUN \
|
||||
apk --no-cache add --virtual build-dependencies python3 build-base && \
|
||||
yarn global add @automatisch/cli@0.5.0 --network-timeout 1000000 && \
|
||||
rm -rf /usr/local/share/.cache/ && \
|
||||
apk del build-dependencies
|
||||
|
||||
COPY ./entrypoint.sh /entrypoint.sh
|
||||
|
||||
RUN yarn global add @automatisch/cli@0.3.0
|
||||
|
||||
RUN apk del build-dependencies python3 build-base
|
||||
|
||||
EXPOSE 3000
|
||||
ENTRYPOINT ["sh", "/entrypoint.sh"]
|
||||
|
@@ -1,5 +1,5 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM automatischio/automatisch:0.3.0
|
||||
FROM automatischio/automatisch:0.5.0
|
||||
WORKDIR /automatisch
|
||||
|
||||
RUN apk add --no-cache openssl dos2unix
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "0.3.0",
|
||||
"version": "0.6.0",
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"command": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@automatisch/root",
|
||||
"license": "AGPL-3.0",
|
||||
"license": "See LICENSE file",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "lerna run --stream --parallel --scope=@*/{web,backend} dev",
|
||||
|
@@ -12,6 +12,8 @@ export async function createUser(
|
||||
const userParams = {
|
||||
email,
|
||||
password,
|
||||
fullName: 'Initial admin',
|
||||
role: 'admin',
|
||||
};
|
||||
|
||||
try {
|
||||
|
@@ -12,6 +12,7 @@ const knexConfig = {
|
||||
database: appConfig.postgresDatabase,
|
||||
ssl: appConfig.postgresEnableSsl,
|
||||
},
|
||||
searchPath: [appConfig.postgresSchema],
|
||||
pool: { min: 0, max: 20 },
|
||||
migrations: {
|
||||
directory: __dirname + '/src/db/migrations',
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@automatisch/backend",
|
||||
"version": "0.3.0",
|
||||
"license": "AGPL-3.0",
|
||||
"version": "0.6.0",
|
||||
"license": "See LICENSE file",
|
||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||
"scripts": {
|
||||
"dev": "ts-node-dev --exit-child src/server.ts",
|
||||
@@ -22,11 +22,13 @@
|
||||
"prebuild": "rm -rf ./dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@automatisch/web": "^0.3.0",
|
||||
"@automatisch/web": "^0.6.0",
|
||||
"@bull-board/express": "^3.10.1",
|
||||
"@graphql-tools/graphql-file-loader": "^7.3.4",
|
||||
"@graphql-tools/load": "^7.5.2",
|
||||
"@rudderstack/rudder-sdk-node": "^1.1.2",
|
||||
"@sentry/node": "^7.42.0",
|
||||
"@sentry/tracing": "^7.42.0",
|
||||
"@types/luxon": "^2.3.1",
|
||||
"ajv-formats": "^2.1.1",
|
||||
"axios": "0.24.0",
|
||||
@@ -45,17 +47,21 @@
|
||||
"graphql-shield": "^7.5.0",
|
||||
"graphql-tools": "^8.2.0",
|
||||
"graphql-type-json": "^0.3.2",
|
||||
"handlebars": "^4.7.7",
|
||||
"http-errors": "~1.6.3",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"knex": "^0.95.11",
|
||||
"knex": "^2.4.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"luxon": "2.5.2",
|
||||
"memory-cache": "^0.2.0",
|
||||
"morgan": "^1.10.0",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"nodemailer": "6.7.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"objection": "^3.0.0",
|
||||
"pg": "^8.7.1",
|
||||
"php-serialize": "^4.0.2",
|
||||
"stripe": "^11.13.0",
|
||||
"winston": "^3.7.1"
|
||||
},
|
||||
"contributors": [
|
||||
@@ -94,7 +100,7 @@
|
||||
"url": "https://github.com/automatisch/automatisch/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@automatisch/types": "^0.3.0",
|
||||
"@automatisch/types": "^0.6.0",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/bull": "^3.15.8",
|
||||
"@types/cors": "^2.8.12",
|
||||
@@ -103,6 +109,7 @@
|
||||
"@types/http-errors": "^1.8.1",
|
||||
"@types/jsonwebtoken": "^8.5.8",
|
||||
"@types/lodash.get": "^4.4.6",
|
||||
"@types/memory-cache": "^0.2.2",
|
||||
"@types/morgan": "^1.9.3",
|
||||
"@types/multer": "1.4.7",
|
||||
"@types/node": "^16.10.2",
|
||||
|
@@ -1,8 +1,12 @@
|
||||
import createError from 'http-errors';
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
|
||||
import { IRequest } from '@automatisch/types';
|
||||
import appConfig from './config/app';
|
||||
import corsOptions from './config/cors-options';
|
||||
import morgan from './helpers/morgan';
|
||||
import * as Sentry from './helpers/sentry.ee';
|
||||
import appAssetsHandler from './helpers/app-assets-handler';
|
||||
import webUIHandler from './helpers/web-ui-handler';
|
||||
import errorHandler from './helpers/error-handler';
|
||||
@@ -13,12 +17,16 @@ import {
|
||||
} from './helpers/create-bull-board-handler';
|
||||
import injectBullBoardHandler from './helpers/inject-bull-board-handler';
|
||||
import router from './routes';
|
||||
import { IRequest } from '@automatisch/types';
|
||||
|
||||
createBullBoardHandler(serverAdapter);
|
||||
|
||||
const app = express();
|
||||
|
||||
Sentry.init(app);
|
||||
|
||||
Sentry.attachRequestHandler(app);
|
||||
Sentry.attachTracingHandler(app);
|
||||
|
||||
injectBullBoardHandler(app, serverAdapter);
|
||||
|
||||
appAssetsHandler(app);
|
||||
@@ -26,17 +34,21 @@ appAssetsHandler(app);
|
||||
app.use(morgan);
|
||||
app.use(
|
||||
express.json({
|
||||
limit: appConfig.requestBodySizeLimit,
|
||||
verify(req, res, buf) {
|
||||
(req as IRequest).rawBody = buf;
|
||||
},
|
||||
})
|
||||
);
|
||||
app.use(
|
||||
express.urlencoded({
|
||||
extended: true,
|
||||
limit: appConfig.requestBodySizeLimit,
|
||||
verify(req, res, buf) {
|
||||
(req as IRequest).rawBody = buf;
|
||||
},
|
||||
})
|
||||
);
|
||||
app.use(express.urlencoded({
|
||||
extended: false,
|
||||
verify(req, res, buf) {
|
||||
(req as IRequest).rawBody = buf;
|
||||
},
|
||||
}));
|
||||
app.use(cors(corsOptions));
|
||||
app.use('/', router);
|
||||
|
||||
@@ -47,6 +59,8 @@ app.use(function (req, res, next) {
|
||||
next(createError(404));
|
||||
});
|
||||
|
||||
Sentry.attachErrorHandler(app);
|
||||
|
||||
app.use(errorHandler);
|
||||
|
||||
export default app;
|
||||
|
@@ -0,0 +1,36 @@
|
||||
import path from 'node:path';
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Create folder',
|
||||
key: 'createFolder',
|
||||
description: 'Create a new folder with the given parent folder and folder name',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Folder',
|
||||
key: 'parentFolder',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Enter the parent folder path, like /TextFiles/ or /Documents/Taxes/',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Folder Name',
|
||||
key: 'folderName',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Enter the name for the new folder',
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const parentFolder = $.step.parameters.parentFolder as string;
|
||||
const folderName = $.step.parameters.folderName as string;
|
||||
const folderPath = path.join(parentFolder, folderName);
|
||||
|
||||
const response = await $.http.post('/2/files/create_folder_v2', { path: folderPath });
|
||||
|
||||
$.setActionItem({ raw: response.data });
|
||||
},
|
||||
});
|
4
packages/backend/src/apps/dropbox/actions/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import createFolder from "./create-folder";
|
||||
import renameFile from "./rename-file";
|
||||
|
||||
export default [createFolder, renameFile];
|
@@ -0,0 +1,45 @@
|
||||
import path from 'node:path';
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Rename file',
|
||||
key: 'renameFile',
|
||||
description: 'Rename a file with the given file path and new name',
|
||||
arguments: [
|
||||
{
|
||||
label: 'File Path',
|
||||
key: 'filePath',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description:
|
||||
'Write the full path to the file such as /Folder1/File.pdf',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'New Name',
|
||||
key: 'newName',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: "Enter the new name for the file (without the extension, e.g., '.pdf')",
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const filePath = $.step.parameters.filePath as string;
|
||||
const newName = $.step.parameters.newName as string;
|
||||
const fileObject = path.parse(filePath);
|
||||
const newPath = path.format({
|
||||
dir: fileObject.dir,
|
||||
ext: fileObject.ext,
|
||||
name: newName,
|
||||
});
|
||||
|
||||
const response = await $.http.post('/2/files/move_v2', {
|
||||
from_path: filePath,
|
||||
to_path: newPath,
|
||||
});
|
||||
|
||||
$.setActionItem({ raw: response.data.metadata });
|
||||
},
|
||||
});
|
3
packages/backend/src/apps/dropbox/assets/favicon.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-label="Dropbox" role="img" viewBox="0 0 512 512" fill="#0061ff">
|
||||
<path d="M158 101l-99 63 295 188 99-63m-99-188l99 63-295 188-99-63m99 83l98 63 98-63-98-62z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 213 B |
22
packages/backend/src/apps/dropbox/auth/generate-auth-url.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { URLSearchParams } from 'url';
|
||||
import { IField, IGlobalVariable } from '@automatisch/types';
|
||||
import scopes from '../common/scopes';
|
||||
|
||||
export default async function generateAuthUrl($: IGlobalVariable) {
|
||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
||||
);
|
||||
const callbackUrl = oauthRedirectUrlField.value as string;
|
||||
|
||||
const searchParams = new URLSearchParams({
|
||||
client_id: $.auth.data.clientId as string,
|
||||
redirect_uri: callbackUrl,
|
||||
response_type: 'code',
|
||||
scope: scopes.join(' '),
|
||||
token_access_type: 'offline',
|
||||
});
|
||||
|
||||
const url = `${$.app.baseUrl}/oauth2/authorize?${searchParams.toString()}`;
|
||||
|
||||
await $.auth.set({ url });
|
||||
}
|
48
packages/backend/src/apps/dropbox/auth/index.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import generateAuthUrl from './generate-auth-url';
|
||||
import verifyCredentials from './verify-credentials';
|
||||
import isStillVerified from './is-still-verified';
|
||||
import refreshToken from './refresh-token';
|
||||
|
||||
export default {
|
||||
fields: [
|
||||
{
|
||||
key: 'oAuthRedirectUrl',
|
||||
label: 'OAuth Redirect URL',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: true,
|
||||
value: '{WEB_APP_URL}/app/dropbox/connections/add',
|
||||
placeholder: null,
|
||||
description:
|
||||
'When asked to input an OAuth callback or redirect URL in Dropbox OAuth, enter the URL above.',
|
||||
clickToCopy: true,
|
||||
},
|
||||
{
|
||||
key: 'clientId',
|
||||
label: 'App Key',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: null,
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'clientSecret',
|
||||
label: 'App 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 getCurrentAccount from '../common/get-current-account';
|
||||
|
||||
const isStillVerified = async ($: IGlobalVariable) => {
|
||||
const account = await getCurrentAccount($);
|
||||
return !!account;
|
||||
};
|
||||
|
||||
export default isStillVerified;
|
41
packages/backend/src/apps/dropbox/auth/refresh-token.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Buffer } from 'node:buffer';
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const refreshToken = async ($: IGlobalVariable) => {
|
||||
const params = {
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: $.auth.data.refreshToken as string,
|
||||
};
|
||||
|
||||
const basicAuthToken = Buffer
|
||||
.from(`${$.auth.data.clientId}:${$.auth.data.clientSecret}`)
|
||||
.toString('base64');
|
||||
|
||||
const { data } = await $.http.post(
|
||||
'oauth2/token',
|
||||
null,
|
||||
{
|
||||
params,
|
||||
headers: {
|
||||
Authorization: `Basic ${basicAuthToken}`
|
||||
},
|
||||
additionalProperties: {
|
||||
skipAddingAuthHeader: true
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const {
|
||||
access_token: accessToken,
|
||||
expires_in: expiresIn,
|
||||
token_type: tokenType,
|
||||
} = data;
|
||||
|
||||
await $.auth.set({
|
||||
accessToken,
|
||||
expiresIn,
|
||||
tokenType,
|
||||
});
|
||||
};
|
||||
|
||||
export default refreshToken;
|
102
packages/backend/src/apps/dropbox/auth/verify-credentials.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { IGlobalVariable, IField } from '@automatisch/types';
|
||||
import getCurrentAccount from '../common/get-current-account';
|
||||
|
||||
type TAccount = {
|
||||
account_id: string,
|
||||
name: {
|
||||
given_name: string,
|
||||
surname: string,
|
||||
familiar_name: string,
|
||||
display_name: string,
|
||||
abbreviated_name: string,
|
||||
},
|
||||
email: string,
|
||||
email_verified: boolean,
|
||||
disabled: boolean,
|
||||
country: string,
|
||||
locale: string,
|
||||
referral_link: string,
|
||||
is_paired: boolean,
|
||||
account_type: {
|
||||
".tag": string,
|
||||
},
|
||||
root_info: {
|
||||
".tag": string,
|
||||
root_namespace_id: string,
|
||||
home_namespace_id: string,
|
||||
},
|
||||
}
|
||||
|
||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
||||
);
|
||||
const redirectUrl = oauthRedirectUrlField.value as string;
|
||||
const params = {
|
||||
client_id: $.auth.data.clientId as string,
|
||||
redirect_uri: redirectUrl,
|
||||
client_secret: $.auth.data.clientSecret as string,
|
||||
code: $.auth.data.code as string,
|
||||
grant_type: 'authorization_code',
|
||||
}
|
||||
const { data: verifiedCredentials } = await $.http.post(
|
||||
'/oauth2/token',
|
||||
null,
|
||||
{ params }
|
||||
);
|
||||
|
||||
const {
|
||||
access_token: accessToken,
|
||||
refresh_token: refreshToken,
|
||||
expires_in: expiresIn,
|
||||
scope: scope,
|
||||
token_type: tokenType,
|
||||
account_id: accountId,
|
||||
team_id: teamId,
|
||||
id_token: idToken,
|
||||
uid,
|
||||
} = verifiedCredentials;
|
||||
|
||||
await $.auth.set({
|
||||
accessToken,
|
||||
refreshToken,
|
||||
expiresIn,
|
||||
scope,
|
||||
tokenType,
|
||||
accountId,
|
||||
teamId,
|
||||
idToken,
|
||||
uid
|
||||
});
|
||||
|
||||
const account = await getCurrentAccount($) as TAccount;
|
||||
|
||||
await $.auth.set({
|
||||
accountId: account.account_id,
|
||||
name: {
|
||||
givenName: account.name.given_name,
|
||||
surname: account.name.surname,
|
||||
familiarName: account.name.familiar_name,
|
||||
displayName: account.name.display_name,
|
||||
abbreviatedName: account.name.abbreviated_name,
|
||||
},
|
||||
email: account.email,
|
||||
emailVerified: account.email_verified,
|
||||
disabled: account.disabled,
|
||||
country: account.country,
|
||||
locale: account.locale,
|
||||
referralLink: account.referral_link,
|
||||
isPaired: account.is_paired,
|
||||
accountType: {
|
||||
".tag": account.account_type['.tag'],
|
||||
},
|
||||
rootInfo: {
|
||||
".tag": account.root_info['.tag'],
|
||||
rootNamespaceId: account.root_info.root_namespace_id,
|
||||
homeNamespaceId: account.root_info.home_namespace_id,
|
||||
},
|
||||
screenName: `${account.name.display_name} - ${account.email}`,
|
||||
});
|
||||
};
|
||||
|
||||
export default verifyCredentials;
|
13
packages/backend/src/apps/dropbox/common/add-auth-header.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { TBeforeRequest } from '@automatisch/types';
|
||||
|
||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||
requestConfig.headers['Content-Type'] = 'application/json';
|
||||
|
||||
if (!requestConfig.additionalProperties?.skipAddingAuthHeader && $.auth.data?.accessToken) {
|
||||
requestConfig.headers.Authorization = `Bearer ${$.auth.data.accessToken}`;
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default addAuthHeader;
|
@@ -0,0 +1,8 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
|
||||
const getCurrentAccount = async ($: IGlobalVariable): Promise<IJSONObject> => {
|
||||
const response = await $.http.post('/2/users/get_current_account', null);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export default getCurrentAccount;
|
8
packages/backend/src/apps/dropbox/common/scopes.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
const scopes = [
|
||||
'account_info.read',
|
||||
'files.metadata.read',
|
||||
'files.content.write',
|
||||
'files.content.read',
|
||||
];
|
||||
|
||||
export default scopes;
|
0
packages/backend/src/apps/dropbox/index.d.ts
vendored
Normal file
18
packages/backend/src/apps/dropbox/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import addAuthHeader from './common/add-auth-header';
|
||||
import auth from './auth';
|
||||
import actions from './actions';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Dropbox',
|
||||
key: 'dropbox',
|
||||
iconUrl: '{BASE_URL}/apps/dropbox/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/dropbox/connection',
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://dropbox.com',
|
||||
apiBaseUrl: 'https://api.dropboxapi.com',
|
||||
primaryColor: '0061ff',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
actions,
|
||||
});
|
79
packages/backend/src/apps/filter/actions/continue/index.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
type TGroupItem = {
|
||||
key: string;
|
||||
operator: keyof TOperators;
|
||||
value: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
type TGroup = Record<'and', TGroupItem[]>;
|
||||
|
||||
const isEqual = (a: string, b: string) => a === b;
|
||||
const isNotEqual = (a: string, b: string) => !isEqual(a, b)
|
||||
const isGreaterThan = (a: string, b: string) => Number(a) > Number(b);
|
||||
const isLessThan = (a: string, b: string) => Number(a) < Number(b);
|
||||
const isGreaterThanOrEqual = (a: string, b: string) => Number(a) >= Number(b);
|
||||
const isLessThanOrEqual = (a: string, b: string) => Number(a) <= Number(b);
|
||||
const contains = (a: string, b: string) => a.includes(b);
|
||||
const doesNotContain = (a: string, b: string) => !contains(a, b);
|
||||
|
||||
type TOperatorFunc = (a: string, b: string) => boolean;
|
||||
|
||||
type TOperators = {
|
||||
equal: TOperatorFunc;
|
||||
not_equal: TOperatorFunc;
|
||||
greater_than: TOperatorFunc;
|
||||
less_than: TOperatorFunc;
|
||||
greater_than_or_equal: TOperatorFunc;
|
||||
less_than_or_equal: TOperatorFunc;
|
||||
contains: TOperatorFunc;
|
||||
not_contains: TOperatorFunc;
|
||||
};
|
||||
|
||||
const operators: TOperators = {
|
||||
'equal': isEqual,
|
||||
'not_equal': isNotEqual,
|
||||
'greater_than': isGreaterThan,
|
||||
'less_than': isLessThan,
|
||||
'greater_than_or_equal': isGreaterThanOrEqual,
|
||||
'less_than_or_equal': isLessThanOrEqual,
|
||||
'contains': contains,
|
||||
'not_contains': doesNotContain,
|
||||
};
|
||||
|
||||
const operate = (operation: keyof TOperators, a: string, b: string) => {
|
||||
return operators[operation](a, b);
|
||||
};
|
||||
|
||||
export default defineAction({
|
||||
name: 'Continue if conditions match',
|
||||
key: 'continueIfMatches',
|
||||
description: 'Let the execution continue if the conditions match',
|
||||
arguments: [],
|
||||
|
||||
async run($) {
|
||||
const orGroups = $.step.parameters.or as TGroup[];
|
||||
|
||||
const matchingGroups = orGroups.reduce((groups, group) => {
|
||||
const matchingConditions = group.and
|
||||
.filter((condition) => operate(condition.operator, condition.key, condition.value));
|
||||
|
||||
if (matchingConditions.length) {
|
||||
return groups.concat([{ and: matchingConditions }]);
|
||||
}
|
||||
|
||||
return groups;
|
||||
}, []);
|
||||
|
||||
if (matchingGroups.length === 0) {
|
||||
$.execution.exit();
|
||||
}
|
||||
|
||||
$.setActionItem({
|
||||
raw: {
|
||||
or: matchingGroups,
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
3
packages/backend/src/apps/filter/actions/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import continueIfMatches from './continue';
|
||||
|
||||
export default [continueIfMatches];
|
8
packages/backend/src/apps/filter/assets/favicon.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg width="800px" height="800px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Shape" fill="#000000" transform="translate(42.666667, 85.333333)">
|
||||
<path d="M3.55271368e-14,1.42108547e-14 L191.565013,234.666667 L192,234.666667 L192,384 L234.666667,384 L234.666667,234.666667 L426.666667,1.42108547e-14 L3.55271368e-14,1.42108547e-14 Z M214.448,192 L211.81248,192 L89.9076267,42.6666667 L336.630187,42.6666667 L214.448,192 Z">
|
||||
</path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 628 B |
0
packages/backend/src/apps/filter/index.d.ts
vendored
Normal file
14
packages/backend/src/apps/filter/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import defineApp from '../../helpers/define-app';
|
||||
import actions from './actions';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Filter',
|
||||
key: 'filter',
|
||||
iconUrl: '{BASE_URL}/apps/filter/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/filter/connection',
|
||||
supportsConnections: false,
|
||||
baseUrl: '',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '001F52',
|
||||
actions,
|
||||
});
|
@@ -0,0 +1,8 @@
|
||||
<svg viewBox="0 0 87.3 78" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m6.6 66.85 3.85 6.65c.8 1.4 1.95 2.5 3.3 3.3l13.75-23.8h-27.5c0 1.55.4 3.1 1.2 4.5z" fill="#0066da"/>
|
||||
<path d="m43.65 25-13.75-23.8c-1.35.8-2.5 1.9-3.3 3.3l-25.4 44a9.06 9.06 0 0 0 -1.2 4.5h27.5z" fill="#00ac47"/>
|
||||
<path d="m73.55 76.8c1.35-.8 2.5-1.9 3.3-3.3l1.6-2.75 7.65-13.25c.8-1.4 1.2-2.95 1.2-4.5h-27.502l5.852 11.5z" fill="#ea4335"/>
|
||||
<path d="m43.65 25 13.75-23.8c-1.35-.8-2.9-1.2-4.5-1.2h-18.5c-1.6 0-3.15.45-4.5 1.2z" fill="#00832d"/>
|
||||
<path d="m59.8 53h-32.3l-13.75 23.8c1.35.8 2.9 1.2 4.5 1.2h50.8c1.6 0 3.15-.45 4.5-1.2z" fill="#2684fc"/>
|
||||
<path d="m73.4 26.5-12.7-22c-.8-1.4-1.95-2.5-3.3-3.3l-13.75 23.8 16.15 28h27.45c0-1.55-.4-3.1-1.2-4.5z" fill="#ffba00"/>
|
||||
</svg>
|
After Width: | Height: | Size: 755 B |
@@ -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-drive/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-drive/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-drive/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 ($.auth.data?.accessToken) {
|
||||
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default addAuthHeader;
|
@@ -0,0 +1,7 @@
|
||||
const authScope: string[] = [
|
||||
'https://www.googleapis.com/auth/drive',
|
||||
'https://www.googleapis.com/auth/userinfo.email',
|
||||
'https://www.googleapis.com/auth/userinfo.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,4 @@
|
||||
import listFolders from './list-folders';
|
||||
import listDrives from './list-drives';
|
||||
|
||||
export default [listFolders, listDrives];
|
@@ -0,0 +1,35 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
|
||||
export default {
|
||||
name: 'List drives',
|
||||
key: 'listDrives',
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
const drives: {
|
||||
data: IJSONObject[];
|
||||
} = {
|
||||
data: [{ value: null, name: 'My Google Drive' }],
|
||||
};
|
||||
|
||||
const params = {
|
||||
pageSize: 100,
|
||||
pageToken: undefined as unknown as string,
|
||||
};
|
||||
|
||||
do {
|
||||
const { data } = await $.http.get(`/v3/drives`, { params });
|
||||
params.pageToken = data.nextPageToken;
|
||||
|
||||
if (data.drives) {
|
||||
for (const drive of data.drives) {
|
||||
drives.data.push({
|
||||
value: drive.id,
|
||||
name: drive.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
} while (params.pageToken);
|
||||
|
||||
return drives;
|
||||
},
|
||||
};
|
@@ -0,0 +1,40 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
|
||||
export default {
|
||||
name: 'List Folders',
|
||||
key: 'listFolders',
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
const folders: {
|
||||
data: IJSONObject[];
|
||||
} = {
|
||||
data: [],
|
||||
};
|
||||
|
||||
const params = {
|
||||
q: `mimeType='application/vnd.google-apps.folder'`,
|
||||
orderBy: 'createdTime desc',
|
||||
pageToken: undefined as unknown as string,
|
||||
pageSize: 1000,
|
||||
};
|
||||
|
||||
do {
|
||||
const { data } = await $.http.get(
|
||||
`https://www.googleapis.com/drive/v3/files`,
|
||||
{
|
||||
params,
|
||||
}
|
||||
);
|
||||
params.pageToken = data.nextPageToken;
|
||||
|
||||
for (const file of data.files) {
|
||||
folders.data.push({
|
||||
value: file.id,
|
||||
name: file.name,
|
||||
});
|
||||
}
|
||||
} while (params.pageToken);
|
||||
|
||||
return folders;
|
||||
},
|
||||
};
|
0
packages/backend/src/apps/google-drive/index.d.ts
vendored
Normal file
20
packages/backend/src/apps/google-drive/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 Drive',
|
||||
key: 'google-drive',
|
||||
baseUrl: 'https://drive.google.com',
|
||||
apiBaseUrl: 'https://www.googleapis.com/drive',
|
||||
iconUrl: '{BASE_URL}/apps/google-drive/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/google-drive/connection',
|
||||
primaryColor: '1FA463',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
dynamicData,
|
||||
});
|
6
packages/backend/src/apps/google-drive/triggers/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import newFiles from './new-files';
|
||||
import newFilesInFolder from './new-files-in-folder';
|
||||
import newFolders from './new-folders';
|
||||
import updatedFiles from './updated-files';
|
||||
|
||||
export default [newFiles, newFilesInFolder, newFolders, updatedFiles];
|
@@ -0,0 +1,54 @@
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
import newFilesInFolder from './new-files-in-folder';
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'New Files in Folder',
|
||||
key: 'newFilesInFolder',
|
||||
pollInterval: 15,
|
||||
description:
|
||||
'Triggers when a new file is added directly to a specific folder (but not its subfolder).',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Drive',
|
||||
key: 'driveId',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
description:
|
||||
'The Google Drive where your file resides. If nothing is selected, then your personal Google Drive will be used.',
|
||||
variables: false,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listDrives',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Folder',
|
||||
key: 'folderId',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
description:
|
||||
'Check a specific folder for new files. Please note: new files added to subfolders inside the folder you choose here will NOT trigger this flow. Defaults to the top-level folder if none is picked.',
|
||||
variables: false,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listFolders',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
await newFilesInFolder($);
|
||||
},
|
||||
});
|
@@ -0,0 +1,36 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const newFilesInFolder = async ($: IGlobalVariable) => {
|
||||
let q = "mimeType!='application/vnd.google-apps.folder'";
|
||||
if ($.step.parameters.folderId) {
|
||||
q += ` and '${$.step.parameters.folderId}' in parents`;
|
||||
} else {
|
||||
q += ` and parents in 'root'`;
|
||||
}
|
||||
const params = {
|
||||
pageToken: undefined as unknown as string,
|
||||
orderBy: 'createdTime desc',
|
||||
fields: '*',
|
||||
pageSize: 1000,
|
||||
q,
|
||||
driveId: $.step.parameters.driveId,
|
||||
};
|
||||
|
||||
do {
|
||||
const { data } = await $.http.get(`/v3/files`, { params });
|
||||
params.pageToken = data.nextPageToken;
|
||||
|
||||
if (data.files?.length) {
|
||||
for (const file of data.files) {
|
||||
$.pushTriggerItem({
|
||||
raw: file,
|
||||
meta: {
|
||||
internalId: file.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} while (params.pageToken);
|
||||
};
|
||||
|
||||
export default newFilesInFolder;
|
@@ -0,0 +1,34 @@
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
import newFiles from './new-files';
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'New Files',
|
||||
key: 'newFiles',
|
||||
pollInterval: 15,
|
||||
description: 'Triggers when any new file is added (inside of any folder).',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Drive',
|
||||
key: 'driveId',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
description:
|
||||
'The Google Drive where your file resides. If nothing is selected, then your personal Google Drive will be used.',
|
||||
variables: false,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listDrives',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
await newFiles($);
|
||||
},
|
||||
});
|
@@ -0,0 +1,30 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const newFiles = async ($: IGlobalVariable) => {
|
||||
const params = {
|
||||
pageToken: undefined as unknown as string,
|
||||
orderBy: 'createdTime desc',
|
||||
fields: '*',
|
||||
pageSize: 1000,
|
||||
q: `mimeType!='application/vnd.google-apps.folder'`,
|
||||
driveId: $.step.parameters.driveId,
|
||||
};
|
||||
|
||||
do {
|
||||
const { data } = await $.http.get('/v3/files', { params });
|
||||
params.pageToken = data.nextPageToken;
|
||||
|
||||
if (data.files?.length) {
|
||||
for (const file of data.files) {
|
||||
$.pushTriggerItem({
|
||||
raw: file,
|
||||
meta: {
|
||||
internalId: file.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} while (params.pageToken);
|
||||
};
|
||||
|
||||
export default newFiles;
|
@@ -0,0 +1,54 @@
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
import newFolders from './new-folders';
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'New Folders',
|
||||
key: 'newFolders',
|
||||
pollInterval: 15,
|
||||
description:
|
||||
'Triggers when a new folder is added directly to a specific folder (but not its subfolder).',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Drive',
|
||||
key: 'driveId',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
description:
|
||||
'The Google Drive where your file resides. If nothing is selected, then your personal Google Drive will be used.',
|
||||
variables: false,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listDrives',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Folder',
|
||||
key: 'folderId',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
description:
|
||||
'Check a specific folder for new subfolders. Please note: new folders added to subfolders inside the folder you choose here will NOT trigger this flow. Defaults to the top-level folder if none is picked.',
|
||||
variables: false,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listFolders',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
await newFolders($);
|
||||
},
|
||||
});
|
@@ -0,0 +1,37 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const newFolders = async ($: IGlobalVariable) => {
|
||||
let q = "mimeType='application/vnd.google-apps.folder'";
|
||||
if ($.step.parameters.folderId) {
|
||||
q += ` and '${$.step.parameters.folderId}' in parents`;
|
||||
} else {
|
||||
q += ` and parents in 'root'`;
|
||||
}
|
||||
|
||||
const params = {
|
||||
pageToken: undefined as unknown as string,
|
||||
orderBy: 'createdTime desc',
|
||||
fields: '*',
|
||||
pageSize: 1000,
|
||||
q,
|
||||
driveId: $.step.parameters.driveId,
|
||||
};
|
||||
|
||||
do {
|
||||
const { data } = await $.http.get(`/v3/files`, { params });
|
||||
params.pageToken = data.nextPageToken;
|
||||
|
||||
if (data.files?.length) {
|
||||
for (const file of data.files) {
|
||||
$.pushTriggerItem({
|
||||
raw: file,
|
||||
meta: {
|
||||
internalId: file.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} while (params.pageToken);
|
||||
};
|
||||
|
||||
export default newFolders;
|
@@ -0,0 +1,71 @@
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
import updatedFiles from './updated-files';
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'Updated Files',
|
||||
key: 'updatedFiles',
|
||||
pollInterval: 15,
|
||||
description:
|
||||
'Triggers when a file is updated in a specific folder (but not its subfolder).',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Drive',
|
||||
key: 'driveId',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
description:
|
||||
'The Google Drive where your file resides. If nothing is selected, then your personal Google Drive will be used.',
|
||||
variables: false,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listDrives',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Folder',
|
||||
key: 'folderId',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
description:
|
||||
'Check a specific folder for updated files. Please note: files located in subfolders of the folder you choose here will NOT trigger this flow. Defaults to the top-level folder if none is picked.',
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listFolders',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Include Deleted',
|
||||
key: 'includeDeleted',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
value: true,
|
||||
description: 'Should this trigger also on files that are deleted?',
|
||||
options: [
|
||||
{
|
||||
label: 'Yes',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: 'No',
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
await updatedFiles($);
|
||||
},
|
||||
});
|
@@ -0,0 +1,41 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const updatedFiles = async ($: IGlobalVariable) => {
|
||||
let q = `mimeType!='application/vnd.google-apps.folder'`;
|
||||
if ($.step.parameters.includeDeleted === false) {
|
||||
q += ` and trashed=${$.step.parameters.includeDeleted}`;
|
||||
}
|
||||
|
||||
if ($.step.parameters.folderId) {
|
||||
q += ` and '${$.step.parameters.folderId}' in parents`;
|
||||
} else {
|
||||
q += ` and parents in 'root'`;
|
||||
}
|
||||
|
||||
const params = {
|
||||
pageToken: undefined as unknown as string,
|
||||
orderBy: 'modifiedTime desc',
|
||||
fields: '*',
|
||||
pageSize: 1000,
|
||||
q,
|
||||
driveId: $.step.parameters.driveId,
|
||||
};
|
||||
|
||||
do {
|
||||
const { data } = await $.http.get(`/v3/files`, { params });
|
||||
params.pageToken = data.nextPageToken;
|
||||
|
||||
if (data.files?.length) {
|
||||
for (const file of data.files) {
|
||||
$.pushTriggerItem({
|
||||
raw: file,
|
||||
meta: {
|
||||
internalId: `${file.id}-${file.modifiedTime}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} while (params.pageToken);
|
||||
};
|
||||
|
||||
export default updatedFiles;
|
89
packages/backend/src/apps/google-sheets/assets/favicon.svg
Normal file
@@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="49px" height="67px" viewBox="0 0 49 67" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 54.1 (76490) - https://sketchapp.com -->
|
||||
<title>Sheets-icon</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-1"></path>
|
||||
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-3"></path>
|
||||
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-5"></path>
|
||||
<linearGradient x1="50.0053945%" y1="8.58610612%" x2="50.0053945%" y2="100.013939%" id="linearGradient-7">
|
||||
<stop stop-color="#263238" stop-opacity="0.2" offset="0%"></stop>
|
||||
<stop stop-color="#263238" stop-opacity="0.02" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-8"></path>
|
||||
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-10"></path>
|
||||
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-12"></path>
|
||||
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="path-14"></path>
|
||||
<radialGradient cx="3.16804688%" cy="2.71744318%" fx="3.16804688%" fy="2.71744318%" r="161.248516%" gradientTransform="translate(0.031680,0.027174),scale(1.000000,0.727273),translate(-0.031680,-0.027174)" id="radialGradient-16">
|
||||
<stop stop-color="#FFFFFF" stop-opacity="0.1" offset="0%"></stop>
|
||||
<stop stop-color="#FFFFFF" stop-opacity="0" offset="100%"></stop>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Consumer-Apps-Sheets-Large-VD-R8-" transform="translate(-451.000000, -451.000000)">
|
||||
<g id="Hero" transform="translate(0.000000, 63.000000)">
|
||||
<g id="Personal" transform="translate(277.000000, 299.000000)">
|
||||
<g id="Sheets-icon" transform="translate(174.833333, 89.958333)">
|
||||
<g id="Group">
|
||||
<g id="Clipped">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<g id="SVGID_1_"></g>
|
||||
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L36.9791667,10.3541667 L29.5833333,0 Z" id="Path" fill="#0F9D58" fill-rule="nonzero" mask="url(#mask-2)"></path>
|
||||
</g>
|
||||
<g id="Clipped">
|
||||
<mask id="mask-4" fill="white">
|
||||
<use xlink:href="#path-3"></use>
|
||||
</mask>
|
||||
<g id="SVGID_1_"></g>
|
||||
<path d="M11.8333333,31.8020833 L11.8333333,53.25 L35.5,53.25 L35.5,31.8020833 L11.8333333,31.8020833 Z M22.1875,50.2916667 L14.7916667,50.2916667 L14.7916667,46.59375 L22.1875,46.59375 L22.1875,50.2916667 Z M22.1875,44.375 L14.7916667,44.375 L14.7916667,40.6770833 L22.1875,40.6770833 L22.1875,44.375 Z M22.1875,38.4583333 L14.7916667,38.4583333 L14.7916667,34.7604167 L22.1875,34.7604167 L22.1875,38.4583333 Z M32.5416667,50.2916667 L25.1458333,50.2916667 L25.1458333,46.59375 L32.5416667,46.59375 L32.5416667,50.2916667 Z M32.5416667,44.375 L25.1458333,44.375 L25.1458333,40.6770833 L32.5416667,40.6770833 L32.5416667,44.375 Z M32.5416667,38.4583333 L25.1458333,38.4583333 L25.1458333,34.7604167 L32.5416667,34.7604167 L32.5416667,38.4583333 Z" id="Shape" fill="#F1F1F1" fill-rule="nonzero" mask="url(#mask-4)"></path>
|
||||
</g>
|
||||
<g id="Clipped">
|
||||
<mask id="mask-6" fill="white">
|
||||
<use xlink:href="#path-5"></use>
|
||||
</mask>
|
||||
<g id="SVGID_1_"></g>
|
||||
<polygon id="Path" fill="url(#linearGradient-7)" fill-rule="nonzero" mask="url(#mask-6)" points="30.8813021 16.4520313 47.3333333 32.9003646 47.3333333 17.75"></polygon>
|
||||
</g>
|
||||
<g id="Clipped">
|
||||
<mask id="mask-9" fill="white">
|
||||
<use xlink:href="#path-8"></use>
|
||||
</mask>
|
||||
<g id="SVGID_1_"></g>
|
||||
<g id="Group" mask="url(#mask-9)">
|
||||
<g transform="translate(26.625000, -2.958333)">
|
||||
<path d="M2.95833333,2.95833333 L2.95833333,16.2708333 C2.95833333,18.7225521 4.94411458,20.7083333 7.39583333,20.7083333 L20.7083333,20.7083333 L2.95833333,2.95833333 Z" id="Path" fill="#87CEAC" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Clipped">
|
||||
<mask id="mask-11" fill="white">
|
||||
<use xlink:href="#path-10"></use>
|
||||
</mask>
|
||||
<g id="SVGID_1_"></g>
|
||||
<path d="M4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,4.80729167 C0,2.36666667 1.996875,0.369791667 4.4375,0.369791667 L29.5833333,0.369791667 L29.5833333,0 L4.4375,0 Z" id="Path" fill-opacity="0.2" fill="#FFFFFF" fill-rule="nonzero" mask="url(#mask-11)"></path>
|
||||
</g>
|
||||
<g id="Clipped">
|
||||
<mask id="mask-13" fill="white">
|
||||
<use xlink:href="#path-12"></use>
|
||||
</mask>
|
||||
<g id="SVGID_1_"></g>
|
||||
<path d="M42.8958333,64.7135417 L4.4375,64.7135417 C1.996875,64.7135417 0,62.7166667 0,60.2760417 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,60.2760417 C47.3333333,62.7166667 45.3364583,64.7135417 42.8958333,64.7135417 Z" id="Path" fill-opacity="0.2" fill="#263238" fill-rule="nonzero" mask="url(#mask-13)"></path>
|
||||
</g>
|
||||
<g id="Clipped">
|
||||
<mask id="mask-15" fill="white">
|
||||
<use xlink:href="#path-14"></use>
|
||||
</mask>
|
||||
<g id="SVGID_1_"></g>
|
||||
<path d="M34.0208333,17.75 C31.5691146,17.75 29.5833333,15.7642188 29.5833333,13.3125 L29.5833333,13.6822917 C29.5833333,16.1340104 31.5691146,18.1197917 34.0208333,18.1197917 L47.3333333,18.1197917 L47.3333333,17.75 L34.0208333,17.75 Z" id="Path" fill-opacity="0.1" fill="#263238" fill-rule="nonzero" mask="url(#mask-15)"></path>
|
||||
</g>
|
||||
</g>
|
||||
<path d="M29.5833333,0 L4.4375,0 C1.996875,0 0,1.996875 0,4.4375 L0,60.6458333 C0,63.0864583 1.996875,65.0833333 4.4375,65.0833333 L42.8958333,65.0833333 C45.3364583,65.0833333 47.3333333,63.0864583 47.3333333,60.6458333 L47.3333333,17.75 L29.5833333,0 Z" id="Path" fill="url(#radialGradient-16)" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 9.0 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-sheets/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-sheets/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;
|
@@ -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 ($.auth.data?.accessToken) {
|
||||
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default addAuthHeader;
|
@@ -0,0 +1,8 @@
|
||||
const authScope: string[] = [
|
||||
'https://www.googleapis.com/auth/drive',
|
||||
'https://www.googleapis.com/auth/spreadsheets',
|
||||
'https://www.googleapis.com/auth/userinfo.email',
|
||||
'https://www.googleapis.com/auth/userinfo.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 listDrives from './list-drives';
|
||||
|
||||
export default [listDrives];
|
@@ -0,0 +1,38 @@
|
||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||
|
||||
export default {
|
||||
name: 'List drives',
|
||||
key: 'listDrives',
|
||||
|
||||
async run($: IGlobalVariable) {
|
||||
const drives: {
|
||||
data: IJSONObject[];
|
||||
} = {
|
||||
data: [{ value: null, name: 'My Google Drive' }],
|
||||
};
|
||||
|
||||
const params = {
|
||||
pageSize: 100,
|
||||
pageToken: undefined as unknown as string,
|
||||
};
|
||||
|
||||
do {
|
||||
const { data } = await $.http.get(
|
||||
`https://www.googleapis.com/drive/v3/drives`,
|
||||
{ params }
|
||||
);
|
||||
params.pageToken = data.nextPageToken;
|
||||
|
||||
if (data.drives) {
|
||||
for (const drive of data.drives) {
|
||||
drives.data.push({
|
||||
value: drive.id,
|
||||
name: drive.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
} while (params.pageToken);
|
||||
|
||||
return drives;
|
||||
},
|
||||
};
|
0
packages/backend/src/apps/google-sheets/index.d.ts
vendored
Normal file
20
packages/backend/src/apps/google-sheets/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 Sheets',
|
||||
key: 'google-sheets',
|
||||
baseUrl: 'https://docs.google.com/spreadsheets',
|
||||
apiBaseUrl: 'https://sheets.googleapis.com',
|
||||
iconUrl: '{BASE_URL}/apps/google-sheets/assets/favicon.svg',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/google-sheets/connection',
|
||||
primaryColor: '0F9D58',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
dynamicData,
|
||||
});
|
@@ -0,0 +1,3 @@
|
||||
import newSpreadsheets from './new-spreadsheets';
|
||||
|
||||
export default [newSpreadsheets];
|
@@ -0,0 +1,33 @@
|
||||
import defineTrigger from '../../../../helpers/define-trigger';
|
||||
import newSpreadsheets from './new-spreadsheets'
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'New Spreadsheets',
|
||||
key: 'newSpreadsheets',
|
||||
pollInterval: 15,
|
||||
description: 'Triggers when you create a new spreadsheet.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Drive',
|
||||
key: 'driveId',
|
||||
type: 'dropdown' as const,
|
||||
required: false,
|
||||
description: 'The Google Drive where your spreadsheet resides. If nothing is selected, then your personal Google Drive will be used.',
|
||||
variables: false,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listDrives',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
await newSpreadsheets($);
|
||||
},
|
||||
});
|
@@ -0,0 +1,33 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const newSpreadsheets = async ($: IGlobalVariable) => {
|
||||
const params = {
|
||||
pageToken: undefined as unknown as string,
|
||||
orderBy: 'createdTime desc',
|
||||
q: `mimeType='application/vnd.google-apps.spreadsheet'`,
|
||||
fields: '*',
|
||||
pageSize: 1000,
|
||||
driveId: $.step.parameters.driveId,
|
||||
};
|
||||
|
||||
do {
|
||||
const { data } = await $.http.get(
|
||||
'https://www.googleapis.com/drive/v3/files',
|
||||
{ params }
|
||||
);
|
||||
params.pageToken = data.nextPageToken;
|
||||
|
||||
if (data.files?.length) {
|
||||
for (const file of data.files) {
|
||||
$.pushTriggerItem({
|
||||
raw: file,
|
||||
meta: {
|
||||
internalId: file.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} while (params.pageToken);
|
||||
};
|
||||
|
||||
export default newSpreadsheets;
|
@@ -0,0 +1,111 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
type TMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
|
||||
|
||||
type THeaderEntry = {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
type THeaderEntries = THeaderEntry[];
|
||||
|
||||
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,
|
||||
},
|
||||
{
|
||||
label: 'Headers',
|
||||
key: 'headers',
|
||||
type: 'dynamic' as const,
|
||||
required: false,
|
||||
description: 'Add or remove headers as needed',
|
||||
value: [{
|
||||
key: 'Content-Type',
|
||||
value: 'application/json'
|
||||
}],
|
||||
fields: [
|
||||
{
|
||||
label: 'Key',
|
||||
key: 'key',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Header key',
|
||||
variables: false,
|
||||
},
|
||||
{
|
||||
label: 'Value',
|
||||
key: 'value',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Header value',
|
||||
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 headers = $.step.parameters.headers as THeaderEntries;
|
||||
const maxFileSize = 25 * 1024 * 1024; // 25MB
|
||||
|
||||
const headersObject = headers.reduce((result, entry) => ({ ...result, [entry.key]: entry.value }), {})
|
||||
|
||||
const metadataResponse = await $.http.head(url, { headers: headersObject });
|
||||
|
||||
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: headersObject,
|
||||
});
|
||||
|
||||
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
@@ -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
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,
|
||||
});
|
@@ -1,8 +1,8 @@
|
||||
import { TBeforeRequest } from '@automatisch/types';
|
||||
|
||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||
if ($.auth.data.apiBaseUrl) {
|
||||
requestConfig.baseURL = $.auth.data.apiBaseUrl as string;
|
||||
if ($.auth.data.serverUrl) {
|
||||
requestConfig.baseURL = $.auth.data.serverUrl as string;
|
||||
}
|
||||
|
||||
if ($.auth.data?.username && $.auth.data?.password) {
|
||||
|
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
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,
|
||||
});
|
@@ -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
@@ -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,
|
||||
});
|
||||
|
3
packages/backend/src/apps/signalwire/actions/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import sendSms from './send-sms';
|
||||
|
||||
export default [sendSms];
|
@@ -0,0 +1,63 @@
|
||||
import defineAction from '../../../../helpers/define-action';
|
||||
|
||||
export default defineAction({
|
||||
name: 'Send an SMS',
|
||||
key: 'sendSms',
|
||||
description: 'Sends an SMS',
|
||||
arguments: [
|
||||
{
|
||||
label: 'From Number',
|
||||
key: 'fromNumber',
|
||||
type: 'dropdown' as const,
|
||||
required: true,
|
||||
description:
|
||||
'The number to send the SMS from. Include only country code. Example: 491234567890',
|
||||
variables: true,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listIncomingPhoneNumbers',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'To Number',
|
||||
key: 'toNumber',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description:
|
||||
'The number to send the SMS to. Include only country code. Example: 491234567890',
|
||||
variables: true,
|
||||
},
|
||||
{
|
||||
label: 'Message',
|
||||
key: 'message',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'The content of the message.',
|
||||
variables: true,
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const requestPath = `/api/laml/2010-04-01/Accounts/${$.auth.data.accountSid}/Messages`;
|
||||
|
||||
const Body = $.step.parameters.message;
|
||||
const From = $.step.parameters.fromNumber;
|
||||
const To = '+' + ($.step.parameters.toNumber as string).trim();
|
||||
|
||||
const response = await $.http.post(requestPath, null, {
|
||||
params: {
|
||||
Body,
|
||||
From,
|
||||
To,
|
||||
}
|
||||
});
|
||||
|
||||
$.setActionItem({ raw: response.data });
|
||||
},
|
||||
});
|
1
packages/backend/src/apps/signalwire/assets/favicon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="a1100050-5390-497e-a7fa-2bb69ec95c7c" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 318.33 362.7"><defs><style>.a463a3b0-e95c-44d7-b809-5997966784eb{fill:#044ef4;}.b59fade4-5c2c-49f1-a6bf-917d1f195a19{fill:#f72a72;}</style></defs><path class="a463a3b0-e95c-44d7-b809-5997966784eb" d="M389.17,278c0,10.31-2.8,17.06-8.37,22.62q-50.07,49.95-100.19,99.85C269,412,252.06,412.54,240.55,402c-12.12-11.13-12.6-29.39-.75-41.47,15-15.34,30.32-30.47,45.56-45.63q27.47-27.3,55.06-54.45c8.91-8.75,20.84-11.07,31.77-6.1C383.3,259.36,388.79,268.25,389.17,278Z" transform="translate(-70.83 -46.81)"/><path class="a463a3b0-e95c-44d7-b809-5997966784eb" d="M70.84,172.94c.16-5.21,2.93-11.81,8.36-17.24q49.89-49.77,99.8-99.53c6.92-6.91,15.08-10.45,24.83-9.06,11.55,1.65,19.62,8.12,23.38,19.22s1.16,21.14-7,29.43q-23.87,24.21-48,48.1-26.22,26.07-52.58,52c-8.93,8.76-20.84,10.92-31.8,6.13C77.17,197.35,70.65,187.14,70.84,172.94Z" transform="translate(-70.83 -46.81)"/><path class="b59fade4-5c2c-49f1-a6bf-917d1f195a19" d="M93.68,210.69c3.79-.17,6.91-.08,10-.49a34.39,34.39,0,0,0,20.56-10.34c6.38-6.52,12.79-13,19.33-19.66,1.23,1.09,2,1.7,2.66,2.38q36.92,36.9,73.81,73.83c8.07,8.1,10.9,17.87,7.66,28.86-3.12,10.58-10.39,17.35-21.17,19.77-9.33,2.1-18.23.31-25.07-6.47C152.25,269.64,123.32,240.42,93.68,210.69Z" transform="translate(-70.83 -46.81)"/><path class="b59fade4-5c2c-49f1-a6bf-917d1f195a19" d="M366.57,246c-15-1.53-25.7,4.26-34.72,14.22-4.89,5.4-10.23,10.39-15.51,15.69-1.1-1-1.86-1.59-2.56-2.28q-36.94-36.93-73.86-73.89c-8.07-8.1-10.89-17.89-7.56-28.89,3.19-10.56,10.47-17.31,21.28-19.68,9.56-2.1,18.45,0,25.44,6.92q43,42.57,85.61,85.47C365.12,244,365.44,244.54,366.57,246Z" transform="translate(-70.83 -46.81)"/></svg>
|
After Width: | Height: | Size: 1.7 KiB |