Compare commits
424 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
06e8f5bfdc | ||
![]() |
c8dae9ea9a | ||
![]() |
cbfb5dd8a6 | ||
![]() |
09b2b7350c | ||
![]() |
e146793d32 | ||
![]() |
0bad2ead10 | ||
![]() |
25176884e7 | ||
![]() |
266d4cddb0 | ||
![]() |
6e529a4205 | ||
![]() |
d2cce278bd | ||
![]() |
b774a62f8c | ||
![]() |
66c12e1a92 | ||
![]() |
184d748890 | ||
![]() |
8cc732c8d1 | ||
![]() |
c688d67e4b | ||
![]() |
3408be2840 | ||
![]() |
e126066132 | ||
![]() |
cfec447d8a | ||
![]() |
805d1fdd52 | ||
![]() |
9299948072 | ||
![]() |
b02960a5ec | ||
![]() |
8134b6db6a | ||
![]() |
cd16a3cc15 | ||
![]() |
074e7828f3 | ||
![]() |
5f7d1f9219 | ||
![]() |
b29c6105a1 | ||
![]() |
1297f5d43c | ||
![]() |
f94a5385d7 | ||
![]() |
8ef2000e45 | ||
![]() |
30d496076b | ||
![]() |
5de06d4482 | ||
![]() |
97fa983305 | ||
![]() |
dccc3c5bc1 | ||
![]() |
ed12465975 | ||
![]() |
b78be222d7 | ||
![]() |
342990e1bf | ||
![]() |
2e5dfdbb0d | ||
![]() |
1790ef0ee6 | ||
![]() |
712a5756e2 | ||
![]() |
bf6ff6b0f7 | ||
![]() |
c6003b6695 | ||
![]() |
8352540fcb | ||
![]() |
22299868fa | ||
![]() |
0d126a8e2b | ||
![]() |
776d027dfa | ||
![]() |
fa9b6d1006 | ||
![]() |
89aa7ffc73 | ||
![]() |
fd971449ca | ||
![]() |
f7cd57e549 | ||
![]() |
34aadbfb09 | ||
![]() |
167195a01c | ||
![]() |
b0abf94191 | ||
![]() |
1009c71e72 | ||
![]() |
29b1695159 | ||
![]() |
44f782221f | ||
![]() |
bab25c51d9 | ||
![]() |
89277e1665 | ||
![]() |
1e9c5a1682 | ||
![]() |
0c75486c7a | ||
![]() |
8c39739880 | ||
![]() |
ace1f84094 | ||
![]() |
2cfd6739ca | ||
![]() |
e0d6f0d653 | ||
![]() |
a4d3b387d0 | ||
![]() |
7394aca02d | ||
![]() |
e812725182 | ||
![]() |
bb76bfcd8b | ||
![]() |
6ff6d0a7dc | ||
![]() |
ddc9867058 | ||
![]() |
ba0d46c6cd | ||
![]() |
369f04fdbc | ||
![]() |
09dd8abe23 | ||
![]() |
8c692758ae | ||
![]() |
32d39b88bd | ||
![]() |
8bd66da511 | ||
![]() |
f86799e494 | ||
![]() |
8d10f26f56 | ||
![]() |
297543f9dd | ||
![]() |
0c53ee8460 | ||
![]() |
862842e3e1 | ||
![]() |
a4fad360df | ||
![]() |
3ba4c8b3bf | ||
![]() |
2992236be4 | ||
![]() |
82161f028e | ||
![]() |
1bcaec144b | ||
![]() |
852d4bba0a | ||
![]() |
af56fa2830 | ||
![]() |
813646e392 | ||
![]() |
1ce31eefc6 | ||
![]() |
fdf53844e1 | ||
![]() |
be57a82302 | ||
![]() |
fb82e863e0 | ||
![]() |
101483409f | ||
![]() |
6fe863eec1 | ||
![]() |
fbb6526aac | ||
![]() |
5556aea913 | ||
![]() |
95dc5fb849 | ||
![]() |
062199d0e3 | ||
![]() |
53ce327516 | ||
![]() |
61a1ce57c2 | ||
![]() |
687295f772 | ||
![]() |
e5366534ed | ||
![]() |
66fe84e126 | ||
![]() |
0b6c28422c | ||
![]() |
ea667bb6a9 | ||
![]() |
c9ba219de1 | ||
![]() |
9df1b29d70 | ||
![]() |
3e34359fa9 | ||
![]() |
1818930d2f | ||
![]() |
c03e674001 | ||
![]() |
10a25b82e0 | ||
![]() |
3b2489d738 | ||
![]() |
5c4ca3c84f | ||
![]() |
06c4b7ed2e | ||
![]() |
5591f6ccc9 | ||
![]() |
63dfb6947e | ||
![]() |
b089069b8e | ||
![]() |
e76a99fd68 | ||
![]() |
a4ea6c1fad | ||
![]() |
64ebdce1b2 | ||
![]() |
2709491d59 | ||
![]() |
81beedede6 | ||
![]() |
33a2386d74 | ||
![]() |
0df5e5283e | ||
![]() |
560407b972 | ||
![]() |
f8c25ae508 | ||
![]() |
c524277665 | ||
![]() |
a70fb009c7 | ||
![]() |
11e67f2ea3 | ||
![]() |
e7118ffe15 | ||
![]() |
79e9455244 | ||
![]() |
6ca8e8958a | ||
![]() |
d3dc207166 | ||
![]() |
51e200533b | ||
![]() |
b8a25b87d8 | ||
![]() |
f4fe0a0d4f | ||
![]() |
1d4f829d29 | ||
![]() |
4afa79fca4 | ||
![]() |
ec22184087 | ||
![]() |
413f3db5b4 | ||
![]() |
8ddfcce787 | ||
![]() |
562341adfe | ||
![]() |
9519ec53ef | ||
![]() |
35bada360d | ||
![]() |
1f39765efe | ||
![]() |
cce5b3b533 | ||
![]() |
e77a03b855 | ||
![]() |
7a54ff212e | ||
![]() |
01340f4597 | ||
![]() |
af6fa80d20 | ||
![]() |
e7474dcb9e | ||
![]() |
0eb906d5df | ||
![]() |
c7babf227c | ||
![]() |
d5c81c14f5 | ||
![]() |
59278378e5 | ||
![]() |
990e69143d | ||
![]() |
c413ae06dc | ||
![]() |
456f8a30cc | ||
![]() |
19c4561feb | ||
![]() |
1392fed023 | ||
![]() |
ca81e14d63 | ||
![]() |
8e7a8a02ec | ||
![]() |
e45dfa94ed | ||
![]() |
a153787ae6 | ||
![]() |
cf37c43bc7 | ||
![]() |
d0aa2bca69 | ||
![]() |
7b3811e6ee | ||
![]() |
4054f551d4 | ||
![]() |
4eeda10f3f | ||
![]() |
4231784ed2 | ||
![]() |
61ff6986d3 | ||
![]() |
09bc0bba1e | ||
![]() |
b2bda8479e | ||
![]() |
09b255f99e | ||
![]() |
0800642a2a | ||
![]() |
48b2b006c0 | ||
![]() |
af4c1f08ec | ||
![]() |
87b26b6342 | ||
![]() |
706fb8d82f | ||
![]() |
f7ca59bd5f | ||
![]() |
6540d0ea53 | ||
![]() |
5995038e21 | ||
![]() |
337ba6ea87 | ||
![]() |
04c6183752 | ||
![]() |
0b63922f46 | ||
![]() |
81c39d7d93 | ||
![]() |
164d31dfbc | ||
![]() |
1a833aad52 | ||
![]() |
77246c1fde | ||
![]() |
56c08a3587 | ||
![]() |
ce6214dc0f | ||
![]() |
22002d50ac | ||
![]() |
ab4e94695d | ||
![]() |
47a01cec7e | ||
![]() |
0cf9bc1a32 | ||
![]() |
6552ebcd3c | ||
![]() |
5af1d94fc0 | ||
![]() |
a4ec7b3047 | ||
![]() |
66f5003d91 | ||
![]() |
0c754e4b4b | ||
![]() |
e008087c4a | ||
![]() |
53f63996bd | ||
![]() |
4fedf77991 | ||
![]() |
34331d8763 | ||
![]() |
2c21b7762c | ||
![]() |
7f9c2b687f | ||
![]() |
b452ed648c | ||
![]() |
98e4b843ea | ||
![]() |
da2884d53c | ||
![]() |
7f2937400a | ||
![]() |
a3a4a8e431 | ||
![]() |
3249c954d3 | ||
![]() |
7395d2a74e | ||
![]() |
2060b7b49d | ||
![]() |
d263726c19 | ||
![]() |
7e3325e959 | ||
![]() |
ec075f05c5 | ||
![]() |
200e483574 | ||
![]() |
6c11bfe93d | ||
![]() |
b4cc7f4d81 | ||
![]() |
26fc63c52c | ||
![]() |
116bf59b68 | ||
![]() |
566f9dd5cc | ||
![]() |
ec740c07fa | ||
![]() |
7506bf186b | ||
![]() |
27c36b644d | ||
![]() |
ed2b1029f6 | ||
![]() |
530a920517 | ||
![]() |
42f8e635ed | ||
![]() |
7ace67f906 | ||
![]() |
1b437778dc | ||
![]() |
43b0c65aab | ||
![]() |
3f6a319ebe | ||
![]() |
4cbd342e17 | ||
![]() |
273f04128c | ||
![]() |
9a5cef08d6 | ||
![]() |
bbecfdb718 | ||
![]() |
cbed79fbf1 | ||
![]() |
c4cbc024e6 | ||
![]() |
2db8dbd5a3 | ||
![]() |
13b995c9f2 | ||
![]() |
a0944193b6 | ||
![]() |
c7f343020a | ||
![]() |
7a48ccc4f4 | ||
![]() |
cc1218b7a3 | ||
![]() |
385e640a92 | ||
![]() |
b40a59cbef | ||
![]() |
1a45ce5ea4 | ||
![]() |
ab05048409 | ||
![]() |
47aabbe9c5 | ||
![]() |
58cd2c522e | ||
![]() |
1fe0cd9f84 | ||
![]() |
eeee1ba1a3 | ||
![]() |
3cb8880c5c | ||
![]() |
80cec86225 | ||
![]() |
cc65ed8fb0 | ||
![]() |
0c3f1f4a5d | ||
![]() |
76375941ca | ||
![]() |
d2a0415def | ||
![]() |
815c0834b2 | ||
![]() |
7e1e1e2524 | ||
![]() |
957d2793cb | ||
![]() |
6ab86b7574 | ||
![]() |
1c75b7226d | ||
![]() |
db22d8e2c9 | ||
![]() |
7838b9609c | ||
![]() |
2a1a0421b6 | ||
![]() |
125ea0457e | ||
![]() |
c1f5f0632b | ||
![]() |
8c84ab29c6 | ||
![]() |
2767af11b2 | ||
![]() |
59bbc4c182 | ||
![]() |
a755ee8dc1 | ||
![]() |
4b1e66add3 | ||
![]() |
4f6727810b | ||
![]() |
60fdfc2b48 | ||
![]() |
268d8c8b7d | ||
![]() |
49d4071928 | ||
![]() |
c99b9dbe0a | ||
![]() |
09d3a06b27 | ||
![]() |
0d49bc003f | ||
![]() |
86a5569bf7 | ||
![]() |
ad9fe7dec6 | ||
![]() |
24bf07e068 | ||
![]() |
bae234827f | ||
![]() |
81c698f45b | ||
![]() |
c9fecec575 | ||
![]() |
2f42dfdc51 | ||
![]() |
5bac68b0de | ||
![]() |
5afd500c26 | ||
![]() |
03f3e5f6ab | ||
![]() |
50d91405a9 | ||
![]() |
69eed65c9b | ||
![]() |
f1355cd0ab | ||
![]() |
cc1a924c8b | ||
![]() |
02005a3f09 | ||
![]() |
d9219a5a48 | ||
![]() |
dffbdf544c | ||
![]() |
e2dbf1a215 | ||
![]() |
66f9cb8d25 | ||
![]() |
c1396b97f0 | ||
![]() |
920a711c00 | ||
![]() |
02a872a376 | ||
![]() |
be4493710f | ||
![]() |
52c0c5e0c5 | ||
![]() |
4c639f170e | ||
![]() |
509a414151 | ||
![]() |
8f3c793a69 | ||
![]() |
a2dd9cf1b8 | ||
![]() |
42285a5879 | ||
![]() |
5d63fce6f0 | ||
![]() |
46a4c8faec | ||
![]() |
ba14481151 | ||
![]() |
bdd8da98c4 | ||
![]() |
5a4207414d | ||
![]() |
730fdd32b1 | ||
![]() |
aa7f6694fc | ||
![]() |
8fba6704df | ||
![]() |
37d02eba02 | ||
![]() |
75a87cf070 | ||
![]() |
8acdc5853d | ||
![]() |
1aa1f441b3 | ||
![]() |
0e26032ac3 | ||
![]() |
871f25c6d9 | ||
![]() |
d8199e7ba7 | ||
![]() |
52b938eabe | ||
![]() |
94fddf3d9b | ||
![]() |
02c98c1ece | ||
![]() |
6ba77667e9 | ||
![]() |
e201a5b806 | ||
![]() |
adac68c407 | ||
![]() |
d051275e54 | ||
![]() |
2afc00364a | ||
![]() |
0a1461231b | ||
![]() |
129327f40d | ||
![]() |
58819aad94 | ||
![]() |
949a2543f5 | ||
![]() |
0f9d732667 | ||
![]() |
b7df175950 | ||
![]() |
4f7ce9874f | ||
![]() |
f63cc80383 | ||
![]() |
dd0a1328e8 | ||
![]() |
27610c002c | ||
![]() |
46ec9b5229 | ||
![]() |
778559d537 | ||
![]() |
1e64b8a903 | ||
![]() |
0e000693d6 | ||
![]() |
cf9e09ea7a | ||
![]() |
eac2f729a5 | ||
![]() |
9578cd27dd | ||
![]() |
9304ffffc9 | ||
![]() |
3fd628cb05 | ||
![]() |
fe68b70bd9 | ||
![]() |
ec2863d218 | ||
![]() |
dc56e7f883 | ||
![]() |
2f1f537e00 | ||
![]() |
b7b3a3025b | ||
![]() |
edb9526538 | ||
![]() |
62117ece06 | ||
![]() |
913a2a0ac1 | ||
![]() |
92ec3d07a3 | ||
![]() |
67ee7899fd | ||
![]() |
b0ceb3fe7e | ||
![]() |
ba6b4c6854 | ||
![]() |
61e90aed60 | ||
![]() |
45b7a399f2 | ||
![]() |
673ed25598 | ||
![]() |
14fc460174 | ||
![]() |
211b4537f9 | ||
![]() |
a683e059aa | ||
![]() |
d2d2cea567 | ||
![]() |
e5bda65a35 | ||
![]() |
6aaef9df4b | ||
![]() |
7fba4d72b0 | ||
![]() |
a6dc2ed4ba | ||
![]() |
20a40eb2f6 | ||
![]() |
ec4ac9d075 | ||
![]() |
b2c14f4226 | ||
![]() |
099dfbd0b0 | ||
![]() |
a9f5736c12 | ||
![]() |
2bd4dd3ab0 | ||
![]() |
186850c256 | ||
![]() |
9922033d33 | ||
![]() |
3c3e6e4144 | ||
![]() |
0e4ac3b7f3 | ||
![]() |
c9813e0316 | ||
![]() |
577c0cfab9 | ||
![]() |
0966f9d715 | ||
![]() |
3f5df118a0 | ||
![]() |
e05c1b26f1 | ||
![]() |
725b38c697 | ||
![]() |
402a0fdf3b | ||
![]() |
078364ffa1 | ||
![]() |
f64d5ec4fc | ||
![]() |
0666174501 | ||
![]() |
12194a50e1 | ||
![]() |
82ee592699 | ||
![]() |
1b4fb2ce6e | ||
![]() |
ebea8d12d1 | ||
![]() |
f842dd77df | ||
![]() |
a6ec7a6c99 | ||
![]() |
369c72282c | ||
![]() |
6f30c1a509 | ||
![]() |
abfd1116c7 | ||
![]() |
017854955d | ||
![]() |
1405cddea1 | ||
![]() |
00dd3164c9 | ||
![]() |
d5cbc0f611 | ||
![]() |
5d2e9ccc67 | ||
![]() |
017a881494 | ||
![]() |
52994970e6 | ||
![]() |
ebae629e5c | ||
![]() |
0dea5150a0 | ||
![]() |
c99e8d270d | ||
![]() |
4d79220b0c | ||
![]() |
96fba7fbb8 | ||
![]() |
e0d610071d | ||
![]() |
ab0966c005 | ||
![]() |
145172f486 | ||
![]() |
e2c5d843df | ||
![]() |
557ea55ddf | ||
![]() |
1591a4edd2 | ||
![]() |
b30b5024a8 | ||
![]() |
8ef2bd3b8d |
4
.github/workflows/playwright.yml
vendored
4
.github/workflows/playwright.yml
vendored
@@ -23,6 +23,7 @@ env:
|
|||||||
REDIS_HOST: localhost
|
REDIS_HOST: localhost
|
||||||
APP_ENV: production
|
APP_ENV: production
|
||||||
LICENSE_KEY: dummy_license_key
|
LICENSE_KEY: dummy_license_key
|
||||||
|
BACKEND_APP_URL: http://localhost:3000
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
@@ -71,9 +72,6 @@ jobs:
|
|||||||
- name: Migrate database
|
- name: Migrate database
|
||||||
working-directory: ./packages/backend
|
working-directory: ./packages/backend
|
||||||
run: yarn db:migrate
|
run: yarn db:migrate
|
||||||
- name: Seed user
|
|
||||||
working-directory: ./packages/backend
|
|
||||||
run: yarn db:seed:user &
|
|
||||||
- name: Install certutils
|
- name: Install certutils
|
||||||
run: sudo apt install -y libnss3-tools
|
run: sudo apt install -y libnss3-tools
|
||||||
- name: Install mkcert
|
- name: Install mkcert
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
FROM node:18-alpine
|
FROM node:18-alpine
|
||||||
|
|
||||||
ENV PORT 3000
|
ENV PORT=3000
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apk --no-cache add --virtual build-dependencies python3 build-base git
|
apk --no-cache add --virtual build-dependencies python3 build-base git make g++
|
||||||
|
|
||||||
WORKDIR /automatisch
|
WORKDIR /automatisch
|
||||||
|
|
||||||
|
@@ -10,7 +10,7 @@ import process from 'process';
|
|||||||
async function fetchAdminRole() {
|
async function fetchAdminRole() {
|
||||||
const role = await Role.query()
|
const role = await Role.query()
|
||||||
.where({
|
.where({
|
||||||
key: 'admin',
|
name: 'Admin',
|
||||||
})
|
})
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.first();
|
.first();
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { knexSnakeCaseMappers } from 'objection';
|
import { knexSnakeCaseMappers } from 'objection';
|
||||||
import appConfig from './src/config/app.js';
|
import appConfig from './src/config/app.js';
|
||||||
import path from 'path';
|
import path, { join } from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
const fileExtension = 'js';
|
const fileExtension = 'js';
|
||||||
@@ -20,12 +20,12 @@ const knexConfig = {
|
|||||||
searchPath: [appConfig.postgresSchema],
|
searchPath: [appConfig.postgresSchema],
|
||||||
pool: { min: 0, max: 20 },
|
pool: { min: 0, max: 20 },
|
||||||
migrations: {
|
migrations: {
|
||||||
directory: __dirname + '/src/db/migrations',
|
directory: join(__dirname, '/src/db/migrations'),
|
||||||
extension: fileExtension,
|
extension: fileExtension,
|
||||||
loadExtensions: [`.${fileExtension}`],
|
loadExtensions: [`.${fileExtension}`],
|
||||||
},
|
},
|
||||||
seeds: {
|
seeds: {
|
||||||
directory: __dirname + '/src/db/seeds',
|
directory: join(__dirname, '/src/db/seeds'),
|
||||||
},
|
},
|
||||||
...(appConfig.isTest ? knexSnakeCaseMappers() : {}),
|
...(appConfig.isTest ? knexSnakeCaseMappers() : {}),
|
||||||
};
|
};
|
||||||
|
@@ -5,12 +5,13 @@
|
|||||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nodemon --watch 'src/**/*.js' --exec 'node' src/server.js",
|
"dev": "nodemon --exec node src/server.js",
|
||||||
"worker": "nodemon --watch 'src/**/*.js' --exec 'node' src/worker.js",
|
"worker": "nodemon --exec node src/worker.js",
|
||||||
"start": "node src/server.js",
|
"start": "node src/server.js",
|
||||||
"start:worker": "node src/worker.js",
|
"start:worker": "node src/worker.js",
|
||||||
"pretest": "APP_ENV=test node ./test/setup/prepare-test-env.js",
|
"pretest": "APP_ENV=test node ./test/setup/prepare-test-env.js",
|
||||||
"test": "APP_ENV=test vitest run",
|
"test": "APP_ENV=test vitest run",
|
||||||
|
"test:watch": "APP_ENV=test vitest watch",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"db:create": "node ./bin/database/create.js",
|
"db:create": "node ./bin/database/create.js",
|
||||||
"db:seed:user": "node ./bin/database/seed-user.js",
|
"db:seed:user": "node ./bin/database/seed-user.js",
|
||||||
@@ -38,7 +39,7 @@
|
|||||||
"debug": "~2.6.9",
|
"debug": "~2.6.9",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
"express": "~4.18.2",
|
"express": "~4.18.2",
|
||||||
"express-async-handler": "^1.2.0",
|
"express-async-errors": "^3.1.1",
|
||||||
"express-basic-auth": "^1.2.1",
|
"express-basic-auth": "^1.2.1",
|
||||||
"express-graphql": "^0.12.0",
|
"express-graphql": "^0.12.0",
|
||||||
"fast-xml-parser": "^4.0.11",
|
"fast-xml-parser": "^4.0.11",
|
||||||
@@ -49,6 +50,7 @@
|
|||||||
"http-errors": "~1.6.3",
|
"http-errors": "~1.6.3",
|
||||||
"http-proxy-agent": "^7.0.0",
|
"http-proxy-agent": "^7.0.0",
|
||||||
"https-proxy-agent": "^7.0.1",
|
"https-proxy-agent": "^7.0.1",
|
||||||
|
"isolated-vm": "^5.0.1",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"knex": "^2.4.0",
|
"knex": "^2.4.0",
|
||||||
"libphonenumber-js": "^1.10.48",
|
"libphonenumber-js": "^1.10.48",
|
||||||
@@ -103,5 +105,11 @@
|
|||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
},
|
||||||
|
"nodemonConfig": {
|
||||||
|
"watch": [
|
||||||
|
"src/"
|
||||||
|
],
|
||||||
|
"ext": "js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import createError from 'http-errors';
|
import createError from 'http-errors';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
|
import 'express-async-errors';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
|
|
||||||
import appConfig from './config/app.js';
|
import appConfig from './config/app.js';
|
||||||
|
@@ -7,7 +7,7 @@ export default defineAction({
|
|||||||
'Creates an attachment of a specified object by given parent ID.',
|
'Creates an attachment of a specified object by given parent ID.',
|
||||||
arguments: [
|
arguments: [
|
||||||
{
|
{
|
||||||
label: 'Templete Data',
|
label: 'Template Data',
|
||||||
key: 'templateData',
|
key: 'templateData',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
|
@@ -0,0 +1,72 @@
|
|||||||
|
import defineAction from '../../../../helpers/define-action.js';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Create folder',
|
||||||
|
key: 'createFolder',
|
||||||
|
description: 'Creates a new folder.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Workspace',
|
||||||
|
key: 'workspaceId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listWorkspaces',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Space',
|
||||||
|
key: 'spaceId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
dependsOn: ['parameters.workspaceId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listSpaces',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.workspaceId',
|
||||||
|
value: '{parameters.workspaceId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Folder Name',
|
||||||
|
key: 'folderName',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const { spaceId, folderName } = $.step.parameters;
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
name: folderName,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await $.http.post(`/v2/space/${spaceId}/folder`, body);
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
135
packages/backend/src/apps/clickup/actions/create-list/index.js
Normal file
135
packages/backend/src/apps/clickup/actions/create-list/index.js
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import defineAction from '../../../../helpers/define-action.js';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Create list',
|
||||||
|
key: 'createList',
|
||||||
|
description: 'Creates a new list.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Workspace',
|
||||||
|
key: 'workspaceId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listWorkspaces',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Space',
|
||||||
|
key: 'spaceId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
dependsOn: ['parameters.workspaceId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listSpaces',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.workspaceId',
|
||||||
|
value: '{parameters.workspaceId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Folder',
|
||||||
|
key: 'folderId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
dependsOn: ['parameters.spaceId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listFolders',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.spaceId',
|
||||||
|
value: '{parameters.spaceId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'List Name',
|
||||||
|
key: 'listName',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'List Info',
|
||||||
|
key: 'listInfo',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Priority',
|
||||||
|
key: 'priority',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
options: [
|
||||||
|
{ label: 'Urgent', value: 1 },
|
||||||
|
{ label: 'High', value: 2 },
|
||||||
|
{ label: 'Normal', value: 3 },
|
||||||
|
{ label: 'Low', value: 4 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Due Date',
|
||||||
|
key: 'dueDate',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description: 'format: integer <int64>',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const { folderId, listName, listInfo, priority, dueDate } =
|
||||||
|
$.step.parameters;
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
name: listName,
|
||||||
|
content: listInfo,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (priority) {
|
||||||
|
body.priority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dueDate) {
|
||||||
|
body.due_date = dueDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await $.http.post(`/v2/folder/${folderId}/list`, body);
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
294
packages/backend/src/apps/clickup/actions/create-task/index.js
Normal file
294
packages/backend/src/apps/clickup/actions/create-task/index.js
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
import defineAction from '../../../../helpers/define-action.js';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Create task',
|
||||||
|
key: 'createTask',
|
||||||
|
description: 'Creates a new task.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Workspace',
|
||||||
|
key: 'workspaceId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listWorkspaces',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Space',
|
||||||
|
key: 'spaceId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
dependsOn: ['parameters.workspaceId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listSpaces',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.workspaceId',
|
||||||
|
value: '{parameters.workspaceId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Folder',
|
||||||
|
key: 'folderId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
dependsOn: ['parameters.spaceId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listFolders',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.spaceId',
|
||||||
|
value: '{parameters.spaceId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'List',
|
||||||
|
key: 'listId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
dependsOn: ['parameters.folderId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listLists',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.folderId',
|
||||||
|
value: '{parameters.folderId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Task Name',
|
||||||
|
key: 'taskName',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Task Description',
|
||||||
|
key: 'taskDescription',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Markdown Content',
|
||||||
|
key: 'markdownContent',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
options: [
|
||||||
|
{ label: 'False', value: 'false' },
|
||||||
|
{ label: 'True', value: 'true' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Assignees',
|
||||||
|
key: 'assigneeIds',
|
||||||
|
type: 'dynamic',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'Assignee',
|
||||||
|
key: 'assigneeId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
dependsOn: ['parameters.listId'],
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listAssignees',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.listId',
|
||||||
|
value: '{parameters.listId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Task Status',
|
||||||
|
key: 'taskStatus',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
dependsOn: ['parameters.listId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listStatuses',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.listId',
|
||||||
|
value: '{parameters.listId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Tags',
|
||||||
|
key: 'tagIds',
|
||||||
|
type: 'dynamic',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'tag',
|
||||||
|
key: 'tagId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listTags',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Priority',
|
||||||
|
key: 'priority',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
options: [
|
||||||
|
{ label: 'Urgent', value: 1 },
|
||||||
|
{ label: 'High', value: 2 },
|
||||||
|
{ label: 'Normal', value: 3 },
|
||||||
|
{ label: 'Low', value: 4 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Due Date',
|
||||||
|
key: 'dueDate',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description: 'format: integer <int64>',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Start Date',
|
||||||
|
key: 'startDate',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description: 'format: integer <int64>',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const {
|
||||||
|
listId,
|
||||||
|
taskName,
|
||||||
|
taskDescription,
|
||||||
|
markdownContent,
|
||||||
|
assigneeIds,
|
||||||
|
taskStatus,
|
||||||
|
tagIds,
|
||||||
|
priority,
|
||||||
|
dueDate,
|
||||||
|
startDate,
|
||||||
|
} = $.step.parameters;
|
||||||
|
|
||||||
|
const tags = tagIds.map((tag) => tag.tagId);
|
||||||
|
const assignees = assigneeIds.map((assignee) =>
|
||||||
|
Number(assignee.assigneeId)
|
||||||
|
);
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
name: taskName,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (assignees.length) {
|
||||||
|
body.assignees = assignees;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taskStatus) {
|
||||||
|
body.status = taskStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tags.length) {
|
||||||
|
body.tags = tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priority) {
|
||||||
|
body.priority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dueDate) {
|
||||||
|
body.due_date = dueDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startDate) {
|
||||||
|
body.start_date = startDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (markdownContent) {
|
||||||
|
body.markdown_description = taskDescription;
|
||||||
|
} else {
|
||||||
|
body.description = taskDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await $.http.post(`/v2/list/${listId}/task`, body);
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
@@ -0,0 +1,82 @@
|
|||||||
|
import defineAction from '../../../../helpers/define-action.js';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Find task by id',
|
||||||
|
key: 'findTaskById',
|
||||||
|
description: 'Finds a task using id.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Task ID',
|
||||||
|
key: 'taskId',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Use Custom ID',
|
||||||
|
key: 'useCustomId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'True',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'False',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
additionalFields: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicFields',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listFieldsWhenUsingCustomId',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.useCustomId',
|
||||||
|
value: '{parameters.useCustomId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Include Subtasks?',
|
||||||
|
key: 'includeSubtasks',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'True',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'False',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const { taskId, useCustomId, includeSubtasks } = $.step.parameters;
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
custom_task_ids: useCustomId || false,
|
||||||
|
include_subtasks: includeSubtasks,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await $.http.get(`/v2/task/${taskId}`, { params });
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
6
packages/backend/src/apps/clickup/actions/index.js
Normal file
6
packages/backend/src/apps/clickup/actions/index.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import createFolder from './create-folder/index.js';
|
||||||
|
import createList from './create-list/index.js';
|
||||||
|
import createTask from './create-task/index.js';
|
||||||
|
import findTaskById from './find-task-by-id/index.js';
|
||||||
|
|
||||||
|
export default [createFolder, createList, createTask, findTaskById];
|
27
packages/backend/src/apps/clickup/assets/favicon.svg
Normal file
27
packages/backend/src/apps/clickup/assets/favicon.svg
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<svg width="185" height="185" viewBox="0 0 185 185" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g filter="url(#filter0_d)">
|
||||||
|
<rect x="30" y="20" width="125" height="125" rx="62.5" fill="white"/>
|
||||||
|
<rect x="30" y="20" width="125" height="125" rx="62.5" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M55.8789 105.714L69.3974 95.3593C76.5762 104.732 84.1998 109.051 92.6948 109.051C101.143 109.051 108.557 104.781 115.414 95.4832L129.119 105.59C119.232 118.996 106.932 126.079 92.6948 126.079C78.5049 126.079 66.0907 119.046 55.8789 105.714Z" fill="url(#paint0_linear)"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M92.6491 60.7078L68.5883 81.4406L57.4727 68.5407L92.6969 38.1885L127.647 68.5644L116.477 81.417L92.6491 60.7078Z" fill="url(#paint1_linear)"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_d" x="0" y="0" width="185" height="185" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
|
||||||
|
<feOffset dy="10"/>
|
||||||
|
<feGaussianBlur stdDeviation="15"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0.0627451 0 0 0 0 0.117647 0 0 0 0 0.211765 0 0 0 0.1 0"/>
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
<linearGradient id="paint0_linear" x1="55.8789" y1="116.251" x2="129.119" y2="116.251" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#8930FD"/>
|
||||||
|
<stop offset="1" stop-color="#49CCF9"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint1_linear" x1="57.4727" y1="67.6025" x2="127.647" y2="67.6025" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#FF02F0"/>
|
||||||
|
<stop offset="1" stop-color="#FFC800"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
21
packages/backend/src/apps/clickup/auth/generate-auth-url.js
Normal file
21
packages/backend/src/apps/clickup/auth/generate-auth-url.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { URLSearchParams } from 'url';
|
||||||
|
|
||||||
|
export default async function generateAuthUrl($) {
|
||||||
|
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||||
|
(field) => field.key == 'oAuthRedirectUrl'
|
||||||
|
);
|
||||||
|
const redirectUri = oauthRedirectUrlField.value;
|
||||||
|
const state = Math.random().toString();
|
||||||
|
const searchParams = new URLSearchParams({
|
||||||
|
client_id: $.auth.data.clientId,
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
state,
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = `https://app.clickup.com/api?${searchParams.toString()}`;
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
url,
|
||||||
|
originalState: state,
|
||||||
|
});
|
||||||
|
}
|
46
packages/backend/src/apps/clickup/auth/index.js
Normal file
46
packages/backend/src/apps/clickup/auth/index.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import generateAuthUrl from './generate-auth-url.js';
|
||||||
|
import verifyCredentials from './verify-credentials.js';
|
||||||
|
import isStillVerified from './is-still-verified.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'oAuthRedirectUrl',
|
||||||
|
label: 'OAuth Redirect URL',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: true,
|
||||||
|
value: '{WEB_APP_URL}/app/clickup/connections/add',
|
||||||
|
placeholder: null,
|
||||||
|
description:
|
||||||
|
'When asked to input a redirect URL in ClickUp, enter the URL above.',
|
||||||
|
clickToCopy: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'clientId',
|
||||||
|
label: 'Client ID',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'clientSecret',
|
||||||
|
label: 'Client Secret',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
generateAuthUrl,
|
||||||
|
verifyCredentials,
|
||||||
|
isStillVerified,
|
||||||
|
};
|
@@ -0,0 +1,8 @@
|
|||||||
|
import getCurrentUser from '../common/get-current-user.js';
|
||||||
|
|
||||||
|
const isStillVerified = async ($) => {
|
||||||
|
const currentUser = await getCurrentUser($);
|
||||||
|
return !!currentUser.id;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isStillVerified;
|
31
packages/backend/src/apps/clickup/auth/verify-credentials.js
Normal file
31
packages/backend/src/apps/clickup/auth/verify-credentials.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import getCurrentUser from '../common/get-current-user.js';
|
||||||
|
|
||||||
|
const verifyCredentials = async ($) => {
|
||||||
|
if ($.auth.data.originalState !== $.auth.data.state) {
|
||||||
|
throw new Error(`The 'state' parameter does not match.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await $.http.post('/v2/oauth/token', {
|
||||||
|
client_id: $.auth.data.clientId,
|
||||||
|
client_secret: $.auth.data.clientSecret,
|
||||||
|
code: $.auth.data.code,
|
||||||
|
});
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
accessToken: data.access_token,
|
||||||
|
tokenType: data.token_type,
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentUser = await getCurrentUser($);
|
||||||
|
const screenName = [currentUser.username, currentUser.email]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' @ ');
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
clientId: $.auth.data.clientId,
|
||||||
|
clientSecret: $.auth.data.clientSecret,
|
||||||
|
screenName,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default verifyCredentials;
|
@@ -0,0 +1,9 @@
|
|||||||
|
const addAuthHeader = ($, requestConfig) => {
|
||||||
|
if ($.auth.data?.accessToken) {
|
||||||
|
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default addAuthHeader;
|
@@ -0,0 +1,6 @@
|
|||||||
|
const getCurrentUser = async ($) => {
|
||||||
|
const { data } = await $.http.get('/v2/user');
|
||||||
|
return data.user;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getCurrentUser;
|
19
packages/backend/src/apps/clickup/dynamic-data/index.js
Normal file
19
packages/backend/src/apps/clickup/dynamic-data/index.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import listAssignees from './list-assignees/index.js';
|
||||||
|
import listFolders from './list-folders/index.js';
|
||||||
|
import listLists from './list-lists/index.js';
|
||||||
|
import listSpaces from './list-spaces/index.js';
|
||||||
|
import listStatuses from './list-statuses/index.js';
|
||||||
|
import listTags from './list-tags/index.js';
|
||||||
|
import listTasks from './list-tasks/index.js';
|
||||||
|
import listWorkspaces from './list-workspaces/index.js';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
listAssignees,
|
||||||
|
listFolders,
|
||||||
|
listLists,
|
||||||
|
listSpaces,
|
||||||
|
listStatuses,
|
||||||
|
listTags,
|
||||||
|
listTasks,
|
||||||
|
listWorkspaces,
|
||||||
|
];
|
@@ -0,0 +1,28 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List assignees',
|
||||||
|
key: 'listAssignees',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const assignees = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
const listId = $.step.parameters.listId;
|
||||||
|
|
||||||
|
if (!listId) {
|
||||||
|
return assignees;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await $.http.get(`/v2/list/${listId}/member`);
|
||||||
|
|
||||||
|
if (data.members) {
|
||||||
|
for (const member of data.members) {
|
||||||
|
assignees.data.push({
|
||||||
|
value: member.id,
|
||||||
|
name: member.username,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return assignees;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,28 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List folders',
|
||||||
|
key: 'listFolders',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const folders = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
const spaceId = $.step.parameters.spaceId;
|
||||||
|
|
||||||
|
if (!spaceId) {
|
||||||
|
return folders;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await $.http.get(`/v2/space/${spaceId}/folder`);
|
||||||
|
|
||||||
|
if (data.folders) {
|
||||||
|
for (const folder of data.folders) {
|
||||||
|
folders.data.push({
|
||||||
|
value: folder.id,
|
||||||
|
name: folder.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return folders;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,28 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List lists',
|
||||||
|
key: 'listLists',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const lists = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
const folderId = $.step.parameters.folderId;
|
||||||
|
|
||||||
|
if (!folderId) {
|
||||||
|
return lists;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await $.http.get(`/v2/folder/${folderId}/list`);
|
||||||
|
|
||||||
|
if (data.lists) {
|
||||||
|
for (const list of data.lists) {
|
||||||
|
lists.data.push({
|
||||||
|
value: list.id,
|
||||||
|
name: list.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lists;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,28 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List spaces',
|
||||||
|
key: 'listSpaces',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const spaces = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
const workspaceId = $.step.parameters.workspaceId;
|
||||||
|
|
||||||
|
if (!workspaceId) {
|
||||||
|
return spaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await $.http.get(`/v2/team/${workspaceId}/space`);
|
||||||
|
|
||||||
|
if (data.spaces) {
|
||||||
|
for (const space of data.spaces) {
|
||||||
|
spaces.data.push({
|
||||||
|
value: space.id,
|
||||||
|
name: space.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return spaces;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,28 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List statuses',
|
||||||
|
key: 'listStatuses',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const statuses = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
const listId = $.step.parameters.listId;
|
||||||
|
|
||||||
|
if (!listId) {
|
||||||
|
return statuses;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await $.http.get(`/v2/list/${listId}`);
|
||||||
|
|
||||||
|
if (data.statuses) {
|
||||||
|
for (const status of data.statuses) {
|
||||||
|
statuses.data.push({
|
||||||
|
value: status.status,
|
||||||
|
name: status.status,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return statuses;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,28 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List tags',
|
||||||
|
key: 'listTags',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const tags = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
const spaceId = $.step.parameters.spaceId;
|
||||||
|
|
||||||
|
if (!spaceId) {
|
||||||
|
return spaceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await $.http.get(`v2/space/${spaceId}/tag`);
|
||||||
|
|
||||||
|
if (data.tags) {
|
||||||
|
for (const tag of data.tags) {
|
||||||
|
tags.data.push({
|
||||||
|
value: tag.name,
|
||||||
|
name: tag.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tags;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,41 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List tasks',
|
||||||
|
key: 'listTasks',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const tasks = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
const listId = $.step.parameters.listId;
|
||||||
|
let next = false;
|
||||||
|
|
||||||
|
if (!listId) {
|
||||||
|
return tasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
order_by: 'created',
|
||||||
|
reverse: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
do {
|
||||||
|
const { data } = await $.http.get(`/v2/list/${listId}/task`, { params });
|
||||||
|
if (data.last_page) {
|
||||||
|
next = false;
|
||||||
|
} else {
|
||||||
|
next = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.tasks) {
|
||||||
|
for (const task of data.tasks) {
|
||||||
|
tasks.data.push({
|
||||||
|
value: task.id,
|
||||||
|
name: task.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (next);
|
||||||
|
|
||||||
|
return tasks;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,23 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List workspaces',
|
||||||
|
key: 'listWorkspaces',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const workspaces = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await $.http.get('/v2/team');
|
||||||
|
|
||||||
|
if (data.teams) {
|
||||||
|
for (const workspace of data.teams) {
|
||||||
|
workspaces.data.push({
|
||||||
|
value: workspace.id,
|
||||||
|
name: workspace.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return workspaces;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,3 @@
|
|||||||
|
import useCustomId from './use-custom-id/index.js';
|
||||||
|
|
||||||
|
export default [useCustomId];
|
@@ -0,0 +1,29 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List workspaces when using custom id',
|
||||||
|
key: 'listFieldsWhenUsingCustomId',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
if ($.step.parameters.useCustomId) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'Workspace',
|
||||||
|
key: 'workspaceId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listWorkspaces',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
24
packages/backend/src/apps/clickup/index.js
Normal file
24
packages/backend/src/apps/clickup/index.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import defineApp from '../../helpers/define-app.js';
|
||||||
|
import addAuthHeader from './common/add-auth-header.js';
|
||||||
|
import auth from './auth/index.js';
|
||||||
|
import triggers from './triggers/index.js';
|
||||||
|
import dynamicData from './dynamic-data/index.js';
|
||||||
|
import actions from './actions/index.js';
|
||||||
|
import dynamicFields from './dynamic-fields/index.js';
|
||||||
|
|
||||||
|
export default defineApp({
|
||||||
|
name: 'ClickUp',
|
||||||
|
key: 'clickup',
|
||||||
|
baseUrl: 'https://clickup.com',
|
||||||
|
apiBaseUrl: 'https://api.clickup.com/api',
|
||||||
|
iconUrl: '{BASE_URL}/apps/clickup/assets/favicon.svg',
|
||||||
|
authDocUrl: 'https://automatisch.io/docs/apps/clickup/connection',
|
||||||
|
primaryColor: 'FD71AF',
|
||||||
|
supportsConnections: true,
|
||||||
|
beforeRequest: [addAuthHeader],
|
||||||
|
auth,
|
||||||
|
triggers,
|
||||||
|
dynamicData,
|
||||||
|
actions,
|
||||||
|
dynamicFields,
|
||||||
|
});
|
6
packages/backend/src/apps/clickup/triggers/index.js
Normal file
6
packages/backend/src/apps/clickup/triggers/index.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import newFolders from './new-folders/index.js';
|
||||||
|
import newLists from './new-lists/index.js';
|
||||||
|
import newTasks from './new-tasks/index.js';
|
||||||
|
import updatedTask from './updated-task/index.js';
|
||||||
|
|
||||||
|
export default [newFolders, newLists, newTasks, updatedTask];
|
105
packages/backend/src/apps/clickup/triggers/new-folders/index.js
Normal file
105
packages/backend/src/apps/clickup/triggers/new-folders/index.js
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import Crypto from 'crypto';
|
||||||
|
import defineTrigger from '../../../../helpers/define-trigger.js';
|
||||||
|
|
||||||
|
export default defineTrigger({
|
||||||
|
name: 'New folders',
|
||||||
|
key: 'newFolder',
|
||||||
|
type: 'webhook',
|
||||||
|
description: 'Triggers when a new folder is created.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Workspace',
|
||||||
|
key: 'workspaceId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listWorkspaces',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Space',
|
||||||
|
key: 'spaceId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
dependsOn: ['parameters.workspaceId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listSpaces',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.workspaceId',
|
||||||
|
value: '{parameters.workspaceId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const dataItem = {
|
||||||
|
raw: $.request.body,
|
||||||
|
meta: {
|
||||||
|
internalId: $.request.body.folder_id,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
$.pushTriggerItem(dataItem);
|
||||||
|
},
|
||||||
|
|
||||||
|
async testRun($) {
|
||||||
|
const sampleEventData = {
|
||||||
|
event: 'folderCreated',
|
||||||
|
folder_id: '90180382912',
|
||||||
|
webhook_id: Crypto.randomUUID(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataItem = {
|
||||||
|
raw: sampleEventData,
|
||||||
|
meta: {
|
||||||
|
internalId: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
$.pushTriggerItem(dataItem);
|
||||||
|
},
|
||||||
|
|
||||||
|
async registerHook($) {
|
||||||
|
const { workspaceId, spaceId } = $.step.parameters;
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
name: $.flow.id,
|
||||||
|
endpoint: $.webhookUrl,
|
||||||
|
events: ['folderCreated'],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (spaceId) {
|
||||||
|
payload.space_id = spaceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await $.http.post(
|
||||||
|
`/v2/team/${workspaceId}/webhook`,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
|
||||||
|
await $.flow.setRemoteWebhookId(data.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
async unregisterHook($) {
|
||||||
|
await $.http.delete(`/v2/webhook/${$.flow.remoteWebhookId}`);
|
||||||
|
},
|
||||||
|
});
|
129
packages/backend/src/apps/clickup/triggers/new-lists/index.js
Normal file
129
packages/backend/src/apps/clickup/triggers/new-lists/index.js
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import Crypto from 'crypto';
|
||||||
|
import defineTrigger from '../../../../helpers/define-trigger.js';
|
||||||
|
|
||||||
|
export default defineTrigger({
|
||||||
|
name: 'New lists',
|
||||||
|
key: 'newLists',
|
||||||
|
type: 'webhook',
|
||||||
|
description: 'Triggers when a new list is created.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Workspace',
|
||||||
|
key: 'workspaceId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listWorkspaces',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Space',
|
||||||
|
key: 'spaceId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
dependsOn: ['parameters.workspaceId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listSpaces',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.workspaceId',
|
||||||
|
value: '{parameters.workspaceId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Folder',
|
||||||
|
key: 'folderId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
dependsOn: ['parameters.spaceId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listFolders',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.spaceId',
|
||||||
|
value: '{parameters.spaceId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const dataItem = {
|
||||||
|
raw: $.request.body,
|
||||||
|
meta: {
|
||||||
|
internalId: $.request.body.list_id,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
$.pushTriggerItem(dataItem);
|
||||||
|
},
|
||||||
|
|
||||||
|
async testRun($) {
|
||||||
|
const sampleEventData = {
|
||||||
|
event: 'listCreated',
|
||||||
|
list_id: '901800588812',
|
||||||
|
webhook_id: Crypto.randomUUID(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataItem = {
|
||||||
|
raw: sampleEventData,
|
||||||
|
meta: {
|
||||||
|
internalId: sampleEventData.webhook_id,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
$.pushTriggerItem(dataItem);
|
||||||
|
},
|
||||||
|
|
||||||
|
async registerHook($) {
|
||||||
|
const { workspaceId, spaceId, folderId } = $.step.parameters;
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
name: $.flow.id,
|
||||||
|
endpoint: $.webhookUrl,
|
||||||
|
events: ['listCreated'],
|
||||||
|
space_id: spaceId,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (folderId) {
|
||||||
|
payload.folder_id = folderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await $.http.post(
|
||||||
|
`/v2/team/${workspaceId}/webhook`,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
|
||||||
|
await $.flow.setRemoteWebhookId(data.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
async unregisterHook($) {
|
||||||
|
await $.http.delete(`/v2/webhook/${$.flow.remoteWebhookId}`);
|
||||||
|
},
|
||||||
|
});
|
186
packages/backend/src/apps/clickup/triggers/new-tasks/index.js
Normal file
186
packages/backend/src/apps/clickup/triggers/new-tasks/index.js
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
import Crypto from 'crypto';
|
||||||
|
import defineTrigger from '../../../../helpers/define-trigger.js';
|
||||||
|
|
||||||
|
export default defineTrigger({
|
||||||
|
name: 'New tasks',
|
||||||
|
key: 'newTasks',
|
||||||
|
type: 'webhook',
|
||||||
|
description: 'Triggers when a new task is created.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Workspace',
|
||||||
|
key: 'workspaceId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listWorkspaces',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Space',
|
||||||
|
key: 'spaceId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
dependsOn: ['parameters.workspaceId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listSpaces',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.workspaceId',
|
||||||
|
value: '{parameters.workspaceId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Folder',
|
||||||
|
key: 'folderId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
dependsOn: ['parameters.spaceId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listFolders',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.spaceId',
|
||||||
|
value: '{parameters.spaceId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'List',
|
||||||
|
key: 'listId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
dependsOn: ['parameters.folderId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listLists',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.folderId',
|
||||||
|
value: '{parameters.folderId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Task',
|
||||||
|
key: 'taskId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
dependsOn: ['parameters.listId'],
|
||||||
|
description:
|
||||||
|
'Choose an optional task to determine when this flow should be activated. In this scenario, only subtasks will initiate this flow.',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listTasks',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.listId',
|
||||||
|
value: '{parameters.listId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const dataItem = {
|
||||||
|
raw: $.request.body,
|
||||||
|
meta: {
|
||||||
|
internalId: $.request.body.task_id,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
$.pushTriggerItem(dataItem);
|
||||||
|
},
|
||||||
|
|
||||||
|
async testRun($) {
|
||||||
|
const sampleEventData = {
|
||||||
|
event: 'taskCreated',
|
||||||
|
task_id: '86enn7pg7',
|
||||||
|
webhook_id: Crypto.randomUUID(),
|
||||||
|
history_items: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataItem = {
|
||||||
|
raw: sampleEventData,
|
||||||
|
meta: {
|
||||||
|
internalId: sampleEventData.webhook_id,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
$.pushTriggerItem(dataItem);
|
||||||
|
},
|
||||||
|
|
||||||
|
async registerHook($) {
|
||||||
|
const { workspaceId, spaceId, folderId, listId, taskId } =
|
||||||
|
$.step.parameters;
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
name: $.flow.id,
|
||||||
|
endpoint: $.webhookUrl,
|
||||||
|
events: ['taskCreated'],
|
||||||
|
space_id: spaceId,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (folderId) {
|
||||||
|
payload.folder_id = folderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listId) {
|
||||||
|
payload.list_id = listId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taskId) {
|
||||||
|
payload.task_id = taskId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await $.http.post(
|
||||||
|
`/v2/team/${workspaceId}/webhook`,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
|
||||||
|
await $.flow.setRemoteWebhookId(data.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
async unregisterHook($) {
|
||||||
|
await $.http.delete(`/v2/webhook/${$.flow.remoteWebhookId}`);
|
||||||
|
},
|
||||||
|
});
|
172
packages/backend/src/apps/clickup/triggers/updated-task/index.js
Normal file
172
packages/backend/src/apps/clickup/triggers/updated-task/index.js
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
import Crypto from 'crypto';
|
||||||
|
import defineTrigger from '../../../../helpers/define-trigger.js';
|
||||||
|
|
||||||
|
export default defineTrigger({
|
||||||
|
name: 'Updated task',
|
||||||
|
key: 'updatedTask',
|
||||||
|
type: 'webhook',
|
||||||
|
description: 'Triggers when a task is updated.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Workspace',
|
||||||
|
key: 'workspaceId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listWorkspaces',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Space',
|
||||||
|
key: 'spaceId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
dependsOn: ['parameters.workspaceId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listSpaces',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.workspaceId',
|
||||||
|
value: '{parameters.workspaceId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Folder',
|
||||||
|
key: 'folderId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
dependsOn: ['parameters.spaceId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listFolders',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.spaceId',
|
||||||
|
value: '{parameters.spaceId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'List',
|
||||||
|
key: 'listId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
dependsOn: ['parameters.folderId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listLists',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.folderId',
|
||||||
|
value: '{parameters.folderId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'What Changed?',
|
||||||
|
key: 'whatChanged',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
variables: true,
|
||||||
|
options: [
|
||||||
|
{ label: 'Status', value: 'taskStatusUpdated' },
|
||||||
|
{ label: 'Assignee Added', value: 'taskAssigneeUpdated' },
|
||||||
|
{ label: 'Priority', value: 'taskPriorityUpdated' },
|
||||||
|
{ label: 'Tag Added', value: 'taskTagUpdated' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const dataItem = {
|
||||||
|
raw: $.request.body,
|
||||||
|
meta: {
|
||||||
|
internalId: Crypto.randomUUID(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
$.pushTriggerItem(dataItem);
|
||||||
|
},
|
||||||
|
|
||||||
|
async testRun($) {
|
||||||
|
const sampleEventData = {
|
||||||
|
event: 'taskUpdated',
|
||||||
|
task_id: '86enn7pg7',
|
||||||
|
webhook_id: Crypto.randomUUID(),
|
||||||
|
history_items: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataItem = {
|
||||||
|
raw: sampleEventData,
|
||||||
|
meta: {
|
||||||
|
internalId: sampleEventData.webhook_id,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
$.pushTriggerItem(dataItem);
|
||||||
|
},
|
||||||
|
|
||||||
|
async registerHook($) {
|
||||||
|
const { workspaceId, spaceId, folderId, listId, whatChanged } =
|
||||||
|
$.step.parameters;
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
name: $.flow.id,
|
||||||
|
endpoint: $.webhookUrl,
|
||||||
|
space_id: spaceId,
|
||||||
|
};
|
||||||
|
|
||||||
|
payload.events = [whatChanged || 'taskUpdated'];
|
||||||
|
|
||||||
|
if (folderId) {
|
||||||
|
payload.folder_id = folderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listId) {
|
||||||
|
payload.list_id = listId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await $.http.post(
|
||||||
|
`/v2/team/${workspaceId}/webhook`,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
|
||||||
|
await $.flow.setRemoteWebhookId(data.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
async unregisterHook($) {
|
||||||
|
await $.http.delete(`/v2/webhook/${$.flow.remoteWebhookId}`);
|
||||||
|
},
|
||||||
|
});
|
3
packages/backend/src/apps/code/actions/index.js
Normal file
3
packages/backend/src/apps/code/actions/index.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import runJavascript from './run-javascript/index.js';
|
||||||
|
|
||||||
|
export default [runJavascript];
|
@@ -0,0 +1,84 @@
|
|||||||
|
import defineAction from '../../../../helpers/define-action.js';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Run Javascript',
|
||||||
|
key: 'runJavascript',
|
||||||
|
description:
|
||||||
|
'Run browser Javascript code. You can not use NodeJS specific features and npm packages.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Inputs',
|
||||||
|
key: 'inputs',
|
||||||
|
type: 'dynamic',
|
||||||
|
required: false,
|
||||||
|
description:
|
||||||
|
'To be able to use data from previous steps, you need to expose them as input entries. You can access these input values in your code by using the `inputs` argument.',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
key: '',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'Key',
|
||||||
|
key: 'key',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Value',
|
||||||
|
key: 'value',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
variables: true,
|
||||||
|
valueType: 'parse',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Code Snippet',
|
||||||
|
key: 'codeSnippet',
|
||||||
|
type: 'code',
|
||||||
|
required: true,
|
||||||
|
variables: false,
|
||||||
|
value:
|
||||||
|
'const code = async (inputs) => { \n // E.g. if you have an input called username,\n // you can access its value by calling inputs.username\n // Return value will be used as output of this step.\n\n return true;\n};',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const { inputs = [], codeSnippet } = $.step.parameters;
|
||||||
|
|
||||||
|
const objectifiedInput = {};
|
||||||
|
for (const input of inputs) {
|
||||||
|
if (input.key) {
|
||||||
|
objectifiedInput[input.key] = input.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ivm = (await import('isolated-vm')).default;
|
||||||
|
const isolate = new ivm.Isolate({ memoryLimit: 128 });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const context = await isolate.createContext();
|
||||||
|
await context.global.set(
|
||||||
|
'inputs',
|
||||||
|
new ivm.ExternalCopy(objectifiedInput).copyInto()
|
||||||
|
);
|
||||||
|
|
||||||
|
const compiledCodeSnippet = await isolate.compileScript(
|
||||||
|
`${codeSnippet}; code(inputs);`
|
||||||
|
);
|
||||||
|
const codeFunction = await compiledCodeSnippet.run(context, {
|
||||||
|
reference: true,
|
||||||
|
promise: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
$.setActionItem({ raw: { output: await codeFunction.copy() } });
|
||||||
|
} finally {
|
||||||
|
isolate.dispose();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
5
packages/backend/src/apps/code/assets/favicon.svg
Normal file
5
packages/backend/src/apps/code/assets/favicon.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="800px" height="800px" viewBox="0 0 512 512">
|
||||||
|
<polyline points="160 368 32 256 160 144" style="fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/>
|
||||||
|
<polyline points="352 368 480 256 352 144" style="fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/>
|
||||||
|
<line x1="304" y1="96" x2="208" y2="416" style="fill:none;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 519 B |
14
packages/backend/src/apps/code/index.js
Normal file
14
packages/backend/src/apps/code/index.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import defineApp from '../../helpers/define-app.js';
|
||||||
|
import actions from './actions/index.js';
|
||||||
|
|
||||||
|
export default defineApp({
|
||||||
|
name: 'Code',
|
||||||
|
key: 'code',
|
||||||
|
baseUrl: '',
|
||||||
|
apiBaseUrl: '',
|
||||||
|
iconUrl: '{BASE_URL}/apps/code/assets/favicon.svg',
|
||||||
|
authDocUrl: '{DOCS_URL}/apps/code/connection',
|
||||||
|
primaryColor: '000000',
|
||||||
|
supportsConnections: false,
|
||||||
|
actions,
|
||||||
|
});
|
@@ -0,0 +1,64 @@
|
|||||||
|
import { createHmac } from 'node:crypto';
|
||||||
|
import defineAction from '../../../../helpers/define-action.js';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Create HMAC',
|
||||||
|
key: 'createHmac',
|
||||||
|
description: 'Create a Hash-based Message Authentication Code (HMAC) using the specified algorithm, secret key, and message.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Algorithm',
|
||||||
|
key: 'algorithm',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
value: 'sha256',
|
||||||
|
description: 'Specifies the cryptographic hash function to use for HMAC generation.',
|
||||||
|
options: [
|
||||||
|
{ label: 'SHA-256', value: 'sha256' },
|
||||||
|
],
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Message',
|
||||||
|
key: 'message',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description: 'The input message to be hashed. This is the value that will be processed to generate the HMAC.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Secret Key',
|
||||||
|
key: 'secretKey',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description: 'The secret key used to create the HMAC.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Output Encoding',
|
||||||
|
key: 'outputEncoding',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
value: 'hex',
|
||||||
|
description: 'Specifies the encoding format for the HMAC digest output.',
|
||||||
|
options: [
|
||||||
|
{ label: 'base64', value: 'base64' },
|
||||||
|
{ label: 'base64url', value: 'base64url' },
|
||||||
|
{ label: 'hex', value: 'hex' },
|
||||||
|
],
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const hash = createHmac($.step.parameters.algorithm, $.step.parameters.secretKey)
|
||||||
|
.update($.step.parameters.message)
|
||||||
|
.digest($.step.parameters.outputEncoding);
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: {
|
||||||
|
hash
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
@@ -0,0 +1,65 @@
|
|||||||
|
import crypto from 'node:crypto';
|
||||||
|
import defineAction from '../../../../helpers/define-action.js';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Create Signature',
|
||||||
|
key: 'createSignature',
|
||||||
|
description: 'Create a digital signature using the specified algorithm, secret key, and message.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Algorithm',
|
||||||
|
key: 'algorithm',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
value: 'RSA-SHA256',
|
||||||
|
description: 'Specifies the cryptographic hash function to use for HMAC generation.',
|
||||||
|
options: [
|
||||||
|
{ label: 'RSA-SHA256', value: 'RSA-SHA256' },
|
||||||
|
],
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Message',
|
||||||
|
key: 'message',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description: 'The input message to be signed.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Private Key',
|
||||||
|
key: 'privateKey',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description: 'The RSA private key in PEM format used for signing.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Output Encoding',
|
||||||
|
key: 'outputEncoding',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
value: 'hex',
|
||||||
|
description: 'Specifies the encoding format for the digital signature output. This determines how the generated signature will be represented as a string.',
|
||||||
|
options: [
|
||||||
|
{ label: 'base64', value: 'base64' },
|
||||||
|
{ label: 'base64url', value: 'base64url' },
|
||||||
|
{ label: 'hex', value: 'hex' },
|
||||||
|
],
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const signer = crypto.createSign($.step.parameters.algorithm);
|
||||||
|
signer.update($.step.parameters.message);
|
||||||
|
signer.end();
|
||||||
|
const signature = signer.sign($.step.parameters.privateKey, $.step.parameters.outputEncoding);
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: {
|
||||||
|
signature
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
4
packages/backend/src/apps/cryptography/actions/index.js
Normal file
4
packages/backend/src/apps/cryptography/actions/index.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import createHmac from './create-hmac/index.js';
|
||||||
|
import createRsaSha256Signature from './create-rsa-sha256-signature/index.js';
|
||||||
|
|
||||||
|
export default [createHmac, createRsaSha256Signature];
|
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="100pt" height="100pt" version="1.1" viewBox="0 0 100 100">
|
||||||
|
<path d="m66.012 33h-3.0117v-11c0-7.1719-5.8281-13-13-13s-13 5.8281-13 13v11h-3.0117c-2.75 0-4.9883 2.2383-4.9883 4.9883v28.012c0 2.75 2.2383 4.9883 4.9883 4.9883h32.012c2.75 0 4.9883-2.2383 4.9883-4.9883v-28.012c0.011719-2.75-2.2266-4.9883-4.9766-4.9883zm-27.012-11c0-6.0703 4.9297-11 11-11s11 4.9297 11 11v11h-22zm30 44.012c0 1.6484-1.3398 2.9883-2.9883 2.9883h-32.023c-1.6484 0-2.9883-1.3398-2.9883-2.9883v-28.023c0-1.6484 1.3398-2.9883 2.9883-2.9883h32.023c1.6484 0 2.9883 1.3398 2.9883 2.9883zm-18 9.9883v14c0 0.55078-0.44922 1-1 1s-1-0.44922-1-1v-14c0-0.55078 0.44922-1 1-1s1 0.44922 1 1zm20 8c0 0.55078-0.44922 1-1 1h-8c-0.55078 0-1-0.44922-1-1v-8c0-0.55078 0.44922-1 1-1s1 0.44922 1 1v7h7c0.55078 0 1 0.44922 1 1zm-32-8v8c0 0.55078-0.44922 1-1 1h-8c-0.55078 0-1-0.44922-1-1s0.44922-1 1-1h7v-7c0-0.55078 0.44922-1 1-1s1 0.44922 1 1zm-14-26c0 0.55078-0.44922 1-1 1h-14c-0.55078 0-1-0.44922-1-1s0.44922-1 1-1h14c0.55078 0 1 0.44922 1 1zm0-12c0 0.55078-0.44922 1-1 1h-8c-0.55078 0-1-0.44922-1-1v-8c0-0.55078 0.44922-1 1-1s1 0.44922 1 1v7h7c0.55078 0 1 0.44922 1 1zm0 24c0 0.55078-0.44922 1-1 1h-7v7c0 0.55078-0.44922 1-1 1s-1-0.44922-1-1v-8c0-0.55078 0.44922-1 1-1h8c0.55078 0 1 0.44922 1 1zm66-12c0 0.55078-0.44922 1-1 1h-14c-0.55078 0-1-0.44922-1-1s0.44922-1 1-1h14c0.55078 0 1 0.44922 1 1zm-16-12c0-0.55078 0.44922-1 1-1h7v-7c0-0.55078 0.44922-1 1-1s1 0.44922 1 1v8c0 0.55078-0.44922 1-1 1h-8c-0.55078 0-1-0.44922-1-1zm10 24v8c0 0.55078-0.44922 1-1 1s-1-0.44922-1-1v-7h-7c-0.55078 0-1-0.44922-1-1s0.44922-1 1-1h8c0.55078 0 1 0.44922 1 1zm-35-17c-2.7617 0-5 2.2383-5 5 0 2.4102 1.7188 4.4297 4 4.8984v5.1016c0 0.55078 0.44922 1 1 1s1-0.44922 1-1v-5.1016c2.2812-0.46094 4-2.4805 4-4.8984 0-2.7617-2.2383-5-5-5zm0 8c-1.6484 0-3-1.3516-3-3s1.3516-3 3-3 3 1.3516 3 3-1.3516 3-3 3z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
14
packages/backend/src/apps/cryptography/index.js
Normal file
14
packages/backend/src/apps/cryptography/index.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import defineApp from '../../helpers/define-app.js';
|
||||||
|
import actions from './actions/index.js';
|
||||||
|
|
||||||
|
export default defineApp({
|
||||||
|
name: 'Cryptography',
|
||||||
|
key: 'cryptography',
|
||||||
|
iconUrl: '{BASE_URL}/apps/cryptography/assets/favicon.svg',
|
||||||
|
authDocUrl: '{DOCS_URL}/apps/cryptography/connection',
|
||||||
|
supportsConnections: false,
|
||||||
|
baseUrl: '',
|
||||||
|
apiBaseUrl: '',
|
||||||
|
primaryColor: '001F52',
|
||||||
|
actions,
|
||||||
|
});
|
@@ -1,8 +1,10 @@
|
|||||||
import defineAction from '../../../../helpers/define-action.js';
|
import defineAction from '../../../../helpers/define-action.js';
|
||||||
import formatDateTime from './transformers/format-date-time.js';
|
import formatDateTime from './transformers/format-date-time.js';
|
||||||
|
import getCurrentTimestamp from './transformers/get-current-timestamp.js';
|
||||||
|
|
||||||
const transformers = {
|
const transformers = {
|
||||||
formatDateTime,
|
formatDateTime,
|
||||||
|
getCurrentTimestamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defineAction({
|
export default defineAction({
|
||||||
@@ -16,7 +18,16 @@ export default defineAction({
|
|||||||
type: 'dropdown',
|
type: 'dropdown',
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: true,
|
||||||
options: [{ label: 'Format Date / Time', value: 'formatDateTime' }],
|
options: [
|
||||||
|
{
|
||||||
|
label: 'Get current timestamp',
|
||||||
|
value: 'getCurrentTimestamp',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Format Date / Time',
|
||||||
|
value: 'formatDateTime',
|
||||||
|
},
|
||||||
|
],
|
||||||
additionalFields: {
|
additionalFields: {
|
||||||
type: 'query',
|
type: 'query',
|
||||||
name: 'getDynamicFields',
|
name: 'getDynamicFields',
|
||||||
|
@@ -0,0 +1,5 @@
|
|||||||
|
const getCurrentTimestamp = () => {
|
||||||
|
return Date.now();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getCurrentTimestamp;
|
@@ -14,6 +14,8 @@ import stringToBase64 from './transformers/string-to-base64.js';
|
|||||||
import encodeUri from './transformers/encode-uri.js';
|
import encodeUri from './transformers/encode-uri.js';
|
||||||
import trimWhitespace from './transformers/trim-whitespace.js';
|
import trimWhitespace from './transformers/trim-whitespace.js';
|
||||||
import useDefaultValue from './transformers/use-default-value.js';
|
import useDefaultValue from './transformers/use-default-value.js';
|
||||||
|
import parseStringifiedJson from './transformers/parse-stringified-json.js';
|
||||||
|
import createUuid from './transformers/create-uuid.js';
|
||||||
|
|
||||||
const transformers = {
|
const transformers = {
|
||||||
base64ToString,
|
base64ToString,
|
||||||
@@ -30,6 +32,8 @@ const transformers = {
|
|||||||
encodeUri,
|
encodeUri,
|
||||||
trimWhitespace,
|
trimWhitespace,
|
||||||
useDefaultValue,
|
useDefaultValue,
|
||||||
|
parseStringifiedJson,
|
||||||
|
createUuid,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default defineAction({
|
export default defineAction({
|
||||||
@@ -47,19 +51,21 @@ export default defineAction({
|
|||||||
options: [
|
options: [
|
||||||
{ label: 'Base64 to String', value: 'base64ToString' },
|
{ label: 'Base64 to String', value: 'base64ToString' },
|
||||||
{ label: 'Capitalize', value: 'capitalize' },
|
{ label: 'Capitalize', value: 'capitalize' },
|
||||||
|
{ label: 'Convert HTML to Markdown', value: 'htmlToMarkdown' },
|
||||||
|
{ label: 'Convert Markdown to HTML', value: 'markdownToHtml' },
|
||||||
|
{ label: 'Create UUID', value: 'createUuid' },
|
||||||
|
{ label: 'Encode URI', value: 'encodeUri' },
|
||||||
{
|
{
|
||||||
label: 'Encode URI Component',
|
label: 'Encode URI Component',
|
||||||
value: 'encodeUriComponent',
|
value: 'encodeUriComponent',
|
||||||
},
|
},
|
||||||
{ label: 'Convert HTML to Markdown', value: 'htmlToMarkdown' },
|
|
||||||
{ label: 'Convert Markdown to HTML', value: 'markdownToHtml' },
|
|
||||||
{ label: 'Extract Email Address', value: 'extractEmailAddress' },
|
{ label: 'Extract Email Address', value: 'extractEmailAddress' },
|
||||||
{ label: 'Extract Number', value: 'extractNumber' },
|
{ label: 'Extract Number', value: 'extractNumber' },
|
||||||
{ label: 'Lowercase', value: 'lowercase' },
|
{ label: 'Lowercase', value: 'lowercase' },
|
||||||
|
{ label: 'Parse stringified JSON', value: 'parseStringifiedJson' },
|
||||||
{ label: 'Pluralize', value: 'pluralize' },
|
{ label: 'Pluralize', value: 'pluralize' },
|
||||||
{ label: 'Replace', value: 'replace' },
|
{ label: 'Replace', value: 'replace' },
|
||||||
{ label: 'String to Base64', value: 'stringToBase64' },
|
{ label: 'String to Base64', value: 'stringToBase64' },
|
||||||
{ label: 'Encode URI', value: 'encodeUri' },
|
|
||||||
{ label: 'Trim Whitespace', value: 'trimWhitespace' },
|
{ label: 'Trim Whitespace', value: 'trimWhitespace' },
|
||||||
{ label: 'Use Default Value', value: 'useDefaultValue' },
|
{ label: 'Use Default Value', value: 'useDefaultValue' },
|
||||||
],
|
],
|
||||||
|
@@ -0,0 +1,7 @@
|
|||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
const createUuidV4 = () => {
|
||||||
|
return uuidv4();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createUuidV4;
|
@@ -0,0 +1,7 @@
|
|||||||
|
const parseStringifiedJson = ($) => {
|
||||||
|
const input = $.step.parameters.input;
|
||||||
|
|
||||||
|
return JSON.parse(input);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default parseStringifiedJson;
|
@@ -1,8 +1,26 @@
|
|||||||
const replace = ($) => {
|
const replace = ($) => {
|
||||||
const input = $.step.parameters.input;
|
const input = $.step.parameters.input;
|
||||||
|
|
||||||
const find = $.step.parameters.find;
|
const find = $.step.parameters.find;
|
||||||
const replace = $.step.parameters.replace;
|
const replace = $.step.parameters.replace;
|
||||||
|
const useRegex = $.step.parameters.useRegex;
|
||||||
|
|
||||||
|
if (useRegex) {
|
||||||
|
const ignoreCase = $.step.parameters.ignoreCase;
|
||||||
|
|
||||||
|
const flags = [ignoreCase && 'i', 'g'].filter(Boolean).join('');
|
||||||
|
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
$.execution.exit();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
const regex = new RegExp(find, flags);
|
||||||
|
|
||||||
|
const replacedValue = input.replaceAll(regex, replace);
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
return replacedValue;
|
||||||
|
}
|
||||||
|
|
||||||
return input.replaceAll(find, replace);
|
return input.replaceAll(find, replace);
|
||||||
};
|
};
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
import listTransformOptions from './list-transform-options/index.js';
|
import listTransformOptions from './list-transform-options/index.js';
|
||||||
|
import listReplaceRegexOptions from './list-replace-regex-options/index.js';
|
||||||
|
|
||||||
export default [listTransformOptions];
|
export default [listTransformOptions, listReplaceRegexOptions];
|
||||||
|
@@ -0,0 +1,23 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List replace regex options',
|
||||||
|
key: 'listReplaceRegexOptions',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
if (!$.step.parameters.useRegex) return [];
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'Ignore case',
|
||||||
|
key: 'ignoreCase',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: 'Ignore case sensitivity.',
|
||||||
|
variables: true,
|
||||||
|
options: [
|
||||||
|
{ label: 'Yes', value: true },
|
||||||
|
{ label: 'No', value: false },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
};
|
@@ -12,6 +12,7 @@ import stringToBase64 from './text/string-to-base64.js';
|
|||||||
import encodeUri from './text/encode-uri.js';
|
import encodeUri from './text/encode-uri.js';
|
||||||
import trimWhitespace from './text/trim-whitespace.js';
|
import trimWhitespace from './text/trim-whitespace.js';
|
||||||
import useDefaultValue from './text/use-default-value.js';
|
import useDefaultValue from './text/use-default-value.js';
|
||||||
|
import parseStringifiedJson from './text/parse-stringified-json.js';
|
||||||
import performMathOperation from './numbers/perform-math-operation.js';
|
import performMathOperation from './numbers/perform-math-operation.js';
|
||||||
import randomNumber from './numbers/random-number.js';
|
import randomNumber from './numbers/random-number.js';
|
||||||
import formatNumber from './numbers/format-number.js';
|
import formatNumber from './numbers/format-number.js';
|
||||||
@@ -38,6 +39,7 @@ const options = {
|
|||||||
formatNumber,
|
formatNumber,
|
||||||
formatPhoneNumber,
|
formatPhoneNumber,
|
||||||
formatDateTime,
|
formatDateTime,
|
||||||
|
parseStringifiedJson,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@@ -0,0 +1,12 @@
|
|||||||
|
const useDefaultValue = [
|
||||||
|
{
|
||||||
|
label: 'Input',
|
||||||
|
key: 'input',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description: 'Stringified JSON you want to parse.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default useDefaultValue;
|
@@ -23,6 +23,33 @@ const replace = [
|
|||||||
description: 'Text that will replace the found text.',
|
description: 'Text that will replace the found text.',
|
||||||
variables: true,
|
variables: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Use Regular Expression',
|
||||||
|
key: 'useRegex',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: 'Use regex to search values.',
|
||||||
|
variables: true,
|
||||||
|
value: false,
|
||||||
|
options: [
|
||||||
|
{ label: 'Yes', value: true },
|
||||||
|
{ label: 'No', value: false },
|
||||||
|
],
|
||||||
|
additionalFields: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicFields',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listReplaceRegexOptions',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.useRegex',
|
||||||
|
value: '{parameters.useRegex}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default replace;
|
export default replace;
|
||||||
|
@@ -10,7 +10,7 @@ export default defineAction({
|
|||||||
label: 'Repo',
|
label: 'Repo',
|
||||||
key: 'repo',
|
key: 'repo',
|
||||||
type: 'dropdown',
|
type: 'dropdown',
|
||||||
required: false,
|
required: true,
|
||||||
variables: true,
|
variables: true,
|
||||||
source: {
|
source: {
|
||||||
type: 'query',
|
type: 'query',
|
||||||
|
@@ -0,0 +1,175 @@
|
|||||||
|
import defineAction from '../../../../helpers/define-action.js';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Find worksheet',
|
||||||
|
key: 'findWorksheet',
|
||||||
|
description:
|
||||||
|
'Finds a worksheet by title. Optionally, create a worksheet if none are found.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Drive',
|
||||||
|
key: 'driveId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description:
|
||||||
|
'The Google Drive where your spreadsheet resides. If nothing is selected, then your personal Google Drive will be used.',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listDrives',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Spreadsheet',
|
||||||
|
key: 'spreadsheetId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
dependsOn: ['parameters.driveId'],
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listSpreadsheets',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.driveId',
|
||||||
|
value: '{parameters.driveId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Title',
|
||||||
|
key: 'title',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description:
|
||||||
|
'The worksheet title needs to match exactly, and the search is case-sensitive.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Create worksheet if none are found.',
|
||||||
|
key: 'createWorksheet',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
options: [
|
||||||
|
{ label: 'Yes', value: true },
|
||||||
|
{ label: 'No', value: false },
|
||||||
|
],
|
||||||
|
additionalFields: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicFields',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listCreateWorksheetFields',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.createWorksheet',
|
||||||
|
value: '{parameters.createWorksheet}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const createWorksheet = $.step.parameters.createWorksheet;
|
||||||
|
|
||||||
|
async function findWorksheet() {
|
||||||
|
const {
|
||||||
|
data: { sheets },
|
||||||
|
} = await $.http.get(`/v4/spreadsheets/${$.step.parameters.spreadsheetId}`);
|
||||||
|
|
||||||
|
const selectedSheet = sheets.find(
|
||||||
|
(sheet) => sheet.properties.title === $.step.parameters.title
|
||||||
|
);
|
||||||
|
|
||||||
|
return selectedSheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedSheet = await findWorksheet();
|
||||||
|
|
||||||
|
if (selectedSheet) {
|
||||||
|
$.setActionItem({
|
||||||
|
raw: selectedSheet,
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (createWorksheet) {
|
||||||
|
const headers = $.step.parameters.headers;
|
||||||
|
const headerValues = headers.map((entry) => entry.header);
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
requests: [
|
||||||
|
{
|
||||||
|
addSheet: {
|
||||||
|
properties: {
|
||||||
|
title: $.step.parameters.title,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await $.http.post(
|
||||||
|
`/v4/spreadsheets/${$.step.parameters.spreadsheetId}:batchUpdate`,
|
||||||
|
body
|
||||||
|
);
|
||||||
|
|
||||||
|
if (headerValues.length) {
|
||||||
|
const body = {
|
||||||
|
requests: [
|
||||||
|
{
|
||||||
|
updateCells: {
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
values: headerValues.map((header) => ({
|
||||||
|
userEnteredValue: { stringValue: header },
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
fields: '*',
|
||||||
|
start: {
|
||||||
|
sheetId:
|
||||||
|
data.replies[data.replies.length - 1].addSheet.properties
|
||||||
|
.sheetId,
|
||||||
|
rowIndex: 0,
|
||||||
|
columnIndex: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
await $.http.post(
|
||||||
|
`/v4/spreadsheets/${$.step.parameters.spreadsheetId}:batchUpdate`,
|
||||||
|
body
|
||||||
|
);
|
||||||
|
|
||||||
|
const createdSheet = await findWorksheet();
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: createdSheet,
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
@@ -1,5 +1,11 @@
|
|||||||
import createSpreadsheet from './create-spreadsheet/index.js';
|
import createSpreadsheet from './create-spreadsheet/index.js';
|
||||||
import createSpreadsheetRow from './create-spreadsheet-row/index.js';
|
import createSpreadsheetRow from './create-spreadsheet-row/index.js';
|
||||||
import createWorksheet from './create-worksheet/index.js';
|
import createWorksheet from './create-worksheet/index.js';
|
||||||
|
import findWorksheet from './find-worksheet/index.js';
|
||||||
|
|
||||||
export default [createSpreadsheet, createSpreadsheetRow, createWorksheet];
|
export default [
|
||||||
|
createSpreadsheet,
|
||||||
|
createSpreadsheetRow,
|
||||||
|
createWorksheet,
|
||||||
|
findWorksheet,
|
||||||
|
];
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
import listSheetHeaders from './list-sheet-headers/index.js';
|
import listSheetHeaders from './list-sheet-headers/index.js';
|
||||||
|
import listCreateWorksheetFields from './list-create-worksheet-fields/index.js';
|
||||||
|
|
||||||
export default [listSheetHeaders];
|
export default [listSheetHeaders, listCreateWorksheetFields];
|
||||||
|
@@ -0,0 +1,26 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List create worksheet fields',
|
||||||
|
key: 'listCreateWorksheetFields',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
if ($.step.parameters.createWorksheet) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'Headers',
|
||||||
|
key: 'headers',
|
||||||
|
type: 'dynamic',
|
||||||
|
required: false,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'Header',
|
||||||
|
key: 'header',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
@@ -145,6 +145,13 @@ export default defineAction({
|
|||||||
responseData = Buffer.from(responseData).toString('base64');
|
responseData = Buffer.from(responseData).toString('base64');
|
||||||
}
|
}
|
||||||
|
|
||||||
$.setActionItem({ raw: { data: responseData } });
|
$.setActionItem({
|
||||||
|
raw: {
|
||||||
|
data: responseData,
|
||||||
|
headers: response.headers,
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
1
packages/backend/src/apps/jotform/assets/favicon.svg
Normal file
1
packages/backend/src/apps/jotform/assets/favicon.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns:xlink="http://www.w3.org/1999/xlink" class="jff-logo-img" width="53" height="59" viewBox="0 0 53 59" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M14.4509 55.1332C15.5462 56.1951 14.7721 58.0143 13.2168 58.0143H3.4831C1.56265 58.0143 0 56.4995 0 54.6377V45.2017C0 43.6939 1.87664 42.9436 2.97195 44.0054L14.4509 55.1332Z" fill="#0A1551"></path><path d="M29.6655 55.8676C26.7843 53.0052 26.7843 48.3642 29.6655 45.5018L40.0638 35.1713C42.945 32.3089 47.6164 32.3089 50.4976 35.1713C53.3788 38.0338 53.3788 42.6747 50.4976 45.5371L40.0993 55.8676C37.2181 58.73 32.5468 58.73 29.6655 55.8676Z" fill="#FFB629"></path><path d="M2.1968 29.9101C-0.684414 27.0476 -0.684413 22.4067 2.1968 19.5443L19.696 2.14685C22.5772 -0.71559 27.2486 -0.715594 30.1298 2.14685C33.011 5.00929 33.011 9.65022 30.1298 12.5127L12.6306 29.9101C9.74937 32.7725 5.078 32.7725 2.1968 29.9101Z" fill="#0099FF"></path><path d="M16.5015 42.3095C13.6203 39.4471 13.6203 34.8062 16.5015 31.9437L40.1461 8.45322C43.0273 5.59079 47.6986 5.59079 50.5798 8.45322C53.4611 11.3157 53.4611 15.9566 50.5798 18.819L26.9353 42.3095C24.0541 45.1719 19.3827 45.1719 16.5015 42.3095Z" fill="#FF6100"></path></svg>
|
After Width: | Height: | Size: 1.2 KiB |
30
packages/backend/src/apps/jotform/auth/index.js
Normal file
30
packages/backend/src/apps/jotform/auth/index.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import verifyCredentials from './verify-credentials.js';
|
||||||
|
import isStillVerified from './is-still-verified.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'apiUrl',
|
||||||
|
label: 'API URL',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
readOnly: false,
|
||||||
|
value: 'https://api.jotform.com',
|
||||||
|
placeholder: 'https://${subdomain}.jotform.com/api',
|
||||||
|
clickToCopy: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'apiKey',
|
||||||
|
label: 'API Key',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
verifyCredentials,
|
||||||
|
isStillVerified,
|
||||||
|
};
|
@@ -0,0 +1,8 @@
|
|||||||
|
import getCurrentUser from '../common/get-current-user.js';
|
||||||
|
|
||||||
|
const isStillVerified = async ($) => {
|
||||||
|
const user = await getCurrentUser($);
|
||||||
|
return !!user.username;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isStillVerified;
|
12
packages/backend/src/apps/jotform/auth/verify-credentials.js
Normal file
12
packages/backend/src/apps/jotform/auth/verify-credentials.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import getCurrentUser from '../common/get-current-user.js';
|
||||||
|
|
||||||
|
const verifyCredentials = async ($) => {
|
||||||
|
const user = await getCurrentUser($);
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
screenName: user.name,
|
||||||
|
apiKey: $.auth.data.apiKey,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default verifyCredentials;
|
@@ -0,0 +1,9 @@
|
|||||||
|
const addAuthHeader = ($, requestConfig) => {
|
||||||
|
if ($.auth.data?.apiKey) {
|
||||||
|
requestConfig.headers['APIKEY'] = `${$.auth.data.apiKey}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default addAuthHeader;
|
@@ -0,0 +1,7 @@
|
|||||||
|
const getCurrentUser = async ($) => {
|
||||||
|
const response = await $.http.get('/user');
|
||||||
|
const currentUser = response.data.content;
|
||||||
|
return currentUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getCurrentUser;
|
11
packages/backend/src/apps/jotform/common/set-base-url.js
Normal file
11
packages/backend/src/apps/jotform/common/set-base-url.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const setBaseUrl = ($, requestConfig) => {
|
||||||
|
if ($.auth.data.apiUrl) {
|
||||||
|
requestConfig.baseURL = $.auth.data.apiUrl;
|
||||||
|
} else if ($.app.apiBaseUrl) {
|
||||||
|
requestConfig.baseURL = $.app.apiBaseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default setBaseUrl;
|
3
packages/backend/src/apps/jotform/dynamic-data/index.js
Normal file
3
packages/backend/src/apps/jotform/dynamic-data/index.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import listForms from './list-forms/index.js';
|
||||||
|
|
||||||
|
export default [listForms];
|
@@ -0,0 +1,41 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List forms',
|
||||||
|
key: 'listForms',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const forms = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
let hasMore = false;
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
limit: 1000,
|
||||||
|
offset: 0,
|
||||||
|
orderby: 'created_at',
|
||||||
|
};
|
||||||
|
|
||||||
|
do {
|
||||||
|
const { data } = await $.http.get('/user/forms', { params });
|
||||||
|
params.offset = params.offset + params.limit;
|
||||||
|
|
||||||
|
if (data.content?.length) {
|
||||||
|
for (const form of data.content) {
|
||||||
|
if (form.status === 'ENABLED') {
|
||||||
|
forms.data.push({
|
||||||
|
value: form.id,
|
||||||
|
name: form.title,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.resultSet.count >= data.resultSet.limit) {
|
||||||
|
hasMore = true;
|
||||||
|
} else {
|
||||||
|
hasMore = false;
|
||||||
|
}
|
||||||
|
} while (hasMore);
|
||||||
|
|
||||||
|
return forms;
|
||||||
|
},
|
||||||
|
};
|
21
packages/backend/src/apps/jotform/index.js
Normal file
21
packages/backend/src/apps/jotform/index.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import defineApp from '../../helpers/define-app.js';
|
||||||
|
import addAuthHeader from './common/add-auth-header.js';
|
||||||
|
import auth from './auth/index.js';
|
||||||
|
import setBaseUrl from './common/set-base-url.js';
|
||||||
|
import triggers from './triggers/index.js';
|
||||||
|
import dynamicData from './dynamic-data/index.js';
|
||||||
|
|
||||||
|
export default defineApp({
|
||||||
|
name: 'Jotform',
|
||||||
|
key: 'jotform',
|
||||||
|
iconUrl: '{BASE_URL}/apps/jotform/assets/favicon.svg',
|
||||||
|
authDocUrl: 'https://automatisch.io/docs/apps/jotform/connection',
|
||||||
|
supportsConnections: true,
|
||||||
|
baseUrl: 'https://www.jotform.com',
|
||||||
|
apiBaseUrl: 'https://api.jotform.com',
|
||||||
|
primaryColor: 'FF6100',
|
||||||
|
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||||
|
auth,
|
||||||
|
triggers,
|
||||||
|
dynamicData,
|
||||||
|
});
|
3
packages/backend/src/apps/jotform/triggers/index.js
Normal file
3
packages/backend/src/apps/jotform/triggers/index.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import newSubmissions from './new-submissions/index.js';
|
||||||
|
|
||||||
|
export default [newSubmissions];
|
@@ -0,0 +1,109 @@
|
|||||||
|
import Crypto from 'crypto';
|
||||||
|
import { URLSearchParams } from 'url';
|
||||||
|
import defineTrigger from '../../../../helpers/define-trigger.js';
|
||||||
|
|
||||||
|
export default defineTrigger({
|
||||||
|
name: 'New submissions',
|
||||||
|
key: 'newSubmissions',
|
||||||
|
type: 'webhook',
|
||||||
|
description:
|
||||||
|
'Triggers when a new submission has been added to a specific form.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Form',
|
||||||
|
key: 'formId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listForms',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const dataItem = {
|
||||||
|
raw: $.request.body,
|
||||||
|
meta: {
|
||||||
|
internalId: Crypto.randomUUID(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
$.pushTriggerItem(dataItem);
|
||||||
|
},
|
||||||
|
|
||||||
|
async testRun($) {
|
||||||
|
const sampleEventData = {
|
||||||
|
ip: '127.0.0.1',
|
||||||
|
type: 'WEB',
|
||||||
|
appID: '',
|
||||||
|
event: '',
|
||||||
|
action: '',
|
||||||
|
formID: Crypto.randomUUID(),
|
||||||
|
parent: '',
|
||||||
|
pretty: 'Name:test, E-mail:user@automatisch.io',
|
||||||
|
teamID: '',
|
||||||
|
unread: '',
|
||||||
|
product: '',
|
||||||
|
subject: '',
|
||||||
|
isSilent: '',
|
||||||
|
username: 'username',
|
||||||
|
deviceIDs: 'Array',
|
||||||
|
formTitle: 'Opt-In Form-Get Free Email Updates!',
|
||||||
|
fromTable: '',
|
||||||
|
customBody: '',
|
||||||
|
documentID: '',
|
||||||
|
rawRequest: '',
|
||||||
|
webhookURL: '',
|
||||||
|
customTitle: '',
|
||||||
|
trackAction: 'Array',
|
||||||
|
customParams: '',
|
||||||
|
submissionID: Crypto.randomUUID(),
|
||||||
|
customBodyParams: 'Array',
|
||||||
|
customTitleParams: 'Array',
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataItem = {
|
||||||
|
raw: sampleEventData,
|
||||||
|
meta: {
|
||||||
|
internalId: sampleEventData.submissionID,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
$.pushTriggerItem(dataItem);
|
||||||
|
},
|
||||||
|
|
||||||
|
async registerHook($) {
|
||||||
|
const formId = $.step.parameters.formId;
|
||||||
|
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
webhookURL: $.webhookUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data } = await $.http.post(
|
||||||
|
`/form/${formId}/webhooks`,
|
||||||
|
params.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
await $.flow.setRemoteWebhookId(data.content[0]);
|
||||||
|
},
|
||||||
|
|
||||||
|
async unregisterHook($) {
|
||||||
|
const formId = $.step.parameters.formId;
|
||||||
|
|
||||||
|
const { data } = await $.http.get(`/form/${formId}/webhooks`);
|
||||||
|
|
||||||
|
const webhookURLs = Object.values(data.content);
|
||||||
|
const webhookId = webhookURLs.findIndex((url) => url === $.webhookUrl);
|
||||||
|
|
||||||
|
await $.http.delete(`/form/${formId}/webhooks/${webhookId}`);
|
||||||
|
},
|
||||||
|
});
|
@@ -0,0 +1,180 @@
|
|||||||
|
import defineAction from '../../../../helpers/define-action.js';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Create campaign',
|
||||||
|
key: 'createCampaign',
|
||||||
|
description: 'Creates a new campaign draft.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Campaign Name',
|
||||||
|
key: 'campaignName',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Audience',
|
||||||
|
key: 'audienceId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listAudiences',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Segment or Tag',
|
||||||
|
key: 'segmentOrTagId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
dependsOn: ['parameters.audienceId'],
|
||||||
|
description:
|
||||||
|
'Choose the specific segment or tag to which you"d like to direct the campaign. If no segment or tag is chosen, the campaign will be distributed to the entire audience previously selected.',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listSegmentsOrTags',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.audienceId',
|
||||||
|
value: '{parameters.audienceId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Email Subject',
|
||||||
|
key: 'emailSubject',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Preview Text',
|
||||||
|
key: 'previewText',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description:
|
||||||
|
'The snippet will be visible in the inbox following the subject line.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'From Name',
|
||||||
|
key: 'fromName',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description: 'The "from" name on the campaign (not an email address).',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'From Email Address',
|
||||||
|
key: 'fromEmailAddress',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description: 'The reply-to email address for the campaign.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'To Name',
|
||||||
|
key: 'toName',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description:
|
||||||
|
'Supports *|MERGETAGS|* for recipient name, such as *|FNAME|*, *|LNAME|*, *|FNAME|* *|LNAME|*, etc.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Template',
|
||||||
|
key: 'templateId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: false,
|
||||||
|
description:
|
||||||
|
'Select either a template or provide HTML email content, you cannot provide both. If both fields are left blank, the campaign draft will have no content.',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listTemplates',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Email Content (HTML)',
|
||||||
|
key: 'emailContent',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description:
|
||||||
|
'Select either a template or provide HTML email content, you cannot provide both. If both fields are left blank, the campaign draft will have no content.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const {
|
||||||
|
campaignName,
|
||||||
|
audienceId,
|
||||||
|
segmentOrTagId,
|
||||||
|
emailSubject,
|
||||||
|
previewText,
|
||||||
|
fromName,
|
||||||
|
fromEmailAddress,
|
||||||
|
toName,
|
||||||
|
templateId,
|
||||||
|
emailContent,
|
||||||
|
} = $.step.parameters;
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
type: 'regular',
|
||||||
|
recipients: {
|
||||||
|
list_id: audienceId,
|
||||||
|
segment_opts: {
|
||||||
|
saved_segment_id: Number(segmentOrTagId),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
subject_line: emailSubject,
|
||||||
|
reply_to: fromEmailAddress,
|
||||||
|
title: campaignName,
|
||||||
|
preview_text: previewText,
|
||||||
|
from_name: fromName,
|
||||||
|
to_name: toName,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data: campaign } = await $.http.post('/3.0/campaigns', body);
|
||||||
|
|
||||||
|
const campaignBody = {
|
||||||
|
template: {
|
||||||
|
id: Number(templateId),
|
||||||
|
},
|
||||||
|
html: emailContent,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await $.http.put(
|
||||||
|
`/3.0/campaigns/${campaign.id}/content`,
|
||||||
|
campaignBody
|
||||||
|
);
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
4
packages/backend/src/apps/mailchimp/actions/index.js
Normal file
4
packages/backend/src/apps/mailchimp/actions/index.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import createCampaign from './create-campaign/index.js';
|
||||||
|
import sendCampaign from './send-campaign/index.js';
|
||||||
|
|
||||||
|
export default [createCampaign, sendCampaign];
|
@@ -0,0 +1,39 @@
|
|||||||
|
import defineAction from '../../../../helpers/define-action.js';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Send campaign',
|
||||||
|
key: 'sendCampaign',
|
||||||
|
description: 'Sends a campaign draft.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Campaign',
|
||||||
|
key: 'campaignId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listCampaigns',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const campaignId = $.step.parameters.campaignId;
|
||||||
|
|
||||||
|
await $.http.post(`/3.0/campaigns/${campaignId}/actions/send`);
|
||||||
|
|
||||||
|
$.setActionItem({
|
||||||
|
raw: {
|
||||||
|
output: 'sent',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
1
packages/backend/src/apps/mailchimp/assets/favicon.svg
Normal file
1
packages/backend/src/apps/mailchimp/assets/favicon.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 5.0 KiB |
@@ -0,0 +1,19 @@
|
|||||||
|
import { URLSearchParams } from 'url';
|
||||||
|
|
||||||
|
export default async function generateAuthUrl($) {
|
||||||
|
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||||
|
(field) => field.key == 'oAuthRedirectUrl'
|
||||||
|
);
|
||||||
|
const redirectUri = oauthRedirectUrlField.value;
|
||||||
|
const searchParams = new URLSearchParams({
|
||||||
|
response_type: 'code',
|
||||||
|
client_id: $.auth.data.clientId,
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = `https://login.mailchimp.com/oauth2/authorize?${searchParams.toString()}`;
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
url,
|
||||||
|
});
|
||||||
|
}
|
46
packages/backend/src/apps/mailchimp/auth/index.js
Normal file
46
packages/backend/src/apps/mailchimp/auth/index.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import generateAuthUrl from './generate-auth-url.js';
|
||||||
|
import verifyCredentials from './verify-credentials.js';
|
||||||
|
import isStillVerified from './is-still-verified.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'oAuthRedirectUrl',
|
||||||
|
label: 'OAuth Redirect URL',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: true,
|
||||||
|
value: '{WEB_APP_URL}/app/mailchimp/connections/add',
|
||||||
|
placeholder: null,
|
||||||
|
description:
|
||||||
|
'When asked to input a redirect URL in Mailchimp, enter the URL above.',
|
||||||
|
clickToCopy: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'clientId',
|
||||||
|
label: 'Client ID',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'clientSecret',
|
||||||
|
label: 'Client Secret',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
generateAuthUrl,
|
||||||
|
verifyCredentials,
|
||||||
|
isStillVerified,
|
||||||
|
};
|
@@ -0,0 +1,8 @@
|
|||||||
|
import getCurrentUser from '../common/get-current-user.js';
|
||||||
|
|
||||||
|
const isStillVerified = async ($) => {
|
||||||
|
const currentUser = await getCurrentUser($);
|
||||||
|
return !!currentUser.user_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isStillVerified;
|
@@ -0,0 +1,40 @@
|
|||||||
|
import getCurrentUser from '../common/get-current-user.js';
|
||||||
|
|
||||||
|
const verifyCredentials = async ($) => {
|
||||||
|
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||||
|
(field) => field.key == 'oAuthRedirectUrl'
|
||||||
|
);
|
||||||
|
const redirectUri = oauthRedirectUrlField.value;
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
grant_type: 'authorization_code',
|
||||||
|
client_id: $.auth.data.clientId,
|
||||||
|
client_secret: $.auth.data.clientSecret,
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
code: $.auth.data.code,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data } = await $.http.post(
|
||||||
|
'https://login.mailchimp.com/oauth2/token',
|
||||||
|
params.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
accessToken: data.access_token,
|
||||||
|
tokenType: data.token_type,
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentUser = await getCurrentUser($);
|
||||||
|
|
||||||
|
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,
|
||||||
|
serverPrefix: currentUser.dc,
|
||||||
|
screenName: currentUser.login.login_name,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default verifyCredentials;
|
@@ -0,0 +1,12 @@
|
|||||||
|
const addAuthHeader = ($, requestConfig) => {
|
||||||
|
if (
|
||||||
|
!requestConfig.additionalProperties?.skipAddingAuthHeader &&
|
||||||
|
$.auth.data?.accessToken
|
||||||
|
) {
|
||||||
|
requestConfig.headers.Authorization = `Bearer ${$.auth.data.accessToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default addAuthHeader;
|
@@ -0,0 +1,18 @@
|
|||||||
|
const getCurrentUser = async ($) => {
|
||||||
|
const { data: currentUser } = await $.http.get(
|
||||||
|
'https://login.mailchimp.com/oauth2/metadata',
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `OAuth ${$.auth.data.accessToken}`,
|
||||||
|
},
|
||||||
|
additionalProperties: {
|
||||||
|
skipAddingAuthHeader: true,
|
||||||
|
skipAddingBaseUrl: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return currentUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getCurrentUser;
|
10
packages/backend/src/apps/mailchimp/common/set-base-url.js
Normal file
10
packages/backend/src/apps/mailchimp/common/set-base-url.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
const setBaseUrl = ($, requestConfig) => {
|
||||||
|
const serverPrefix = $.auth.data.serverPrefix;
|
||||||
|
if (!requestConfig.additionalProperties?.skipAddingBaseUrl && serverPrefix) {
|
||||||
|
requestConfig.baseURL = `https://${serverPrefix}.api.mailchimp.com`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default setBaseUrl;
|
@@ -0,0 +1,6 @@
|
|||||||
|
import listAudiences from './list-audiences/index.js';
|
||||||
|
import listCampaigns from './list-campaigns/index.js';
|
||||||
|
import listTags from './list-segments-or-tags/index.js';
|
||||||
|
import listTemplates from './list-templates/index.js';
|
||||||
|
|
||||||
|
export default [listAudiences, listCampaigns, listTags, listTemplates];
|
@@ -0,0 +1,40 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List audiences',
|
||||||
|
key: 'listAudiences',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const audiences = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
let hasMore = false;
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
sort_field: 'date_created',
|
||||||
|
sort_dir: 'DESC',
|
||||||
|
count: 1000,
|
||||||
|
offset: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
do {
|
||||||
|
const { data } = await $.http.get('/3.0/lists', { params });
|
||||||
|
params.offset = params.offset + params.count;
|
||||||
|
|
||||||
|
if (data?.lists) {
|
||||||
|
for (const audience of data.lists) {
|
||||||
|
audiences.data.push({
|
||||||
|
value: audience.id,
|
||||||
|
name: audience.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.total_items > params.offset) {
|
||||||
|
hasMore = true;
|
||||||
|
} else {
|
||||||
|
hasMore = false;
|
||||||
|
}
|
||||||
|
} while (hasMore);
|
||||||
|
|
||||||
|
return audiences;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,42 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List campaigns',
|
||||||
|
key: 'listCampaigns',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const campaigns = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
let hasMore = false;
|
||||||
|
const audienceId = $.step.parameters.audienceId;
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
list_id: audienceId,
|
||||||
|
sort_field: 'create_time',
|
||||||
|
sort_dir: 'DESC',
|
||||||
|
count: 1000,
|
||||||
|
offset: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
do {
|
||||||
|
const { data } = await $.http.get('/3.0/campaigns', { params });
|
||||||
|
params.offset = params.offset + params.count;
|
||||||
|
|
||||||
|
if (data?.campaigns) {
|
||||||
|
for (const campaign of data.campaigns) {
|
||||||
|
campaigns.data.push({
|
||||||
|
value: campaign.id,
|
||||||
|
name: campaign.settings.title || campaign.settings.subject_line || 'Unnamed campaign',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.total_items > params.offset) {
|
||||||
|
hasMore = true;
|
||||||
|
} else {
|
||||||
|
hasMore = false;
|
||||||
|
}
|
||||||
|
} while (hasMore);
|
||||||
|
|
||||||
|
return campaigns;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,44 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List segments or tags',
|
||||||
|
key: 'listSegmentsOrTags',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const segmentsOrTags = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
const audienceId = $.step.parameters.audienceId;
|
||||||
|
|
||||||
|
if (!audienceId) {
|
||||||
|
return segmentsOrTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { tags: allTags },
|
||||||
|
} = await $.http.get(`/3.0/lists/${audienceId}/tag-search`);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { segments },
|
||||||
|
} = await $.http.get(`/3.0/lists/${audienceId}/segments`);
|
||||||
|
|
||||||
|
const mergedArray = [...allTags, ...segments].reduce(
|
||||||
|
(accumulator, current) => {
|
||||||
|
if (!accumulator.some((item) => item.id === current.id)) {
|
||||||
|
accumulator.push(current);
|
||||||
|
}
|
||||||
|
return accumulator;
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mergedArray?.length) {
|
||||||
|
for (const tagOrSegment of mergedArray) {
|
||||||
|
segmentsOrTags.data.push({
|
||||||
|
value: tagOrSegment.id,
|
||||||
|
name: tagOrSegment.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return segmentsOrTags;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,30 @@
|
|||||||
|
export default {
|
||||||
|
name: 'List templates',
|
||||||
|
key: 'listTemplates',
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const templates = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
sort_field: 'date_created',
|
||||||
|
sort_dir: 'DESC',
|
||||||
|
count: 1000,
|
||||||
|
offset: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await $.http.get('/3.0/templates', { params });
|
||||||
|
|
||||||
|
if (data?.templates) {
|
||||||
|
for (const template of data.templates) {
|
||||||
|
templates.data.push({
|
||||||
|
value: template.id,
|
||||||
|
name: template.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return templates;
|
||||||
|
},
|
||||||
|
};
|
23
packages/backend/src/apps/mailchimp/index.js
Normal file
23
packages/backend/src/apps/mailchimp/index.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import defineApp from '../../helpers/define-app.js';
|
||||||
|
import addAuthHeader from './common/add-auth-header.js';
|
||||||
|
import setBaseUrl from './common/set-base-url.js';
|
||||||
|
import auth from './auth/index.js';
|
||||||
|
import triggers from './triggers/index.js';
|
||||||
|
import dynamicData from './dynamic-data/index.js';
|
||||||
|
import actions from './actions/index.js';
|
||||||
|
|
||||||
|
export default defineApp({
|
||||||
|
name: 'Mailchimp',
|
||||||
|
key: 'mailchimp',
|
||||||
|
baseUrl: 'https://mailchimp.com',
|
||||||
|
apiBaseUrl: '',
|
||||||
|
iconUrl: '{BASE_URL}/apps/mailchimp/assets/favicon.svg',
|
||||||
|
authDocUrl: 'https://automatisch.io/docs/apps/mailchimp/connection',
|
||||||
|
primaryColor: '000000',
|
||||||
|
supportsConnections: true,
|
||||||
|
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||||
|
auth,
|
||||||
|
triggers,
|
||||||
|
dynamicData,
|
||||||
|
actions,
|
||||||
|
});
|
@@ -0,0 +1,101 @@
|
|||||||
|
import defineTrigger from '../../../../helpers/define-trigger.js';
|
||||||
|
|
||||||
|
export default defineTrigger({
|
||||||
|
name: 'Email opened',
|
||||||
|
key: 'emailOpened',
|
||||||
|
pollInterval: 15,
|
||||||
|
description:
|
||||||
|
'Triggers when a recipient opens an email as part of a particular campaign.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Audience',
|
||||||
|
key: 'audienceId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listAudiences',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Campaign Type',
|
||||||
|
key: 'campaignType',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'Campaign',
|
||||||
|
value: 'campaign',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Campaign',
|
||||||
|
key: 'campaignId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
dependsOn: ['parameters.audienceId'],
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listCampaigns',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.audienceId',
|
||||||
|
value: '{parameters.audienceId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const campaignId = $.step.parameters.campaignId;
|
||||||
|
let hasMore = false;
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
count: 1000,
|
||||||
|
offset: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
do {
|
||||||
|
const { data } = await $.http.get(
|
||||||
|
`/3.0/reports/${campaignId}/open-details`,
|
||||||
|
{ params }
|
||||||
|
);
|
||||||
|
params.offset = params.offset + params.count;
|
||||||
|
|
||||||
|
if (data.members?.length) {
|
||||||
|
for (const member of data.members) {
|
||||||
|
$.pushTriggerItem({
|
||||||
|
raw: member,
|
||||||
|
meta: {
|
||||||
|
internalId: member.email_id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.total_items > params.offset) {
|
||||||
|
hasMore = true;
|
||||||
|
} else {
|
||||||
|
hasMore = false;
|
||||||
|
}
|
||||||
|
} while (hasMore);
|
||||||
|
},
|
||||||
|
});
|
5
packages/backend/src/apps/mailchimp/triggers/index.js
Normal file
5
packages/backend/src/apps/mailchimp/triggers/index.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import emailOpened from './email-opened/index.js';
|
||||||
|
import newSubscribers from './new-subscribers/index.js';
|
||||||
|
import newUnsubscribers from './new-unsubscribers/index.js';
|
||||||
|
|
||||||
|
export default [emailOpened, newSubscribers, newUnsubscribers];
|
@@ -0,0 +1,105 @@
|
|||||||
|
import Crypto from 'crypto';
|
||||||
|
import defineTrigger from '../../../../helpers/define-trigger.js';
|
||||||
|
|
||||||
|
export default defineTrigger({
|
||||||
|
name: 'New subscribers',
|
||||||
|
key: 'newSubscribers',
|
||||||
|
type: 'webhook',
|
||||||
|
description: 'Triggers when a new subscriber is appended to an audience.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Audience',
|
||||||
|
key: 'audienceId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listAudiences',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const dataItem = {
|
||||||
|
raw: $.request.body,
|
||||||
|
meta: {
|
||||||
|
internalId: Crypto.randomUUID(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
$.pushTriggerItem(dataItem);
|
||||||
|
},
|
||||||
|
|
||||||
|
async testRun($) {
|
||||||
|
const audienceId = $.step.parameters.audienceId;
|
||||||
|
|
||||||
|
const computedWebhookEvent = {
|
||||||
|
data: {
|
||||||
|
id: Crypto.randomUUID(),
|
||||||
|
email: 'user@automatisch.io',
|
||||||
|
ip_opt: '127.0.0.1',
|
||||||
|
merges: {
|
||||||
|
EMAIL: 'user@automatisch.io',
|
||||||
|
FNAME: 'FNAME',
|
||||||
|
LNAME: 'LNAME',
|
||||||
|
PHONE: '',
|
||||||
|
ADDRESS: '',
|
||||||
|
BIRTHDAY: '',
|
||||||
|
},
|
||||||
|
web_id: Crypto.randomUUID(),
|
||||||
|
list_id: audienceId,
|
||||||
|
email_type: 'html',
|
||||||
|
},
|
||||||
|
type: 'subscribe',
|
||||||
|
fired_at: new Date().toLocaleString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataItem = {
|
||||||
|
raw: computedWebhookEvent,
|
||||||
|
meta: {
|
||||||
|
internalId: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
$.pushTriggerItem(dataItem);
|
||||||
|
},
|
||||||
|
|
||||||
|
async registerHook($) {
|
||||||
|
const audienceId = $.step.parameters.audienceId;
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
url: $.webhookUrl,
|
||||||
|
events: {
|
||||||
|
subscribe: true,
|
||||||
|
},
|
||||||
|
sources: {
|
||||||
|
user: true,
|
||||||
|
admin: true,
|
||||||
|
api: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await $.http.post(
|
||||||
|
`/3.0/lists/${audienceId}/webhooks`,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
|
||||||
|
await $.flow.setRemoteWebhookId(response.data.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
async unregisterHook($) {
|
||||||
|
const audienceId = $.step.parameters.audienceId;
|
||||||
|
|
||||||
|
await $.http.delete(
|
||||||
|
`/3.0/lists/${audienceId}/webhooks/${$.flow.remoteWebhookId}`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
@@ -0,0 +1,108 @@
|
|||||||
|
import Crypto from 'crypto';
|
||||||
|
import defineTrigger from '../../../../helpers/define-trigger.js';
|
||||||
|
|
||||||
|
export default defineTrigger({
|
||||||
|
name: 'New unsubscribers',
|
||||||
|
key: 'newUnsubscribers',
|
||||||
|
type: 'webhook',
|
||||||
|
description: 'Triggers when any existing subscriber opts out of an audience.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Audience',
|
||||||
|
key: 'audienceId',
|
||||||
|
type: 'dropdown',
|
||||||
|
required: true,
|
||||||
|
description: '',
|
||||||
|
variables: true,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listAudiences',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const dataItem = {
|
||||||
|
raw: $.request.body,
|
||||||
|
meta: {
|
||||||
|
internalId: Crypto.randomUUID(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
$.pushTriggerItem(dataItem);
|
||||||
|
},
|
||||||
|
|
||||||
|
async testRun($) {
|
||||||
|
const audienceId = $.step.parameters.audienceId;
|
||||||
|
|
||||||
|
const computedWebhookEvent = {
|
||||||
|
data: {
|
||||||
|
id: Crypto.randomUUID(),
|
||||||
|
email: 'user@automatisch.io',
|
||||||
|
action: 'unsub',
|
||||||
|
ip_opt: '127.0.0.1',
|
||||||
|
merges: {
|
||||||
|
EMAIL: 'user@automatisch.io',
|
||||||
|
FNAME: 'FNAME',
|
||||||
|
LNAME: 'LNAME',
|
||||||
|
PHONE: '',
|
||||||
|
ADDRESS: '',
|
||||||
|
BIRTHDAY: '',
|
||||||
|
},
|
||||||
|
reason: 'manual',
|
||||||
|
web_id: Crypto.randomUUID(),
|
||||||
|
list_id: audienceId,
|
||||||
|
email_type: 'html',
|
||||||
|
campaign_id: Crypto.randomUUID(),
|
||||||
|
},
|
||||||
|
type: 'unsubscribe',
|
||||||
|
fired_at: new Date().toLocaleString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataItem = {
|
||||||
|
raw: computedWebhookEvent,
|
||||||
|
meta: {
|
||||||
|
internalId: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
$.pushTriggerItem(dataItem);
|
||||||
|
},
|
||||||
|
|
||||||
|
async registerHook($) {
|
||||||
|
const audienceId = $.step.parameters.audienceId;
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
url: $.webhookUrl,
|
||||||
|
events: {
|
||||||
|
unsubscribe: true,
|
||||||
|
},
|
||||||
|
sources: {
|
||||||
|
user: true,
|
||||||
|
admin: true,
|
||||||
|
api: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await $.http.post(
|
||||||
|
`/3.0/lists/${audienceId}/webhooks`,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
|
||||||
|
await $.flow.setRemoteWebhookId(response.data.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
async unregisterHook($) {
|
||||||
|
const audienceId = $.step.parameters.audienceId;
|
||||||
|
|
||||||
|
await $.http.delete(
|
||||||
|
`/3.0/lists/${audienceId}/webhooks/${$.flow.remoteWebhookId}`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
1
packages/backend/src/apps/mailerlite/assets/favicon.svg
Normal file
1
packages/backend/src/apps/mailerlite/assets/favicon.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 9.0 KiB |
33
packages/backend/src/apps/mailerlite/auth/index.js
Normal file
33
packages/backend/src/apps/mailerlite/auth/index.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import verifyCredentials from './verify-credentials.js';
|
||||||
|
import isStillVerified from './is-still-verified.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'screenName',
|
||||||
|
label: 'Screen Name',
|
||||||
|
type: 'string',
|
||||||
|
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',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: 'MailerLite API key of your account.',
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
verifyCredentials,
|
||||||
|
isStillVerified,
|
||||||
|
};
|
@@ -0,0 +1,8 @@
|
|||||||
|
import verifyCredentials from './verify-credentials.js';
|
||||||
|
|
||||||
|
const isStillVerified = async ($) => {
|
||||||
|
await verifyCredentials($);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isStillVerified;
|
@@ -0,0 +1,10 @@
|
|||||||
|
const verifyCredentials = async ($) => {
|
||||||
|
await $.http.get('/campaigns');
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
screenName: $.auth.data.screenName,
|
||||||
|
apiKey: $.auth.data.apiKey,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default verifyCredentials;
|
@@ -0,0 +1,9 @@
|
|||||||
|
const addAuthHeader = ($, requestConfig) => {
|
||||||
|
if ($.auth.data?.apiKey) {
|
||||||
|
requestConfig.headers.Authorization = `Bearer ${$.auth.data.apiKey}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default addAuthHeader;
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user