Compare commits
71 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ff72b2ae35 | ||
![]() |
315edea5ca | ||
![]() |
27e0ef4e38 | ||
![]() |
8090d8be5a | ||
![]() |
8c8fd98bd5 | ||
![]() |
6f724b5bec | ||
![]() |
909cbd1f0c | ||
![]() |
094de0ca21 | ||
![]() |
af8ca4af9e | ||
![]() |
709e788f5f | ||
![]() |
1a4a1f7f8b | ||
![]() |
ff0bde059a | ||
![]() |
ac671110a3 | ||
![]() |
30c95da90e | ||
![]() |
0b52a1bd01 | ||
![]() |
3fc7fce9ca | ||
![]() |
f8df7987b8 | ||
![]() |
697594b63e | ||
![]() |
7be3923224 | ||
![]() |
5b913aa30a | ||
![]() |
da9cb7d6eb | ||
![]() |
aca19f7f73 | ||
![]() |
faf151cd62 | ||
![]() |
675cd4de2a | ||
![]() |
74ebe492c5 | ||
![]() |
254fcecf33 | ||
![]() |
a334864980 | ||
![]() |
b6b377c209 | ||
![]() |
5f76aa02dd | ||
![]() |
0ba0c998e9 | ||
![]() |
2e54216469 | ||
![]() |
56dbe62cad | ||
![]() |
048db60a10 | ||
![]() |
f3b1b2254f | ||
![]() |
e705aaa2f6 | ||
![]() |
1f80f843ba | ||
![]() |
02a76418fc | ||
![]() |
d1245705de | ||
![]() |
6d74f7c64d | ||
![]() |
bf1076b7d2 | ||
![]() |
7175d92eaf | ||
![]() |
f30ac46672 | ||
![]() |
0970db3295 | ||
![]() |
5d24216124 | ||
![]() |
9a55333ce8 | ||
![]() |
cb58ab5fb7 | ||
![]() |
5d9ed13003 | ||
![]() |
5b51cb30f5 | ||
![]() |
d8c5850eaa | ||
![]() |
4839b100cb | ||
![]() |
469050914b | ||
![]() |
8036d20bf9 | ||
![]() |
f04597335f | ||
![]() |
3d26ca6cc2 | ||
![]() |
ec3454291b | ||
![]() |
21e2f394a9 | ||
![]() |
dd9f69e4cd | ||
![]() |
f7e8cb24ee | ||
![]() |
3b5a77ec22 | ||
![]() |
16715129d1 | ||
![]() |
3aafaabda6 | ||
![]() |
279abfedfd | ||
![]() |
e77580dd35 | ||
![]() |
9cde662092 | ||
![]() |
862734c572 | ||
![]() |
61dc611588 | ||
![]() |
03368e2cca | ||
![]() |
6382325280 | ||
![]() |
a5b0910b60 | ||
![]() |
ba1e1ef20c | ||
![]() |
dd8bab299d |
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
There are other existing solutions in the market, like Zapier and Integromat, so you might be wondering why you should use Automatisch.
|
There are other existing solutions in the market, like Zapier and Integromat, so you might be wondering why you should use Automatisch.
|
||||||
|
|
||||||
✅ The most significant advantage of having Automatisch is keeping your data on your own servers. Not all companies want to use an automation service in the cloud, and the current open-source or self-hosted solutions mainly focus on developers rather than a user without a technical background.
|
✅ One of the main benefits of using Automatisch is that it allows you to store your data on your own servers, which is essential for businesses that handle sensitive user information and cannot risk sharing it with external cloud services. This is especially relevant for industries such as healthcare and finance, as well as for European companies that must adhere to the General Data Protection Regulation (GDPR).
|
||||||
|
|
||||||
🤓 Your contributions are vital to the development of Automatisch. As an open-source software, anyone can have an impact on how it is being developed.
|
🤓 Your contributions are vital to the development of Automatisch. As an open-source software, anyone can have an impact on how it is being developed.
|
||||||
|
|
||||||
@@ -35,6 +35,8 @@ docker compose up
|
|||||||
|
|
||||||
You can use `user@automatisch.io` email address and `sample` password to login to Automatisch. Please do not forget to change your email and password from the settings page.
|
You can use `user@automatisch.io` email address and `sample` password to login to Automatisch. Please do not forget to change your email and password from the settings page.
|
||||||
|
|
||||||
|
For other installation types, you can check the [installation](https://automatisch.io/docs/guide/installation) guide.
|
||||||
|
|
||||||
## Community Links
|
## Community Links
|
||||||
|
|
||||||
- [Discord](https://discord.gg/dJSah9CVrC)
|
- [Discord](https://discord.gg/dJSah9CVrC)
|
||||||
|
@@ -2,9 +2,13 @@
|
|||||||
FROM node:16-alpine
|
FROM node:16-alpine
|
||||||
WORKDIR /automatisch
|
WORKDIR /automatisch
|
||||||
|
|
||||||
|
RUN apk --no-cache add --virtual build-dependencies python3 build-base
|
||||||
|
|
||||||
COPY ./entrypoint.sh /entrypoint.sh
|
COPY ./entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
RUN yarn global add @automatisch/cli@0.2.0
|
RUN yarn global add @automatisch/cli@0.3.0
|
||||||
|
|
||||||
|
RUN apk del build-dependencies python3 build-base
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
ENTRYPOINT ["sh", "/entrypoint.sh"]
|
ENTRYPOINT ["sh", "/entrypoint.sh"]
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
FROM automatischio/automatisch:0.2.0
|
FROM automatischio/automatisch:0.3.0
|
||||||
WORKDIR /automatisch
|
WORKDIR /automatisch
|
||||||
|
|
||||||
RUN apk add --no-cache openssl dos2unix
|
RUN apk add --no-cache openssl dos2unix
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"version": "0.3.0",
|
"version": "0.4.0",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"command": {
|
"command": {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/backend",
|
"name": "@automatisch/backend",
|
||||||
"version": "0.3.0",
|
"version": "0.4.0",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
"prebuild": "rm -rf ./dist"
|
"prebuild": "rm -rf ./dist"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@automatisch/web": "^0.3.0",
|
"@automatisch/web": "^0.4.0",
|
||||||
"@bull-board/express": "^3.10.1",
|
"@bull-board/express": "^3.10.1",
|
||||||
"@graphql-tools/graphql-file-loader": "^7.3.4",
|
"@graphql-tools/graphql-file-loader": "^7.3.4",
|
||||||
"@graphql-tools/load": "^7.5.2",
|
"@graphql-tools/load": "^7.5.2",
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"debug": "~2.6.9",
|
"debug": "~2.6.9",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
"express": "~4.16.1",
|
"express": "~4.18.2",
|
||||||
"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",
|
||||||
@@ -46,11 +46,12 @@
|
|||||||
"graphql-tools": "^8.2.0",
|
"graphql-tools": "^8.2.0",
|
||||||
"graphql-type-json": "^0.3.2",
|
"graphql-type-json": "^0.3.2",
|
||||||
"http-errors": "~1.6.3",
|
"http-errors": "~1.6.3",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"knex": "^0.95.11",
|
"knex": "^0.95.11",
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"luxon": "2.3.1",
|
"luxon": "2.5.2",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
|
"multer": "1.4.5-lts.1",
|
||||||
"nodemailer": "6.7.0",
|
"nodemailer": "6.7.0",
|
||||||
"oauth-1.0a": "^2.2.6",
|
"oauth-1.0a": "^2.2.6",
|
||||||
"objection": "^3.0.0",
|
"objection": "^3.0.0",
|
||||||
@@ -93,16 +94,17 @@
|
|||||||
"url": "https://github.com/automatisch/automatisch/issues"
|
"url": "https://github.com/automatisch/automatisch/issues"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@automatisch/types": "^0.3.0",
|
"@automatisch/types": "^0.4.0",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/bull": "^3.15.8",
|
"@types/bull": "^3.15.8",
|
||||||
"@types/cors": "^2.8.12",
|
"@types/cors": "^2.8.12",
|
||||||
"@types/crypto-js": "^4.0.2",
|
"@types/crypto-js": "^4.0.2",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.15",
|
||||||
"@types/http-errors": "^1.8.1",
|
"@types/http-errors": "^1.8.1",
|
||||||
"@types/jsonwebtoken": "^8.5.8",
|
"@types/jsonwebtoken": "^8.5.8",
|
||||||
"@types/lodash.get": "^4.4.6",
|
"@types/lodash.get": "^4.4.6",
|
||||||
"@types/morgan": "^1.9.3",
|
"@types/morgan": "^1.9.3",
|
||||||
|
"@types/multer": "1.4.7",
|
||||||
"@types/node": "^16.10.2",
|
"@types/node": "^16.10.2",
|
||||||
"@types/nodemailer": "^6.4.4",
|
"@types/nodemailer": "^6.4.4",
|
||||||
"@types/pg": "^8.6.1",
|
"@types/pg": "^8.6.1",
|
||||||
|
@@ -26,12 +26,17 @@ appAssetsHandler(app);
|
|||||||
app.use(morgan);
|
app.use(morgan);
|
||||||
app.use(
|
app.use(
|
||||||
express.json({
|
express.json({
|
||||||
verify: (req, res, buf) => {
|
verify(req, res, buf) {
|
||||||
(req as IRequest).rawBody = buf;
|
(req as IRequest).rawBody = buf;
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
app.use(express.urlencoded({ extended: false }));
|
app.use(express.urlencoded({
|
||||||
|
extended: false,
|
||||||
|
verify(req, res, buf) {
|
||||||
|
(req as IRequest).rawBody = buf;
|
||||||
|
},
|
||||||
|
}));
|
||||||
app.use(cors(corsOptions));
|
app.use(cors(corsOptions));
|
||||||
app.use('/', router);
|
app.use('/', router);
|
||||||
|
|
||||||
|
56
packages/backend/src/apps/delay/actions/delay-for/index.ts
Normal file
56
packages/backend/src/apps/delay/actions/delay-for/index.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import defineAction from '../../../../helpers/define-action';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Delay For',
|
||||||
|
key: 'delayFor',
|
||||||
|
description:
|
||||||
|
'Delays the execution of the next action by a specified amount of time.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Delay for unit',
|
||||||
|
key: 'delayForUnit',
|
||||||
|
type: 'dropdown' as const,
|
||||||
|
required: true,
|
||||||
|
value: null,
|
||||||
|
description: 'Delay for unit, e.g. minutes, hours, days, weeks.',
|
||||||
|
variables: false,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'Minutes',
|
||||||
|
value: 'minutes',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Hours',
|
||||||
|
value: 'hours',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Days',
|
||||||
|
value: 'days',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Weeks',
|
||||||
|
value: 'weeks',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Delay for value',
|
||||||
|
key: 'delayForValue',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
description: 'Delay for value, use a number, e.g. 1, 2, 3.',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const { delayForUnit, delayForValue } = $.step.parameters;
|
||||||
|
|
||||||
|
const dataItem = {
|
||||||
|
delayForUnit,
|
||||||
|
delayForValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
$.setActionItem({ raw: dataItem });
|
||||||
|
},
|
||||||
|
});
|
28
packages/backend/src/apps/delay/actions/delay-until/index.ts
Normal file
28
packages/backend/src/apps/delay/actions/delay-until/index.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import defineAction from '../../../../helpers/define-action';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Delay Until',
|
||||||
|
key: 'delayUntil',
|
||||||
|
description:
|
||||||
|
'Delays the execution of the next action until a specified date.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Delay until (Date)',
|
||||||
|
key: 'delayUntil',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
description: 'Delay until the date. E.g. 2022-12-18',
|
||||||
|
variables: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const { delayUntil } = $.step.parameters;
|
||||||
|
|
||||||
|
const dataItem = {
|
||||||
|
delayUntil,
|
||||||
|
};
|
||||||
|
|
||||||
|
$.setActionItem({ raw: dataItem });
|
||||||
|
},
|
||||||
|
});
|
4
packages/backend/src/apps/delay/actions/index.ts
Normal file
4
packages/backend/src/apps/delay/actions/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import delayFor from './delay-for';
|
||||||
|
import delayUntil from './delay-until';
|
||||||
|
|
||||||
|
export default [delayFor, delayUntil];
|
7
packages/backend/src/apps/delay/assets/favicon.svg
Normal file
7
packages/backend/src/apps/delay/assets/favicon.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 122.88 100.6" style="enable-background:new 0 0 122.88 100.6" xml:space="preserve">
|
||||||
|
<style type="text/css">.st0{fill:#272727;} .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#000000;}</style>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M72.58,0c6.8,0,13.3,1.36,19.23,3.81c6.16,2.55,11.7,6.29,16.33,10.92l0,0c4.63,4.63,8.37,10.17,10.92,16.34 c2.46,5.93,3.81,12.43,3.81,19.23c0,6.8-1.36,13.3-3.81,19.23c-2.55,6.16-6.29,11.7-10.92,16.33l0,0 c-4.63,4.63-10.17,8.37-16.34,10.92c-5.93,2.46-12.43,3.81-19.23,3.81c-6.8,0-13.3-1.36-19.23-3.81 c-6.15-2.55-11.69-6.28-16.33-10.92l-0.01-0.01c-4.64-4.64-8.37-10.17-10.92-16.33c-0.79-1.91-1.47-3.87-2.02-5.89 c1.05,0.1,2.12,0.15,3.2,0.15c2.05,0,4.05-0.19,6-0.54c0.32,0.97,0.67,1.93,1.06,2.87c2.09,5.05,5.17,9.6,8.99,13.43 c3.82,3.82,8.38,6.9,13.43,8.99c4.87,2.02,10.21,3.13,15.83,3.13c5.62,0,10.96-1.11,15.83-3.13c5.05-2.09,9.6-5.17,13.43-8.99 c3.82-3.82,6.9-8.38,8.99-13.43c2.02-4.87,3.13-10.21,3.13-15.83c0-5.62-1.11-10.96-3.13-15.83c-2.09-5.05-5.17-9.6-8.99-13.43 c-3.82-3.82-8.38-6.9-13.43-8.99c-4.87-2.02-10.21-3.13-15.83-3.13c-5.62,0-10.96,1.11-15.83,3.13c-0.44,0.18-0.87,0.37-1.3,0.56 c-1.65-2.61-3.66-4.97-5.95-7.02c1.25-0.65,2.53-1.24,3.84-1.79C59.28,1.36,65.78,0,72.58,0L72.58,0z M66.8,26.39 c0-1.23,0.5-2.35,1.31-3.16c0.81-0.81,1.93-1.31,3.16-1.31c1.23,0,2.35,0.5,3.16,1.31c0.81,0.81,1.31,1.93,1.31,3.16v23.47 l17.54,10.4c1.05,0.62,1.76,1.62,2.05,2.73c0.28,1.1,0.15,2.31-0.47,3.37l0,0.01l0,0c-0.62,1.05-1.62,1.76-2.73,2.05 c-1.1,0.28-2.31,0.15-3.37-0.47l-0.01,0l0,0L69.1,56.29c-0.67-0.38-1.24-0.92-1.64-1.57c-0.42-0.68-0.66-1.48-0.66-2.32V26.39 L66.8,26.39z"/>
|
||||||
|
<path class="st1" d="M27.27,3.18c15.06,0,27.27,12.21,27.27,27.27c0,15.06-12.21,27.27-27.27,27.27C12.21,57.73,0,45.52,0,30.45 C0,15.39,12.21,3.18,27.27,3.18L27.27,3.18z M24.35,41.34h5.82v5.16h-5.82V41.34L24.35,41.34L24.35,41.34z M30.17,37.77h-5.82 c-0.58-7.07-1.8-11.56-1.8-18.63c0-2.61,2.12-4.72,4.72-4.72c2.61,0,4.72,2.12,4.72,4.72C32,26.2,30.76,30.7,30.17,37.77 L30.17,37.77L30.17,37.77L30.17,37.77z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
0
packages/backend/src/apps/delay/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/delay/index.d.ts
vendored
Normal file
14
packages/backend/src/apps/delay/index.ts
Normal file
14
packages/backend/src/apps/delay/index.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import defineApp from '../../helpers/define-app';
|
||||||
|
import actions from './actions';
|
||||||
|
|
||||||
|
export default defineApp({
|
||||||
|
name: 'Delay',
|
||||||
|
key: 'delay',
|
||||||
|
iconUrl: '{BASE_URL}/apps/delay/assets/favicon.svg',
|
||||||
|
authDocUrl: 'https://automatisch.io/docs/apps/delay/connection',
|
||||||
|
supportsConnections: false,
|
||||||
|
baseUrl: '',
|
||||||
|
apiBaseUrl: '',
|
||||||
|
primaryColor: '001F52',
|
||||||
|
actions,
|
||||||
|
});
|
@@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="762.564" height="769.312" viewBox="0 0 772772" fill="none">
|
||||||
|
<g clip-path="url(#clip0_493_7535)">
|
||||||
|
<path d="M747.782 474.701C725.051 423.2 683.614 383.657 631.048 363.294L413.561 279.117L429.189 369.569L602.633 436.697C670.591 462.98 704.451 539.58 678.168 607.537C651.885 675.494 575.285 709.354 507.328 683.071C473.231 669.811 446.711 643.883 432.741 610.023C429.426 601.854 426.939 593.33 425.164 584.569L358.509 636.07C358.982 637.372 359.338 638.674 359.811 639.858C382.069 693.963 424.216 735.282 478.795 756.356C503.776 765.946 529.349 770.563 554.566 770.563C639.098 770.563 719.131 719.299 751.334 635.833C771.697 583.267 770.277 526.083 747.545 474.583L747.782 474.701Z" fill="#02AFC7"/>
|
||||||
|
<path d="M440.91 419.881L294.459 533.064C287.119 538.628 279.187 543.482 270.781 547.507C237.631 563.254 200.693 564.556 166.477 551.414C133.564 538.628 107.636 513.884 93.3109 481.563C78.9854 449.242 78.1567 413.369 90.943 380.456C103.729 347.543 128.473 321.615 160.794 307.29C190.866 294.03 224.134 292.491 255.271 302.672L241.064 219.206C147.061 207.13 53.2943 259.815 17.6583 352.042C-2.70517 404.608 -1.28446 461.792 21.4468 513.292C44.1781 564.793 85.6154 604.336 138.181 624.699C162.57 634.171 188.38 638.788 214.189 638.788C245.208 638.788 276.227 632.039 304.641 618.543C318.019 612.268 330.687 604.454 342.29 595.575L527.81 452.083L440.91 419.762V419.881Z" fill="#31DEA4"/>
|
||||||
|
<path d="M624.42 193.396C629.629 229.624 619.921 265.379 597.19 294.148C594.822 297.108 592.335 299.831 589.731 302.554L665.62 333.454C696.402 289.649 709.899 235.307 702.203 182.149C694.153 126.386 665.028 77.135 619.921 43.3933C574.932 9.76989 519.406 -4.43717 463.643 3.49511C407.88 11.5458 358.629 40.6703 324.888 85.7777C291.146 130.885 276.939 186.411 284.989 242.055L323.23 457.529L393.673 404.371L362.773 230.334C357.919 195.646 366.798 161.075 387.872 133.016C409.064 104.72 439.846 86.488 474.89 81.3972C509.935 76.4247 544.623 85.3041 572.801 106.378C601.096 127.57 619.329 158.47 624.42 193.396V193.396Z" fill="#FF5986"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_493_7535">
|
||||||
|
<rect width="762.564" height="769.312" fill="white" transform="translate(3.33301 1.36719)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
43
packages/backend/src/apps/flowers-software/auth/index.ts
Normal file
43
packages/backend/src/apps/flowers-software/auth/index.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import verifyCredentials from './verify-credentials';
|
||||||
|
import isStillVerified from './is-still-verified';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'username',
|
||||||
|
label: 'Username',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'password',
|
||||||
|
label: 'Password',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'apiKey',
|
||||||
|
label: 'API Key',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
verifyCredentials,
|
||||||
|
isStillVerified,
|
||||||
|
};
|
@@ -0,0 +1,10 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
import verifyCredentials from './verify-credentials';
|
||||||
|
|
||||||
|
const isStillVerified = async ($: IGlobalVariable) => {
|
||||||
|
await verifyCredentials($);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isStillVerified;
|
@@ -0,0 +1,20 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
import getWebhooks from '../common/get-webhooks';
|
||||||
|
|
||||||
|
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||||
|
const response = await getWebhooks($);
|
||||||
|
const successful = Array.isArray(response.data);
|
||||||
|
|
||||||
|
if (!successful) {
|
||||||
|
throw new Error('Failed while authorizing!');
|
||||||
|
}
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
screenName: $.auth.data.username,
|
||||||
|
username: $.auth.data.username,
|
||||||
|
password: $.auth.data.password,
|
||||||
|
apiKey: $.auth.data.apiKey,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default verifyCredentials;
|
@@ -0,0 +1,18 @@
|
|||||||
|
import { TBeforeRequest } from '@automatisch/types';
|
||||||
|
|
||||||
|
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||||
|
const { data } = $.auth;
|
||||||
|
|
||||||
|
if (data?.username && data.password && data.apiKey) {
|
||||||
|
requestConfig.headers['x-api-key'] = data.apiKey as string;
|
||||||
|
|
||||||
|
requestConfig.auth = {
|
||||||
|
username: data.username as string,
|
||||||
|
password: data.password as string,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default addAuthHeader;
|
@@ -0,0 +1,5 @@
|
|||||||
|
import type { IGlobalVariable } from "@automatisch/types";
|
||||||
|
|
||||||
|
export default async function getWebhooks($: IGlobalVariable) {
|
||||||
|
return await $.http.get('/v2/public/api/webhooks');
|
||||||
|
}
|
@@ -0,0 +1,488 @@
|
|||||||
|
const webhookFilters = [
|
||||||
|
{
|
||||||
|
label: "Contact Company Created",
|
||||||
|
value: "CONTACT_COMPANY_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Contact Company Deleted",
|
||||||
|
value: "CONTACT_COMPANY_DELETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Contact Company Updated",
|
||||||
|
value: "CONTACT_COMPANY_UPDATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Contact Created",
|
||||||
|
value: "CONTACT_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Contact Deleted",
|
||||||
|
value: "CONTACT_DELETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Contact Updated",
|
||||||
|
value: "CONTACT_UPDATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Customer Created",
|
||||||
|
value: "CUSTOMER_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Customer Updated",
|
||||||
|
value: "CUSTOMER_UPDATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Document Deleted",
|
||||||
|
value: "DOCUMENT_DELETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Document Downloaded",
|
||||||
|
value: "DOCUMENT_DOWNLOADED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Document Saved",
|
||||||
|
value: "DOCUMENT_SAVED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Document Updated",
|
||||||
|
value: "DOCUMENT_UPDATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Archived",
|
||||||
|
value: "FLOW_ARCHIVED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Created",
|
||||||
|
value: "FLOW_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Automation Action Created",
|
||||||
|
value: "FLOW_OBJECT_AUTOMATION_ACTION_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Automation Action Deleted",
|
||||||
|
value: "FLOW_OBJECT_AUTOMATION_ACTION_DELETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Automation Created",
|
||||||
|
value: "FLOW_OBJECT_AUTOMATION_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Automation Deleted",
|
||||||
|
value: "FLOW_OBJECT_AUTOMATION_DELETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Automation Updated",
|
||||||
|
value: "FLOW_OBJECT_AUTOMATION_UPDATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Automation Webdav Created",
|
||||||
|
value: "FLOW_OBJECT_AUTOMATION_WEBDAV_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Automation Webdav Deleted",
|
||||||
|
value: "FLOW_OBJECT_AUTOMATION_WEBDAV_DELETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Automation Webdav Updated",
|
||||||
|
value: "FLOW_OBJECT_AUTOMATION_WEBDAV_UPDATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Created",
|
||||||
|
value: "FLOW_OBJECT_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Deleted",
|
||||||
|
value: "FLOW_OBJECT_DELETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Document Added",
|
||||||
|
value: "FLOW_OBJECT_DOCUMENT_ADDED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Document Removed",
|
||||||
|
value: "FLOW_OBJECT_DOCUMENT_REMOVED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Resource Created",
|
||||||
|
value: "FLOW_OBJECT_RESOURCE_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Resource Deleted",
|
||||||
|
value: "FLOW_OBJECT_RESOURCE_DELETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Resource Updated",
|
||||||
|
value: "FLOW_OBJECT_RESOURCE_UPDATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Task Condition Created",
|
||||||
|
value: "FLOW_OBJECT_TASK_CONDITION_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Task Condition Deleted",
|
||||||
|
value: "FLOW_OBJECT_TASK_CONDITION_DELETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Task Condition Updated",
|
||||||
|
value: "FLOW_OBJECT_TASK_CONDITION_UPDATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Task Created",
|
||||||
|
value: "FLOW_OBJECT_TASK_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Task Deleted",
|
||||||
|
value: "FLOW_OBJECT_TASK_DELETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Task Updated",
|
||||||
|
value: "FLOW_OBJECT_TASK_UPDATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Object Updated",
|
||||||
|
value: "FLOW_OBJECT_UPDATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Objects Connection Created",
|
||||||
|
value: "FLOW_OBJECTS_CONNECTION_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Objects Connection Deleted",
|
||||||
|
value: "FLOW_OBJECTS_CONNECTION_DELETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Objects Connection Updated",
|
||||||
|
value: "FLOW_OBJECTS_CONNECTION_UPDATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Objects External Connection Created",
|
||||||
|
value: "FLOW_OBJECTS_EXTERNAL_CONNECTION_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Objects External Connection Deleted",
|
||||||
|
value: "FLOW_OBJECTS_EXTERNAL_CONNECTION_DELETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Objects External Connection Updated",
|
||||||
|
value: "FLOW_OBJECTS_EXTERNAL_CONNECTION_UPDATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Objects External Connections Group Created",
|
||||||
|
value: "FLOW_OBJECTS_EXTERNAL_CONNECTIONS_GROUP_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Objects External Connections Group Deleted",
|
||||||
|
value: "FLOW_OBJECTS_EXTERNAL_CONNECTIONS_GROUP_DELETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Objects External Connections Group Updated",
|
||||||
|
value: "FLOW_OBJECTS_EXTERNAL_CONNECTIONS_GROUP_UPDATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Unarchived",
|
||||||
|
value: "FLOW_UNARCHIVED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Flow Updated",
|
||||||
|
value: "FLOW_UPDATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Note Created",
|
||||||
|
value: "NOTE_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Note Deleted",
|
||||||
|
value: "NOTE_DELETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Note Updated",
|
||||||
|
value: "NOTE_UPDATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Team Created",
|
||||||
|
value: "TEAM_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Team Deleted",
|
||||||
|
value: "TEAM_DELETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Team Updated",
|
||||||
|
value: "TEAM_UPDATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "User Added To Team",
|
||||||
|
value: "USER_ADDED_TO_TEAM"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "User Added To Teamleads",
|
||||||
|
value: "USER_ADDED_TO_TEAMLEADS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "User Archived",
|
||||||
|
value: "USER_ARCHIVED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "User Changed Password",
|
||||||
|
value: "USER_CHANGED_PASSWORD"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "User Created",
|
||||||
|
value: "USER_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "User Forgot Password",
|
||||||
|
value: "USER_FORGOT_PASSWORD"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "User Invited",
|
||||||
|
value: "USER_INVITED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "User Logged In",
|
||||||
|
value: "USER_LOGGED_IN"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "User Notification Settings Changed",
|
||||||
|
value: "USER_NOTIFICATION_SETTINGS_CHANGED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "User Profile Updated",
|
||||||
|
value: "USER_PROFILE_UPDATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "User Removed From Team",
|
||||||
|
value: "USER_REMOVED_FROM_TEAM"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "User Removed From Teamleads",
|
||||||
|
value: "USER_REMOVED_FROM_TEAMLEADS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "User Unarchived",
|
||||||
|
value: "USER_UNARCHIVED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Archived",
|
||||||
|
value: "WORKFLOW_ARCHIVED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Completed",
|
||||||
|
value: "WORKFLOW_COMPLETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Created",
|
||||||
|
value: "WORKFLOW_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Creation Failed",
|
||||||
|
value: "WORKFLOW_CREATION_FAILED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Api Get Completed",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_API_GET_COMPLETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Api Get Failed",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_API_GET_FAILED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Api Post Completed",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_API_POST_COMPLETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Api Post Failed",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_API_POST_FAILED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Datev Completed",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_DATEV_COMPLETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Datev Failed",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_DATEV_FAILED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Email Completed",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_EMAIL_COMPLETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Email Failed",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_EMAIL_FAILED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Lexoffice Completed",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_LEXOFFICE_COMPLETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Lexoffice Failed",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_LEXOFFICE_FAILED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Rejected",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_REJECTED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Retried",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_RETRIED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Sevdesk Completed",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_SEVDESK_COMPLETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Sevdesk Failed",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_SEVDESK_FAILED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Stamp Completed",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_STAMP_COMPLETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Stamp Failed",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_STAMP_FAILED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Task Completed",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_TASK_COMPLETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Task Failed",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_TASK_FAILED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Template Completed",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_TEMPLATE_COMPLETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Template Failed",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_TEMPLATE_FAILED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Webdav Document Uploaded",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_WEBDAV_DOCUMENT_UPLOADED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Zapier Completed",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_ZAPIER_COMPLETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Automation Zapier Failed",
|
||||||
|
value: "WORKFLOW_OBJECT_AUTOMATION_ZAPIER_FAILED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Combination Task Group Created",
|
||||||
|
value: "WORKFLOW_OBJECT_COMBINATION_TASK_GROUP_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Combination Task Group Deleted",
|
||||||
|
value: "WORKFLOW_OBJECT_COMBINATION_TASK_GROUP_DELETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Completed Automations Finished",
|
||||||
|
value: "WORKFLOW_OBJECT_COMPLETED_AUTOMATIONS_FINISHED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Completed",
|
||||||
|
value: "WORKFLOW_OBJECT_COMPLETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Created",
|
||||||
|
value: "WORKFLOW_OBJECT_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Document Added",
|
||||||
|
value: "WORKFLOW_OBJECT_DOCUMENT_ADDED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Document Lock Added",
|
||||||
|
value: "WORKFLOW_OBJECT_DOCUMENT_LOCK_ADDED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Document Lock Deleted",
|
||||||
|
value: "WORKFLOW_OBJECT_DOCUMENT_LOCK_DELETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Document Removed",
|
||||||
|
value: "WORKFLOW_OBJECT_DOCUMENT_REMOVED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Email Added",
|
||||||
|
value: "WORKFLOW_OBJECT_EMAIL_ADDED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Email Removed",
|
||||||
|
value: "WORKFLOW_OBJECT_EMAIL_REMOVED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object External User Created",
|
||||||
|
value: "WORKFLOW_OBJECT_EXTERNAL_USER_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object External User Deleted",
|
||||||
|
value: "WORKFLOW_OBJECT_EXTERNAL_USER_DELETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Note Added",
|
||||||
|
value: "WORKFLOW_OBJECT_NOTE_ADDED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Note Removed",
|
||||||
|
value: "WORKFLOW_OBJECT_NOTE_REMOVED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Resource Created",
|
||||||
|
value: "WORKFLOW_OBJECT_RESOURCE_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Snapshot Created",
|
||||||
|
value: "WORKFLOW_OBJECT_SNAPSHOT_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Task Condition Created",
|
||||||
|
value: "WORKFLOW_OBJECT_TASK_CONDITION_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Task Created",
|
||||||
|
value: "WORKFLOW_OBJECT_TASK_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Task Deleted",
|
||||||
|
value: "WORKFLOW_OBJECT_TASK_DELETED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Task Snapshot Created",
|
||||||
|
value: "WORKFLOW_OBJECT_TASK_SNAPSHOT_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Task Updated",
|
||||||
|
value: "WORKFLOW_OBJECT_TASK_UPDATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Object Updated",
|
||||||
|
value: "WORKFLOW_OBJECT_UPDATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Objects Connection Created",
|
||||||
|
value: "WORKFLOW_OBJECTS_CONNECTION_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Objects External Connection Created",
|
||||||
|
value: "WORKFLOW_OBJECTS_EXTERNAL_CONNECTION_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Objects External Connection Group Created",
|
||||||
|
value: "WORKFLOW_OBJECTS_EXTERNAL_CONNECTION_GROUP_CREATED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Unarchived",
|
||||||
|
value: "WORKFLOW_UNARCHIVED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Workflow Updated",
|
||||||
|
value: "WORKFLOW_UPDATED"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default webhookFilters;
|
0
packages/backend/src/apps/flowers-software/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/flowers-software/index.d.ts
vendored
Normal file
18
packages/backend/src/apps/flowers-software/index.ts
Normal file
18
packages/backend/src/apps/flowers-software/index.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import defineApp from '../../helpers/define-app';
|
||||||
|
import addAuthHeader from './common/add-auth-header';
|
||||||
|
import auth from './auth';
|
||||||
|
import triggers from './triggers';
|
||||||
|
|
||||||
|
export default defineApp({
|
||||||
|
name: 'Flowers Software',
|
||||||
|
key: 'flowers-software',
|
||||||
|
iconUrl: '{BASE_URL}/apps/flowers-software/assets/favicon.svg',
|
||||||
|
authDocUrl: 'https://automatisch.io/docs/apps/flowers-software/connection',
|
||||||
|
supportsConnections: true,
|
||||||
|
baseUrl: 'https://flowers-software.com',
|
||||||
|
apiBaseUrl: 'https://webapp.flowers-software.com/api',
|
||||||
|
primaryColor: '02AFC7',
|
||||||
|
beforeRequest: [addAuthHeader],
|
||||||
|
auth,
|
||||||
|
triggers,
|
||||||
|
});
|
@@ -0,0 +1,3 @@
|
|||||||
|
import newActivity from './new-activity';
|
||||||
|
|
||||||
|
export default [newActivity];
|
@@ -0,0 +1,54 @@
|
|||||||
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
import defineTrigger from '../../../../helpers/define-trigger';
|
||||||
|
import webhookFilters from '../../common/webhook-filters';
|
||||||
|
|
||||||
|
export default defineTrigger({
|
||||||
|
name: 'New activity',
|
||||||
|
key: 'newActivity',
|
||||||
|
type: 'webhook',
|
||||||
|
description: 'Triggers when a new activity occurs.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Activity type',
|
||||||
|
key: 'filters',
|
||||||
|
type: 'dropdown' as const,
|
||||||
|
required: true,
|
||||||
|
description: 'Pick an activity type to receive events for.',
|
||||||
|
variables: false,
|
||||||
|
options: webhookFilters,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async testRun($) {
|
||||||
|
if (!isEmpty($.lastExecutionStep?.dataOut)) {
|
||||||
|
$.pushTriggerItem({
|
||||||
|
raw: $.lastExecutionStep.dataOut,
|
||||||
|
meta: {
|
||||||
|
internalId: '',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async registerHook($) {
|
||||||
|
const payload = {
|
||||||
|
name: $.flow.id,
|
||||||
|
type: 'POST',
|
||||||
|
url: $.webhookUrl,
|
||||||
|
filters: [$.step.parameters.filters]
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await $.http.post(
|
||||||
|
`/v2/public/api/webhooks`,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
|
||||||
|
await $.flow.setRemoteWebhookId(data.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
async unregisterHook($) {
|
||||||
|
await $.http.delete(
|
||||||
|
`/v2/public/api/webhooks/${$.flow.remoteWebhookId}`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
@@ -13,9 +13,8 @@ export default async function generateAuthUrl($: IGlobalVariable) {
|
|||||||
scope: scopes.join(','),
|
scope: scopes.join(','),
|
||||||
});
|
});
|
||||||
|
|
||||||
const url = `${
|
const url = `${$.app.baseUrl
|
||||||
$.app.baseUrl
|
}/login/oauth/authorize?${searchParams.toString()}`;
|
||||||
}/login/oauth/authorize?${searchParams.toString()}`;
|
|
||||||
|
|
||||||
await $.auth.set({
|
await $.auth.set({
|
||||||
url,
|
url,
|
||||||
|
41
packages/backend/src/apps/google-forms/assets/favicon.svg
Normal file
41
packages/backend/src/apps/google-forms/assets/favicon.svg
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 1904.8 2500" style="enable-background:new 0 0 1904.8 2500;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#673AB7;}
|
||||||
|
.st1{fill:#F1F1F1;}
|
||||||
|
.st2{fill:url(#SVGID_1_);}
|
||||||
|
.st3{fill:#B39DDB;}
|
||||||
|
.st4{fill:#FFFFFF;fill-opacity:0.2;}
|
||||||
|
.st5{fill:#311B92;fill-opacity:0.2;}
|
||||||
|
.st6{fill:#311B92;fill-opacity:0.1;}
|
||||||
|
.st7{fill:url(#SVGID_2_);}
|
||||||
|
</style>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M1190.5,0H178.6C83.3,0,0,83.3,0,178.6v2142.9c0,95.2,83.3,178.6,178.6,178.6h1547.6
|
||||||
|
c95.2,0,178.6-83.3,178.6-178.6V714.3l-416.7-297.6L1190.5,0z"/>
|
||||||
|
<path class="st1" d="M714.3,1845.2h714.3v-119H714.3V1845.2z M714.3,1071.4v119h714.3v-119H714.3z M607.1,1131
|
||||||
|
c0,47.6-35.7,95.2-95.2,95.2s-95.2-35.7-95.2-95.2c0-59.5,35.7-95.2,95.2-95.2S607.1,1083.3,607.1,1131z M607.1,1464.3
|
||||||
|
c0,47.6-35.7,95.2-95.2,95.2s-95.2-35.7-95.2-95.2c0-59.5,35.7-95.2,95.2-95.2S607.1,1416.7,607.1,1464.3z M607.1,1785.7
|
||||||
|
c0,47.6-35.7,95.2-95.2,95.2s-95.2-35.7-95.2-95.2c0-59.5,35.7-95.2,95.2-95.2S607.1,1738.1,607.1,1785.7z M714.3,1547.6h714.3
|
||||||
|
v-119.1H714.3L714.3,1547.6L714.3,1547.6z"/>
|
||||||
|
|
||||||
|
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="122.5479" y1="1635.2429" x2="122.5479" y2="1634.3186" gradientTransform="matrix(666.67 0 0 -654.7559 -80127.6016 1071403.75)">
|
||||||
|
<stop offset="0" style="stop-color:#311B92;stop-opacity:0.2"/>
|
||||||
|
<stop offset="1" style="stop-color:#311B92;stop-opacity:2.000000e-02"/>
|
||||||
|
</linearGradient>
|
||||||
|
<path class="st2" d="M1238.1,666.7l666.7,654.8V714.3L1238.1,666.7z"/>
|
||||||
|
<path class="st3" d="M1190.5,0v535.7c0,95.2,83.3,178.6,178.6,178.6h535.7L1190.5,0z"/>
|
||||||
|
<path class="st4" d="M178.6,0C83.3,0,0,83.3,0,178.6v11.9C0,95.2,83.3,11.9,178.6,11.9h1011.9V0H178.6L178.6,0z"/>
|
||||||
|
<path class="st5" d="M1726.2,2488.1H178.6C83.3,2488.1,0,2404.8,0,2309.5v11.9c0,95.2,83.3,178.6,178.6,178.6h1547.6
|
||||||
|
c95.2,0,178.6-83.3,178.6-178.6v-11.9C1904.8,2404.8,1821.4,2488.1,1726.2,2488.1z"/>
|
||||||
|
<path class="st6" d="M1369,714.3c-95.2,0-178.6-83.3-178.6-178.6v11.9c0,95.2,83.3,178.6,178.6,178.6h535.7v-11.9L1369,714.3
|
||||||
|
L1369,714.3z"/>
|
||||||
|
|
||||||
|
<radialGradient id="SVGID_2_" cx="122.6324" cy="1634.4275" r="12.899" gradientTransform="matrix(1904.7655 0 0 -1904.75 -233525.7031 3113242.5)" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.1"/>
|
||||||
|
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
|
||||||
|
</radialGradient>
|
||||||
|
<path class="st7" d="M1190.5,0H178.6C83.3,0,0,83.3,0,178.6v2142.9c0,95.2,83.3,178.6,178.6,178.6h1547.6
|
||||||
|
c95.2,0,178.6-83.3,178.6-178.6V714.3L1190.5,0z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,24 @@
|
|||||||
|
import { IField, IGlobalVariable } from '@automatisch/types';
|
||||||
|
import { URLSearchParams } from 'url';
|
||||||
|
import authScope from '../common/auth-scope';
|
||||||
|
|
||||||
|
export default async function generateAuthUrl($: IGlobalVariable) {
|
||||||
|
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||||
|
(field: IField) => field.key == 'oAuthRedirectUrl'
|
||||||
|
);
|
||||||
|
const redirectUri = oauthRedirectUrlField.value as string;
|
||||||
|
const searchParams = new URLSearchParams({
|
||||||
|
client_id: $.auth.data.clientId as string,
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
prompt: 'select_account',
|
||||||
|
scope: authScope.join(' '),
|
||||||
|
response_type: 'code',
|
||||||
|
access_type: 'offline',
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = `https://accounts.google.com/o/oauth2/v2/auth?${searchParams.toString()}`;
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
url,
|
||||||
|
});
|
||||||
|
}
|
48
packages/backend/src/apps/google-forms/auth/index.ts
Normal file
48
packages/backend/src/apps/google-forms/auth/index.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import generateAuthUrl from './generate-auth-url';
|
||||||
|
import verifyCredentials from './verify-credentials';
|
||||||
|
import refreshToken from './refresh-token';
|
||||||
|
import isStillVerified from './is-still-verified';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'oAuthRedirectUrl',
|
||||||
|
label: 'OAuth Redirect URL',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
readOnly: true,
|
||||||
|
value: '{WEB_APP_URL}/app/google-forms/connections/add',
|
||||||
|
placeholder: null,
|
||||||
|
description:
|
||||||
|
'When asked to input a redirect URL in Google Cloud, enter the URL above.',
|
||||||
|
clickToCopy: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'clientId',
|
||||||
|
label: 'Client ID',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'clientSecret',
|
||||||
|
label: 'Client Secret',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
generateAuthUrl,
|
||||||
|
verifyCredentials,
|
||||||
|
isStillVerified,
|
||||||
|
refreshToken,
|
||||||
|
};
|
@@ -0,0 +1,9 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
import getCurrentUser from '../common/get-current-user';
|
||||||
|
|
||||||
|
const isStillVerified = async ($: IGlobalVariable) => {
|
||||||
|
const currentUser = await getCurrentUser($);
|
||||||
|
return !!currentUser.resourceName;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isStillVerified;
|
26
packages/backend/src/apps/google-forms/auth/refresh-token.ts
Normal file
26
packages/backend/src/apps/google-forms/auth/refresh-token.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { URLSearchParams } from 'node:url';
|
||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
import authScope from '../common/auth-scope';
|
||||||
|
|
||||||
|
const refreshToken = async ($: IGlobalVariable) => {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
client_id: $.auth.data.clientId as string,
|
||||||
|
client_secret: $.auth.data.clientSecret as string,
|
||||||
|
grant_type: 'refresh_token',
|
||||||
|
refresh_token: $.auth.data.refreshToken as string,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data } = await $.http.post(
|
||||||
|
'https://oauth2.googleapis.com/token',
|
||||||
|
params.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
accessToken: data.access_token,
|
||||||
|
expiresIn: data.expires_in,
|
||||||
|
scope: authScope.join(' '),
|
||||||
|
tokenType: data.token_type,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default refreshToken;
|
@@ -0,0 +1,57 @@
|
|||||||
|
import { IField, IGlobalVariable } from '@automatisch/types';
|
||||||
|
import getCurrentUser from '../common/get-current-user';
|
||||||
|
|
||||||
|
type TUser = {
|
||||||
|
displayName: string;
|
||||||
|
metadata: {
|
||||||
|
primary: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type TEmailAddress = {
|
||||||
|
value: string;
|
||||||
|
metadata: {
|
||||||
|
primary: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||||
|
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||||
|
(field: IField) => field.key == 'oAuthRedirectUrl'
|
||||||
|
);
|
||||||
|
const redirectUri = oauthRedirectUrlField.value as string;
|
||||||
|
const { data } = await $.http.post(`https://oauth2.googleapis.com/token`, {
|
||||||
|
client_id: $.auth.data.clientId,
|
||||||
|
client_secret: $.auth.data.clientSecret,
|
||||||
|
code: $.auth.data.code,
|
||||||
|
grant_type: 'authorization_code',
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
});
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
accessToken: data.access_token,
|
||||||
|
tokenType: data.token_type,
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentUser = await getCurrentUser($);
|
||||||
|
|
||||||
|
const { displayName } = currentUser.names.find(
|
||||||
|
(name: TUser) => name.metadata.primary
|
||||||
|
);
|
||||||
|
const { value: email } = currentUser.emailAddresses.find(
|
||||||
|
(emailAddress: TEmailAddress) => emailAddress.metadata.primary
|
||||||
|
);
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
clientId: $.auth.data.clientId,
|
||||||
|
clientSecret: $.auth.data.clientSecret,
|
||||||
|
scope: $.auth.data.scope,
|
||||||
|
idToken: data.id_token,
|
||||||
|
expiresIn: data.expires_in,
|
||||||
|
refreshToken: data.refresh_token,
|
||||||
|
resourceName: currentUser.resourceName,
|
||||||
|
screenName: `${displayName} - ${email}`,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default verifyCredentials;
|
@@ -0,0 +1,11 @@
|
|||||||
|
import { TBeforeRequest } from '@automatisch/types';
|
||||||
|
|
||||||
|
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||||
|
if (requestConfig.headers && $.auth.data?.accessToken) {
|
||||||
|
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default addAuthHeader;
|
@@ -0,0 +1,9 @@
|
|||||||
|
const authScope: string[] = [
|
||||||
|
'https://www.googleapis.com/auth/forms.body.readonly',
|
||||||
|
'https://www.googleapis.com/auth/forms.responses.readonly',
|
||||||
|
'https://www.googleapis.com/auth/drive.readonly',
|
||||||
|
'https://www.googleapis.com/auth/userinfo.email',
|
||||||
|
'profile',
|
||||||
|
];
|
||||||
|
|
||||||
|
export default authScope;
|
@@ -0,0 +1,10 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
|
||||||
|
const getCurrentUser = async ($: IGlobalVariable) => {
|
||||||
|
const { data: currentUser } = await $.http.get(
|
||||||
|
'https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses'
|
||||||
|
);
|
||||||
|
return currentUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getCurrentUser;
|
@@ -0,0 +1,3 @@
|
|||||||
|
import listForms from './list-forms';
|
||||||
|
|
||||||
|
export default [listForms];
|
@@ -0,0 +1,34 @@
|
|||||||
|
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'List forms',
|
||||||
|
key: 'listForms',
|
||||||
|
|
||||||
|
async run($: IGlobalVariable) {
|
||||||
|
const forms: {
|
||||||
|
data: IJSONObject[];
|
||||||
|
} = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
q: `mimeType='application/vnd.google-apps.form'`,
|
||||||
|
spaces: 'drive',
|
||||||
|
pageToken: undefined as unknown as string,
|
||||||
|
};
|
||||||
|
|
||||||
|
do {
|
||||||
|
const { data } = await $.http.get(`https://www.googleapis.com/drive/v3/files`, { params });
|
||||||
|
params.pageToken = data.nextPageToken;
|
||||||
|
|
||||||
|
for (const file of data.files) {
|
||||||
|
forms.data.push({
|
||||||
|
value: file.id,
|
||||||
|
name: file.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} while (params.pageToken);
|
||||||
|
|
||||||
|
return forms;
|
||||||
|
},
|
||||||
|
};
|
0
packages/backend/src/apps/google-forms/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/google-forms/index.d.ts
vendored
Normal file
20
packages/backend/src/apps/google-forms/index.ts
Normal file
20
packages/backend/src/apps/google-forms/index.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import defineApp from '../../helpers/define-app';
|
||||||
|
import addAuthHeader from './common/add-auth-header';
|
||||||
|
import auth from './auth';
|
||||||
|
import triggers from './triggers';
|
||||||
|
import dynamicData from './dynamic-data';
|
||||||
|
|
||||||
|
export default defineApp({
|
||||||
|
name: 'Google Forms',
|
||||||
|
key: 'google-forms',
|
||||||
|
baseUrl: 'https://docs.google.com/forms',
|
||||||
|
apiBaseUrl: 'https://forms.googleapis.com',
|
||||||
|
iconUrl: '{BASE_URL}/apps/google-forms/assets/favicon.svg',
|
||||||
|
authDocUrl: 'https://automatisch.io/docs/apps/google-forms/connection',
|
||||||
|
primaryColor: '673AB7',
|
||||||
|
supportsConnections: true,
|
||||||
|
beforeRequest: [addAuthHeader],
|
||||||
|
auth,
|
||||||
|
triggers,
|
||||||
|
dynamicData,
|
||||||
|
});
|
3
packages/backend/src/apps/google-forms/triggers/index.ts
Normal file
3
packages/backend/src/apps/google-forms/triggers/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import newFormResponses from './new-form-responses';
|
||||||
|
|
||||||
|
export default [newFormResponses];
|
@@ -0,0 +1,33 @@
|
|||||||
|
import defineTrigger from '../../../../helpers/define-trigger';
|
||||||
|
import newFormResponses from './new-form-responses';
|
||||||
|
|
||||||
|
export default defineTrigger({
|
||||||
|
name: 'New Form Responses',
|
||||||
|
key: 'newFormResponses',
|
||||||
|
pollInterval: 15,
|
||||||
|
description: 'Triggers when a new form response is submitted.',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Form',
|
||||||
|
key: 'formId',
|
||||||
|
type: 'dropdown' as const,
|
||||||
|
required: true,
|
||||||
|
description: 'Pick a form to receive form responses.',
|
||||||
|
variables: false,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listForms',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
await newFormResponses($);
|
||||||
|
},
|
||||||
|
});
|
@@ -0,0 +1,28 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
|
||||||
|
const newResponses = async ($: IGlobalVariable) => {
|
||||||
|
const params = {
|
||||||
|
pageToken: undefined as unknown as string,
|
||||||
|
};
|
||||||
|
|
||||||
|
do {
|
||||||
|
const pathname = `/v1/forms/${$.step.parameters.formId}/responses`;
|
||||||
|
const { data } = await $.http.get(pathname, { params });
|
||||||
|
params.pageToken = data.nextPageToken;
|
||||||
|
|
||||||
|
if (data.responses?.length) {
|
||||||
|
for (const formResponse of data.responses) {
|
||||||
|
const dataItem = {
|
||||||
|
raw: formResponse,
|
||||||
|
meta: {
|
||||||
|
internalId: formResponse.responseId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
$.pushTriggerItem(dataItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (params.pageToken);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default newResponses;
|
@@ -1,16 +1,34 @@
|
|||||||
import { IGlobalVariable } from '@automatisch/types';
|
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||||
import { XMLParser } from 'fast-xml-parser';
|
import { XMLParser } from 'fast-xml-parser';
|
||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
|
||||||
|
const getInternalId = async (item: IJSONObject): Promise<string> => {
|
||||||
|
if (item.guid) {
|
||||||
|
return item.guid.toString();
|
||||||
|
} else if (item.id) {
|
||||||
|
return item.id.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return await hashItem(JSON.stringify(item));
|
||||||
|
};
|
||||||
|
|
||||||
|
const hashItem = async (value: string) => {
|
||||||
|
return await bcrypt.hash(value, 1);
|
||||||
|
};
|
||||||
|
|
||||||
const newItemsInFeed = async ($: IGlobalVariable) => {
|
const newItemsInFeed = async ($: IGlobalVariable) => {
|
||||||
const { data } = await $.http.get($.step.parameters.feedUrl as string);
|
const { data } = await $.http.get($.step.parameters.feedUrl as string);
|
||||||
const parser = new XMLParser();
|
const parser = new XMLParser();
|
||||||
const parsedData = parser.parse(data);
|
const parsedData = parser.parse(data);
|
||||||
|
|
||||||
for (const item of parsedData.rss.channel.item) {
|
// naive implementation to cover atom and rss feeds
|
||||||
|
const items = parsedData.rss?.channel?.item || parsedData.feed?.entry || [];
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
const dataItem = {
|
const dataItem = {
|
||||||
raw: item,
|
raw: item,
|
||||||
meta: {
|
meta: {
|
||||||
internalId: item.guid,
|
internalId: await getInternalId(item),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
100
packages/backend/src/apps/todoist/actions/create-task/index.ts
Normal file
100
packages/backend/src/apps/todoist/actions/create-task/index.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import defineAction from '../../../../helpers/define-action';
|
||||||
|
|
||||||
|
export default defineAction({
|
||||||
|
name: 'Create Task',
|
||||||
|
key: 'createTask',
|
||||||
|
description: 'Creates a Task in Todoist',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Project ID',
|
||||||
|
key: 'projectId',
|
||||||
|
type: 'dropdown' as const,
|
||||||
|
required: false,
|
||||||
|
variables: false,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listProjects',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Section ID',
|
||||||
|
key: 'sectionId',
|
||||||
|
type: 'dropdown' as const,
|
||||||
|
required: false,
|
||||||
|
variables: false,
|
||||||
|
dependsOn: ['parameters.projectId'],
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listSections',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.projectId',
|
||||||
|
value: '{parameters.projectId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Labels',
|
||||||
|
key: 'labels',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: false,
|
||||||
|
variables: true,
|
||||||
|
description:
|
||||||
|
'Labels to add to task (comma separated). Examples: "work" "work,imported"',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Content',
|
||||||
|
key: 'content',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
variables: true,
|
||||||
|
description:
|
||||||
|
'Task content, may be markdown. Example: "Foo"',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Description',
|
||||||
|
key: 'description',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: false,
|
||||||
|
variables: true,
|
||||||
|
description:
|
||||||
|
'Task description, may be markdown. Example: "Foo"',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
const requestPath = `/tasks`;
|
||||||
|
const {
|
||||||
|
projectId,
|
||||||
|
sectionId,
|
||||||
|
labels,
|
||||||
|
content,
|
||||||
|
description
|
||||||
|
} = $.step.parameters;
|
||||||
|
|
||||||
|
const labelsArray = (labels as string).split(',')
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
content,
|
||||||
|
description: description || null,
|
||||||
|
project_id: projectId || null,
|
||||||
|
labels: labelsArray || null,
|
||||||
|
section_id: sectionId || null,
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await $.http.post(requestPath, payload);
|
||||||
|
|
||||||
|
$.setActionItem({ raw: response.data });
|
||||||
|
},
|
||||||
|
});
|
3
packages/backend/src/apps/todoist/actions/index.ts
Normal file
3
packages/backend/src/apps/todoist/actions/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import createTask from './create-task';
|
||||||
|
|
||||||
|
export default [createTask];
|
14
packages/backend/src/apps/todoist/assets/favicon.svg
Normal file
14
packages/backend/src/apps/todoist/assets/favicon.svg
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||||
|
<g>
|
||||||
|
<path d="M224.001997,0 L31.9980026,0 C14.3579381,0.0394964443 0.0614809418,14.336846 0,32 L0,224 C0,241.6 14.3971038,256 31.9980026,256 L224.001997,256 C241.602896,256 256,241.6 256,224 L256,32 C256,14.4 241.602896,0 224.001997,0" fill="#E44332">
|
||||||
|
|
||||||
|
</path>
|
||||||
|
<path d="M54.132778,120.802491 C58.5960224,118.196275 154.476075,62.477451 156.667847,61.1862981 C158.859619,59.9110855 158.97917,55.9898065 156.508446,54.5711324 C154.053661,53.1604284 149.391165,50.4824817 147.661658,49.4543415 C145.192242,48.0957707 142.191169,48.132074 139.755339,49.5499825 C138.527947,50.2672896 56.6035026,97.8486625 53.8697654,99.4107981 C50.5781227,101.291737 46.5372925,101.323617 43.2695601,99.4107981 L0,74.0181257 L0,95.6011002 C10.5205046,101.801822 36.7181549,117.200015 43.062338,120.826401 C46.8481256,122.978322 50.4745117,122.930502 54.1407481,120.802491" fill="#FFFFFF">
|
||||||
|
|
||||||
|
</path>
|
||||||
|
<path d="M54.132778,161.609296 C58.5960224,159.00308 154.476075,103.284257 156.667847,101.993104 C158.859619,100.717891 158.97917,96.7966121 156.508446,95.377938 C154.053661,93.9672339 149.391165,91.2892873 147.661658,90.2611471 C145.192242,88.9025763 142.191169,88.9388796 139.755339,90.3567881 C138.527947,91.0740952 56.6035026,138.655468 53.8697654,140.217604 C50.5781227,142.098542 46.5372925,142.130423 43.2695601,140.217604 L0,114.824931 L0,136.407906 C10.5205046,142.608627 36.7181549,158.00682 43.062338,161.633206 C46.8481256,163.785128 50.4745117,163.737307 54.1407481,161.609296" fill="#FFFFFF">
|
||||||
|
|
||||||
|
</path>
|
||||||
|
<path d="M54.132778,204.966527 C58.5960224,202.360311 154.476075,146.641487 156.667847,145.350335 C158.859619,144.075122 158.97917,140.153843 156.508446,138.735169 C154.053661,137.324465 149.391165,134.646518 147.661658,133.618378 C145.192242,132.259807 142.191169,132.29611 139.755339,133.714019 C138.527947,134.431326 56.6035026,182.012699 53.8697654,183.574835 C50.5781227,185.455773 46.5372925,185.487654 43.2695601,183.574835 L0,158.182162 L0,179.765137 C10.5205046,185.965858 36.7181549,201.364051 43.062338,204.990437 C46.8481256,207.142359 50.4745117,207.094538 54.1407481,204.966527" fill="#FFFFFF">
|
After Width: | Height: | Size: 2.4 KiB |
17
packages/backend/src/apps/todoist/auth/generate-auth-url.ts
Normal file
17
packages/backend/src/apps/todoist/auth/generate-auth-url.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
import { URLSearchParams } from 'url';
|
||||||
|
|
||||||
|
export default async function generateAuthUrl($: IGlobalVariable) {
|
||||||
|
const scopes = ['data:read_write'];
|
||||||
|
const searchParams = new URLSearchParams({
|
||||||
|
client_id: $.auth.data.clientId as string,
|
||||||
|
scope: scopes.join(','),
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = `${$.app.baseUrl
|
||||||
|
}/oauth/authorize?${searchParams.toString()}`;
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
url,
|
||||||
|
});
|
||||||
|
}
|
46
packages/backend/src/apps/todoist/auth/index.ts
Normal file
46
packages/backend/src/apps/todoist/auth/index.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import generateAuthUrl from './generate-auth-url';
|
||||||
|
import verifyCredentials from './verify-credentials';
|
||||||
|
import isStillVerified from './is-still-verified';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'screenName',
|
||||||
|
label: 'Screen Name',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description:
|
||||||
|
'Name your connection (only used for Automatisch UI).',
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'clientId',
|
||||||
|
label: 'Client ID',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'clientSecret',
|
||||||
|
label: 'Client Secret',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
generateAuthUrl,
|
||||||
|
verifyCredentials,
|
||||||
|
isStillVerified,
|
||||||
|
};
|
@@ -0,0 +1,8 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
|
||||||
|
const isStillVerified = async ($: IGlobalVariable) => {
|
||||||
|
await $.http.get('/projects');
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isStillVerified;
|
19
packages/backend/src/apps/todoist/auth/verify-credentials.ts
Normal file
19
packages/backend/src/apps/todoist/auth/verify-credentials.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
|
||||||
|
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||||
|
const { data } = await $.http.post(
|
||||||
|
`${$.app.baseUrl}/oauth/access_token`,
|
||||||
|
{
|
||||||
|
client_id: $.auth.data.clientId,
|
||||||
|
client_secret: $.auth.data.clientSecret,
|
||||||
|
code: $.auth.data.code,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
tokenType: data.token_type,
|
||||||
|
accessToken: data.access_token,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default verifyCredentials;
|
13
packages/backend/src/apps/todoist/common/add-auth-header.ts
Normal file
13
packages/backend/src/apps/todoist/common/add-auth-header.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { TBeforeRequest } from '@automatisch/types';
|
||||||
|
|
||||||
|
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||||
|
const authData = $.auth.data;
|
||||||
|
if (authData?.accessToken && authData?.tokenType) {
|
||||||
|
const authorizationHeader = `${authData.tokenType} ${authData.accessToken}`;
|
||||||
|
requestConfig.headers.Authorization = authorizationHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default addAuthHeader;
|
5
packages/backend/src/apps/todoist/dynamic-data/index.ts
Normal file
5
packages/backend/src/apps/todoist/dynamic-data/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import listProjects from './list-projects';
|
||||||
|
import listSections from './list-sections';
|
||||||
|
import listLabels from './list-labels';
|
||||||
|
|
||||||
|
export default [listProjects, listSections, listLabels];
|
@@ -0,0 +1,19 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'List labels',
|
||||||
|
key: 'listLabels',
|
||||||
|
|
||||||
|
async run($: IGlobalVariable) {
|
||||||
|
const response = await $.http.get('/labels');
|
||||||
|
|
||||||
|
response.data = response.data.map((label: { name: string }) => {
|
||||||
|
return {
|
||||||
|
value: label.name,
|
||||||
|
name: label.name,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,19 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'List projects',
|
||||||
|
key: 'listProjects',
|
||||||
|
|
||||||
|
async run($: IGlobalVariable) {
|
||||||
|
const response = await $.http.get('/projects');
|
||||||
|
|
||||||
|
response.data = response.data.map((project: { id: string, name: string }) => {
|
||||||
|
return {
|
||||||
|
value: project.id,
|
||||||
|
name: project.name,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
};
|
@@ -0,0 +1,23 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'List sections',
|
||||||
|
key: 'listSections',
|
||||||
|
|
||||||
|
async run($: IGlobalVariable) {
|
||||||
|
const params = {
|
||||||
|
project_id: ($.step.parameters.projectId as string),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await $.http.get('/sections', {params});
|
||||||
|
|
||||||
|
response.data = response.data.map((section: { id: string, name: string }) => {
|
||||||
|
return {
|
||||||
|
value: section.id,
|
||||||
|
name: section.name,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
};
|
0
packages/backend/src/apps/todoist/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/todoist/index.d.ts
vendored
Normal file
22
packages/backend/src/apps/todoist/index.ts
Normal file
22
packages/backend/src/apps/todoist/index.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import defineApp from '../../helpers/define-app';
|
||||||
|
import addAuthHeader from './common/add-auth-header';
|
||||||
|
import auth from './auth';
|
||||||
|
import triggers from './triggers';
|
||||||
|
import actions from './actions';
|
||||||
|
import dynamicData from './dynamic-data';
|
||||||
|
|
||||||
|
export default defineApp({
|
||||||
|
name: 'Todoist',
|
||||||
|
key: 'todoist',
|
||||||
|
iconUrl: '{BASE_URL}/apps/todoist/assets/favicon.svg',
|
||||||
|
authDocUrl: 'https://automatisch.io/docs/apps/todoist/connection',
|
||||||
|
supportsConnections: true,
|
||||||
|
baseUrl: 'https://todoist.com',
|
||||||
|
apiBaseUrl: 'https://api.todoist.com/rest/v2',
|
||||||
|
primaryColor: 'e44332',
|
||||||
|
beforeRequest: [addAuthHeader],
|
||||||
|
auth,
|
||||||
|
triggers,
|
||||||
|
actions,
|
||||||
|
dynamicData,
|
||||||
|
});
|
@@ -0,0 +1,30 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
|
||||||
|
const getActiveTasks = async ($: IGlobalVariable) => {
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
project_id: ($.step.parameters.projectId as string)?.trim(),
|
||||||
|
section_id: ($.step.parameters.sectionId as string)?.trim(),
|
||||||
|
label: ($.step.parameters.label as string)?.trim(),
|
||||||
|
filter: ($.step.parameters.filter as string)?.trim(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await $.http.get('/tasks', { params });
|
||||||
|
|
||||||
|
// todoist api doesn't offer sorting, so we inverse sort on id here
|
||||||
|
response.data.sort((a: { id: number; }, b: { id: number; }) => {
|
||||||
|
return b.id - a.id;
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const task of response.data) {
|
||||||
|
$.pushTriggerItem({
|
||||||
|
raw: task,
|
||||||
|
meta:{
|
||||||
|
internalId: task.id as string,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default getActiveTasks;
|
@@ -0,0 +1,80 @@
|
|||||||
|
import defineTrigger from '../../../../helpers/define-trigger';
|
||||||
|
import getActiveTasks from './get-tasks';
|
||||||
|
|
||||||
|
export default defineTrigger({
|
||||||
|
name: 'Get Active Tasks',
|
||||||
|
key: 'getActiveTasks',
|
||||||
|
pollInterval: 15,
|
||||||
|
description: 'Triggers when new Task(s) are found',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
label: 'Project ID',
|
||||||
|
key: 'projectId',
|
||||||
|
type: 'dropdown' as const,
|
||||||
|
required: false,
|
||||||
|
variables: false,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listProjects',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Section ID',
|
||||||
|
key: 'sectionId',
|
||||||
|
type: 'dropdown' as const,
|
||||||
|
required: false,
|
||||||
|
variables: false,
|
||||||
|
dependsOn: ['parameters.projectId'],
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listSections',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.projectId',
|
||||||
|
value: '{parameters.projectId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Label',
|
||||||
|
key: 'label',
|
||||||
|
type: 'dropdown' as const,
|
||||||
|
required: false,
|
||||||
|
variables: false,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listLabels',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Filter',
|
||||||
|
key: 'filter',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: false,
|
||||||
|
variables: false,
|
||||||
|
description:
|
||||||
|
'Limit queried tasks to this filter. Example: "Meeting & today"',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
await getActiveTasks($);
|
||||||
|
},
|
||||||
|
});
|
3
packages/backend/src/apps/todoist/triggers/index.ts
Normal file
3
packages/backend/src/apps/todoist/triggers/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import getTasks from './get-tasks';
|
||||||
|
|
||||||
|
export default [getTasks];
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { URL } from 'node:url';
|
||||||
import * as dotenv from 'dotenv';
|
import * as dotenv from 'dotenv';
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
@@ -38,14 +39,23 @@ const port = process.env.PORT || '3000';
|
|||||||
const serveWebAppSeparately =
|
const serveWebAppSeparately =
|
||||||
process.env.SERVE_WEB_APP_SEPARATELY === 'true' ? true : false;
|
process.env.SERVE_WEB_APP_SEPARATELY === 'true' ? true : false;
|
||||||
|
|
||||||
let webAppUrl = `${protocol}://${host}:${port}`;
|
let apiUrl = (new URL(`${protocol}://${host}:${port}`)).toString();
|
||||||
const webhookUrl = process.env.WEBHOOK_URL || webAppUrl;
|
apiUrl = apiUrl.substring(0, apiUrl.length - 1);
|
||||||
|
|
||||||
if (serveWebAppSeparately) {
|
// use apiUrl by default, which has less priority over the following cases
|
||||||
webAppUrl = process.env.WEB_APP_URL || 'http://localhost:3001';
|
let webAppUrl = apiUrl;
|
||||||
|
|
||||||
|
if (process.env.WEB_APP_URL) {
|
||||||
|
// use env. var. if provided
|
||||||
|
webAppUrl = (new URL(process.env.WEB_APP_URL)).toString();
|
||||||
|
webAppUrl = webAppUrl.substring(0, webAppUrl.length - 1);
|
||||||
|
} else if (serveWebAppSeparately) {
|
||||||
|
// no env. var. and serving separately, sign of development
|
||||||
|
webAppUrl = 'http://localhost:3001'
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseUrl = `${protocol}://${host}:${port}`;
|
let webhookUrl = (new URL(process.env.WEBHOOK_URL || apiUrl)).toString();
|
||||||
|
webhookUrl = webhookUrl.substring(0, webhookUrl.length - 1);
|
||||||
|
|
||||||
const appEnv = process.env.APP_ENV || 'development';
|
const appEnv = process.env.APP_ENV || 'development';
|
||||||
|
|
||||||
@@ -75,7 +85,7 @@ const appConfig: AppConfig = {
|
|||||||
enableBullMQDashboard: process.env.ENABLE_BULLMQ_DASHBOARD === 'true',
|
enableBullMQDashboard: process.env.ENABLE_BULLMQ_DASHBOARD === 'true',
|
||||||
bullMQDashboardUsername: process.env.BULLMQ_DASHBOARD_USERNAME,
|
bullMQDashboardUsername: process.env.BULLMQ_DASHBOARD_USERNAME,
|
||||||
bullMQDashboardPassword: process.env.BULLMQ_DASHBOARD_PASSWORD,
|
bullMQDashboardPassword: process.env.BULLMQ_DASHBOARD_PASSWORD,
|
||||||
baseUrl,
|
baseUrl: apiUrl,
|
||||||
webAppUrl,
|
webAppUrl,
|
||||||
webhookUrl,
|
webhookUrl,
|
||||||
telemetryEnabled: process.env.TELEMETRY_ENABLED === 'false' ? false : true,
|
telemetryEnabled: process.env.TELEMETRY_ENABLED === 'false' ? false : true,
|
||||||
|
@@ -45,6 +45,7 @@ export default async (request: IRequest, response: Response) => {
|
|||||||
|
|
||||||
// in case trigger type is 'webhook'
|
// in case trigger type is 'webhook'
|
||||||
let payload = request.body;
|
let payload = request.body;
|
||||||
|
let rawInternalId: string | Buffer = request.rawBody;
|
||||||
|
|
||||||
// in case it's our built-in generic webhook trigger
|
// in case it's our built-in generic webhook trigger
|
||||||
if (isWebhookApp) {
|
if (isWebhookApp) {
|
||||||
@@ -53,12 +54,14 @@ export default async (request: IRequest, response: Response) => {
|
|||||||
body: request.body,
|
body: request.body,
|
||||||
query: request.query,
|
query: request.query,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rawInternalId = JSON.stringify(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
const triggerItem: ITriggerItem = {
|
const triggerItem: ITriggerItem = {
|
||||||
raw: payload,
|
raw: payload,
|
||||||
meta: {
|
meta: {
|
||||||
internalId: await bcrypt.hash(request.rawBody, 1),
|
internalId: await bcrypt.hash(rawInternalId, 1),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -0,0 +1,13 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
return knex.schema.table('flows', (table) => {
|
||||||
|
table.string('remote_webhook_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
return knex.schema.table('flows', (table) => {
|
||||||
|
table.dropColumn('remote_webhook_id');
|
||||||
|
});
|
||||||
|
}
|
@@ -381,6 +381,7 @@ type Trigger {
|
|||||||
key: String
|
key: String
|
||||||
description: String
|
description: String
|
||||||
pollInterval: Int
|
pollInterval: Int
|
||||||
|
type: String
|
||||||
substeps: [TriggerSubstep]
|
substeps: [TriggerSubstep]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
25
packages/backend/src/helpers/delay-as-milliseconds.ts
Normal file
25
packages/backend/src/helpers/delay-as-milliseconds.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import Step from '../models/step';
|
||||||
|
import delayForAsMilliseconds, {
|
||||||
|
TDelayForUnit,
|
||||||
|
} from './delay-for-as-milliseconds';
|
||||||
|
import delayUntilAsMilliseconds from './delay-until-as-milliseconds';
|
||||||
|
|
||||||
|
const delayAsMilliseconds = (step: Step) => {
|
||||||
|
let delayDuration = 0;
|
||||||
|
|
||||||
|
if (step.key === 'delayFor') {
|
||||||
|
const { delayForUnit, delayForValue } = step.parameters;
|
||||||
|
|
||||||
|
delayDuration = delayForAsMilliseconds(
|
||||||
|
delayForUnit as TDelayForUnit,
|
||||||
|
Number(delayForValue)
|
||||||
|
);
|
||||||
|
} else if (step.key === 'delayUntil') {
|
||||||
|
const { delayUntil } = step.parameters;
|
||||||
|
delayDuration = delayUntilAsMilliseconds(delayUntil as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
return delayDuration;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default delayAsMilliseconds;
|
21
packages/backend/src/helpers/delay-for-as-milliseconds.ts
Normal file
21
packages/backend/src/helpers/delay-for-as-milliseconds.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
export type TDelayForUnit = 'minutes' | 'hours' | 'days' | 'weeks';
|
||||||
|
|
||||||
|
const delayAsMilliseconds = (
|
||||||
|
delayForUnit: TDelayForUnit,
|
||||||
|
delayForValue: number
|
||||||
|
) => {
|
||||||
|
switch (delayForUnit) {
|
||||||
|
case 'minutes':
|
||||||
|
return delayForValue * 60 * 1000;
|
||||||
|
case 'hours':
|
||||||
|
return delayForValue * 60 * 60 * 1000;
|
||||||
|
case 'days':
|
||||||
|
return delayForValue * 24 * 60 * 60 * 1000;
|
||||||
|
case 'weeks':
|
||||||
|
return delayForValue * 7 * 24 * 60 * 60 * 1000;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default delayAsMilliseconds;
|
@@ -0,0 +1,8 @@
|
|||||||
|
const delayUntilAsMilliseconds = (delayUntil: string) => {
|
||||||
|
const delayUntilDate = new Date(delayUntil);
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
return delayUntilDate.getTime() - now.getTime();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default delayUntilAsMilliseconds;
|
@@ -37,6 +37,7 @@ const globalVariable = async (
|
|||||||
testRun = false,
|
testRun = false,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
|
const isTrigger = step?.isTrigger;
|
||||||
const lastInternalId = testRun ? undefined : await flow?.lastInternalId();
|
const lastInternalId = testRun ? undefined : await flow?.lastInternalId();
|
||||||
const nextStep = await step?.getNextStep();
|
const nextStep = await step?.getNextStep();
|
||||||
|
|
||||||
@@ -123,6 +124,18 @@ const globalVariable = async (
|
|||||||
$.webhookUrl = webhookUrl;
|
$.webhookUrl = webhookUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isTrigger && (await step.getTriggerCommand()).type === 'webhook') {
|
||||||
|
$.flow.setRemoteWebhookId = async (remoteWebhookId) => {
|
||||||
|
await flow.$query().patchAndFetch({
|
||||||
|
remoteWebhookId,
|
||||||
|
});
|
||||||
|
|
||||||
|
$.flow.remoteWebhookId = remoteWebhookId;
|
||||||
|
};
|
||||||
|
|
||||||
|
$.flow.remoteWebhookId = flow.remoteWebhookId;
|
||||||
|
}
|
||||||
|
|
||||||
const lastInternalIds =
|
const lastInternalIds =
|
||||||
testRun || (flow && step.isAction) ? [] : await flow?.lastInternalIds(2000);
|
testRun || (flow && step.isAction) ? [] : await flow?.lastInternalIds(2000);
|
||||||
|
|
||||||
|
@@ -13,6 +13,7 @@ class Flow extends Base {
|
|||||||
active: boolean;
|
active: boolean;
|
||||||
steps: Step[];
|
steps: Step[];
|
||||||
published_at: string;
|
published_at: string;
|
||||||
|
remoteWebhookId: string;
|
||||||
executions?: Execution[];
|
executions?: Execution[];
|
||||||
|
|
||||||
static tableName = 'flows';
|
static tableName = 'flows';
|
||||||
@@ -25,6 +26,7 @@ class Flow extends Base {
|
|||||||
id: { type: 'string', format: 'uuid' },
|
id: { type: 'string', format: 'uuid' },
|
||||||
name: { type: 'string', minLength: 1 },
|
name: { type: 'string', minLength: 1 },
|
||||||
userId: { type: 'string', format: 'uuid' },
|
userId: { type: 'string', format: 'uuid' },
|
||||||
|
remoteWebhookId: { type: 'string' },
|
||||||
active: { type: 'boolean' },
|
active: { type: 'boolean' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@@ -1,7 +1,18 @@
|
|||||||
import { Router } from 'express';
|
import express, { Router } from 'express';
|
||||||
|
import multer from 'multer';
|
||||||
|
import { IRequest } from '@automatisch/types';
|
||||||
import webhookHandler from '../controllers/webhooks/handler';
|
import webhookHandler from '../controllers/webhooks/handler';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
const upload = multer();
|
||||||
|
|
||||||
|
router.use(upload.none());
|
||||||
|
|
||||||
|
router.use(express.text({
|
||||||
|
verify(req, res, buf) {
|
||||||
|
(req as IRequest).rawBody = buf;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
router.get('/:flowId', webhookHandler);
|
router.get('/:flowId', webhookHandler);
|
||||||
router.put('/:flowId', webhookHandler);
|
router.put('/:flowId', webhookHandler);
|
||||||
|
@@ -4,7 +4,11 @@ import logger from '../helpers/logger';
|
|||||||
import Step from '../models/step';
|
import Step from '../models/step';
|
||||||
import actionQueue from '../queues/action';
|
import actionQueue from '../queues/action';
|
||||||
import { processAction } from '../services/action';
|
import { processAction } from '../services/action';
|
||||||
import { REMOVE_AFTER_30_DAYS_OR_150_JOBS, REMOVE_AFTER_7_DAYS_OR_50_JOBS } from '../helpers/remove-job-configuration';
|
import {
|
||||||
|
REMOVE_AFTER_30_DAYS_OR_150_JOBS,
|
||||||
|
REMOVE_AFTER_7_DAYS_OR_50_JOBS,
|
||||||
|
} from '../helpers/remove-job-configuration';
|
||||||
|
import delayAsMilliseconds from '../helpers/delay-as-milliseconds';
|
||||||
|
|
||||||
type JobData = {
|
type JobData = {
|
||||||
flowId: string;
|
flowId: string;
|
||||||
@@ -12,6 +16,8 @@ type JobData = {
|
|||||||
stepId: string;
|
stepId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DEFAULT_DELAY_DURATION = 0;
|
||||||
|
|
||||||
export const worker = new Worker(
|
export const worker = new Worker(
|
||||||
'action',
|
'action',
|
||||||
async (job) => {
|
async (job) => {
|
||||||
@@ -35,6 +41,11 @@ export const worker = new Worker(
|
|||||||
const jobOptions = {
|
const jobOptions = {
|
||||||
removeOnComplete: REMOVE_AFTER_7_DAYS_OR_50_JOBS,
|
removeOnComplete: REMOVE_AFTER_7_DAYS_OR_50_JOBS,
|
||||||
removeOnFail: REMOVE_AFTER_30_DAYS_OR_150_JOBS,
|
removeOnFail: REMOVE_AFTER_30_DAYS_OR_150_JOBS,
|
||||||
|
delay: DEFAULT_DELAY_DURATION,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (step.appKey === 'delay') {
|
||||||
|
jobOptions.delay = delayAsMilliseconds(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
await actionQueue.add(jobName, jobPayload, jobOptions);
|
await actionQueue.add(jobName, jobPayload, jobOptions);
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/cli",
|
"name": "@automatisch/cli",
|
||||||
"version": "0.3.0",
|
"version": "0.4.0",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
"version": "oclif readme && git add README.md"
|
"version": "oclif readme && git add README.md"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@automatisch/backend": "^0.3.0",
|
"@automatisch/backend": "^0.4.0",
|
||||||
"@oclif/core": "^1",
|
"@oclif/core": "^1",
|
||||||
"@oclif/plugin-help": "^5",
|
"@oclif/plugin-help": "^5",
|
||||||
"@oclif/plugin-plugins": "^2.0.1",
|
"@oclif/plugin-plugins": "^2.0.1",
|
||||||
|
@@ -10171,21 +10171,14 @@ json-stringify-safe@^5.0.1:
|
|||||||
resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
|
resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
|
||||||
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
|
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
|
||||||
|
|
||||||
json5@^1.0.1:
|
json5@1.0.2, json5@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
|
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
|
||||||
integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
|
integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==
|
||||||
dependencies:
|
dependencies:
|
||||||
minimist "^1.2.0"
|
minimist "^1.2.0"
|
||||||
|
|
||||||
json5@^2.1.2:
|
json5@^2.1.2, json5@^2.2.0, json5@^2.2.1:
|
||||||
version "2.2.0"
|
|
||||||
resolved "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz"
|
|
||||||
integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
|
|
||||||
dependencies:
|
|
||||||
minimist "^1.2.5"
|
|
||||||
|
|
||||||
json5@^2.2.0, json5@^2.2.1:
|
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
|
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
|
||||||
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
|
integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
|
||||||
@@ -10891,9 +10884,9 @@ minimatch@^5.0.1:
|
|||||||
brace-expansion "^2.0.1"
|
brace-expansion "^2.0.1"
|
||||||
|
|
||||||
minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6:
|
minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6:
|
||||||
version "1.2.6"
|
version "1.2.7"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
|
||||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
|
||||||
|
|
||||||
minipass-collect@^1.0.2:
|
minipass-collect@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/docs",
|
"name": "@automatisch/docs",
|
||||||
"version": "0.3.0",
|
"version": "0.4.0",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
@@ -41,6 +41,15 @@ export default defineConfig({
|
|||||||
{ text: 'Connection', link: '/apps/deepl/connection' },
|
{ text: 'Connection', link: '/apps/deepl/connection' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: 'Delay',
|
||||||
|
collapsible: true,
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Actions', link: '/apps/delay/actions' },
|
||||||
|
{ text: 'Connection', link: '/apps/delay/connection' },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: 'Discord',
|
text: 'Discord',
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
@@ -141,6 +150,16 @@ export default defineConfig({
|
|||||||
{ text: 'Connection', link: '/apps/telegram-bot/connection' },
|
{ text: 'Connection', link: '/apps/telegram-bot/connection' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: 'Todoist',
|
||||||
|
collapsible: true,
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{ text: 'Triggers', link: '/apps/todoist/triggers' },
|
||||||
|
{ text: 'Actions', link: '/apps/todoist/actions' },
|
||||||
|
{ text: 'Connection', link: '/apps/todoist/connection' },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: 'Twilio',
|
text: 'Twilio',
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
|
51
packages/docs/pages/.vitepress/theme/CustomLayout.vue
Normal file
51
packages/docs/pages/.vitepress/theme/CustomLayout.vue
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<style scoped>
|
||||||
|
.announcement-bar {
|
||||||
|
background: var(--vp-c-brand-dark);
|
||||||
|
color: #fff;
|
||||||
|
border-bottom: 2px solid rgba(194, 194, 194, 0.2);
|
||||||
|
text-align: center;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
position: fixed;
|
||||||
|
padding: 4px 40px;
|
||||||
|
z-index: 999999;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 400;
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
.announcement-bar {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import DefaultTheme from 'vitepress/theme';
|
||||||
|
|
||||||
|
const { Layout } = DefaultTheme;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Layout>
|
||||||
|
<template #layout-top>
|
||||||
|
<div class="announcement-bar">
|
||||||
|
<span
|
||||||
|
>⭐ If you like what we are doing with Automatisch, please give us a
|
||||||
|
star on
|
||||||
|
<strong
|
||||||
|
><a
|
||||||
|
href="https://github.com/automatisch/automatisch"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>GitHub</a
|
||||||
|
></strong
|
||||||
|
>.</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Layout>
|
||||||
|
</template>
|
@@ -117,9 +117,33 @@
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
|
||||||
|
--announcement-bar-height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.VPTeamMembersItem .avatar-img {
|
.VPTeamMembersItem .avatar-img {
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header.VPNav {
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPNavScreen.VPNavScreen {
|
||||||
|
top: calc(var(--announcement-bar-height) + var(--vp-nav-height-mobile));
|
||||||
|
}
|
||||||
|
|
||||||
|
.VPLocalNav.VPLocalNav {
|
||||||
|
top: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
aside.VPSidebar {
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
#VPContent {
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,3 +1,8 @@
|
|||||||
import DefaultTheme from 'vitepress/theme';
|
import DefaultTheme from 'vitepress/theme';
|
||||||
import './custom.css';
|
import './custom.css';
|
||||||
export default DefaultTheme;
|
import CustomLayout from './CustomLayout.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
...DefaultTheme,
|
||||||
|
Layout: CustomLayout,
|
||||||
|
};
|
||||||
|
@@ -14,26 +14,28 @@ The default values for some environment variables might be different in our deve
|
|||||||
Please be careful with the `ENCRYPTION_KEY` and `WEBHOOK_SECRET_KEY` environment variables. They are used to encrypt your credentials from third-party services and verify webhook requests. If you change them, your existing connections and flows will not continue to work.
|
Please be careful with the `ENCRYPTION_KEY` and `WEBHOOK_SECRET_KEY` environment variables. They are used to encrypt your credentials from third-party services and verify webhook requests. If you change them, your existing connections and flows will not continue to work.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
| Variable Name | Type | Default Value | Description |
|
| Variable Name | Type | Default Value | Description |
|
||||||
| --------------------------- | ------- | ------------------ | --------------------------------------------- |
|
| --------------------------- | ------- | ------------------ | ---------------------------------------------------- |
|
||||||
| `HOST` | string | `localhost` | HTTP Host |
|
| `HOST` | string | `localhost` | HTTP Host |
|
||||||
| `PROTOCOL` | string | `http` | HTTP Protocol |
|
| `PROTOCOL` | string | `http` | HTTP Protocol |
|
||||||
| `PORT` | string | `3000` | HTTP Port |
|
| `PORT` | string | `3000` | HTTP Port |
|
||||||
| `APP_ENV` | string | `production` | Automatisch Environment |
|
| `APP_ENV` | string | `production` | Automatisch Environment |
|
||||||
| `POSTGRES_DATABASE` | string | `automatisch` | Database Name |
|
| `WEB_APP_URL` | string | | Can be used to override connection URLs and CORS URL |
|
||||||
| `POSTGRES_PORT` | number | `5432` | Database Port |
|
| `WEBHOOK_URL` | string | | Can be used to override webhook URL |
|
||||||
| `POSTGRES_HOST` | string | `postgres` | Database Host |
|
| `POSTGRES_DATABASE` | string | `automatisch` | Database Name |
|
||||||
| `POSTGRES_USERNAME` | string | `automatisch_user` | Database User |
|
| `POSTGRES_PORT` | number | `5432` | Database Port |
|
||||||
| `POSTGRES_PASSWORD` | string | | Password of Database User |
|
| `POSTGRES_HOST` | string | `postgres` | Database Host |
|
||||||
| `ENCRYPTION_KEY` | string | | Encryption Key to store credentials |
|
| `POSTGRES_USERNAME` | string | `automatisch_user` | Database User |
|
||||||
| `WEBHOOK_SECRET_KEY` | string | | Webhook Secret Key to verify webhook requests |
|
| `POSTGRES_PASSWORD` | string | | Password of Database User |
|
||||||
| `APP_SECRET_KEY` | string | | Secret Key to authenticate the user |
|
| `ENCRYPTION_KEY` | string | | Encryption Key to store credentials |
|
||||||
| `REDIS_HOST` | string | `redis` | Redis Host |
|
| `WEBHOOK_SECRET_KEY` | string | | Webhook Secret Key to verify webhook requests |
|
||||||
| `REDIS_PORT` | number | `6379` | Redis Port |
|
| `APP_SECRET_KEY` | string | | Secret Key to authenticate the user |
|
||||||
| `REDIS_USERNAME` | string | `` | Redis Username |
|
| `REDIS_HOST` | string | `redis` | Redis Host |
|
||||||
| `REDIS_PASSWORD` | string | `` | Redis Password |
|
| `REDIS_PORT` | number | `6379` | Redis Port |
|
||||||
| `REDIS_TLS` | boolean | `false` | Redis TLS |
|
| `REDIS_USERNAME` | string | | Redis Username |
|
||||||
| `TELEMETRY_ENABLED` | boolean | `true` | Enable/Disable Telemetry |
|
| `REDIS_PASSWORD` | string | | Redis Password |
|
||||||
| `ENABLE_BULLMQ_DASHBOARD` | boolean | `false` | Enable BullMQ Dashboard |
|
| `REDIS_TLS` | boolean | `false` | Redis TLS |
|
||||||
| `BULLMQ_DASHBOARD_USERNAME` | string | | Username to login BullMQ Dashboard |
|
| `TELEMETRY_ENABLED` | boolean | `true` | Enable/Disable Telemetry |
|
||||||
| `BULLMQ_DASHBOARD_PASSWORD` | string | | Password to login BullMQ Dashboard |
|
| `ENABLE_BULLMQ_DASHBOARD` | boolean | `false` | Enable BullMQ Dashboard |
|
||||||
|
| `BULLMQ_DASHBOARD_USERNAME` | string | | Username to login BullMQ Dashboard |
|
||||||
|
| `BULLMQ_DASHBOARD_PASSWORD` | string | | Password to login BullMQ Dashboard |
|
||||||
|
14
packages/docs/pages/apps/delay/actions.md
Normal file
14
packages/docs/pages/apps/delay/actions.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
favicon: /favicons/delay.svg
|
||||||
|
items:
|
||||||
|
- name: Delay For
|
||||||
|
desc: Delays the execution of the next action by a specified amount of time.
|
||||||
|
- name: Delay Until
|
||||||
|
desc: Delays the execution of the next action until a specified date.
|
||||||
|
---
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import CustomListing from '../../components/CustomListing.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CustomListing />
|
3
packages/docs/pages/apps/delay/connection.md
Normal file
3
packages/docs/pages/apps/delay/connection.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Delay
|
||||||
|
|
||||||
|
Delay is a built-in app shipped with Automatisch, and it doesn't need to talk with any other external service to run. So there are no additional steps to use the Delay app. It can be used only as an action and it delays the execution of the next action by a specified amount of time.
|
12
packages/docs/pages/apps/todoist/actions.md
Normal file
12
packages/docs/pages/apps/todoist/actions.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
favicon: /favicons/todoist.svg
|
||||||
|
items:
|
||||||
|
- name: Create Task
|
||||||
|
desc: Creates a task in Todoist.
|
||||||
|
---
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import CustomListing from '../../components/CustomListing.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CustomListing />
|
14
packages/docs/pages/apps/todoist/connection.md
Normal file
14
packages/docs/pages/apps/todoist/connection.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Todoist
|
||||||
|
|
||||||
|
:::info
|
||||||
|
This page explains the steps you need to follow to set up the Todoist connection in Automatisch. If any of the steps are outdated, please let us know!
|
||||||
|
:::
|
||||||
|
|
||||||
|
1. Go to the account [App Management page](https://developer.todoist.com/appconsole.html) to register a **new OAuth application** on Todoist.
|
||||||
|
1. Fill **App name** and **App service URL**.
|
||||||
|
1. Copy **OAuth Redirect URL** from Automatisch to **OAuth redirect URL** field on Todoist page.
|
||||||
|
1. Click on the **Save settings** button on the Todoist page.
|
||||||
|
1. Copy the **Client ID** and **Client secret** values from the Todoist page to the corresponding fields on Automatisch.
|
||||||
|
1. Enter a memorable name for your connection in the **Screen Name** field.
|
||||||
|
1. Click the **Submit** button on Automatisch.
|
||||||
|
1. Congrats! Start using your new Todoist connection within the flows.
|
12
packages/docs/pages/apps/todoist/triggers.md
Normal file
12
packages/docs/pages/apps/todoist/triggers.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
favicon: /favicons/todoist.svg
|
||||||
|
items:
|
||||||
|
- name: Get Tasks
|
||||||
|
desc: Finds tasks in Todoist, optionally matching specified parameters.
|
||||||
|
---
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import CustomListing from '../../components/CustomListing.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<CustomListing />
|
@@ -4,7 +4,7 @@
|
|||||||
This page explains the steps you need to follow to set up the Twitter connection in Automatisch. If any of the steps are outdated, please let us know!
|
This page explains the steps you need to follow to set up the Twitter connection in Automatisch. If any of the steps are outdated, please let us know!
|
||||||
:::
|
:::
|
||||||
|
|
||||||
1. Go to the [Twitter Developer Portal](https://developer.twitter.com/en/portal/projects-and-apps),complete the questionnaire and click the **Let's do this** button.
|
1. Go to the [Twitter Developer Portal](https://developer.twitter.com/en/portal/projects-and-apps), complete the questionnaire and click the **Let's do this** button.
|
||||||
2. Accept terms & conditions on the following page and click **Submit**.
|
2. Accept terms & conditions on the following page and click **Submit**.
|
||||||
|
|
||||||
:::warning
|
:::warning
|
||||||
|
@@ -49,6 +49,10 @@ The build integrations section is best understood when read from beginning to en
|
|||||||
|
|
||||||
### Webhook-based triggers
|
### Webhook-based triggers
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
If you are developing a webhook-based trigger, you need to ensure that the webhook is publicly accessible. You can use [ngrok](https://ngrok.com) for this purpose and override the webhook URL by setting the **WEBHOOK_URL** environment variable.
|
||||||
|
:::
|
||||||
|
|
||||||
- [New entry - Typeform](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/typeform/triggers/new-entry/index.ts)
|
- [New entry - Typeform](https://github.com/automatisch/automatisch/tree/main/packages/backend/src/apps/typeform/triggers/new-entry/index.ts)
|
||||||
|
|
||||||
### Pagination with descending order
|
### Pagination with descending order
|
||||||
|
@@ -7,6 +7,7 @@ We just have a few available integrations at the moment and we also know that wo
|
|||||||
Following integrations are currently supported by Automatisch.
|
Following integrations are currently supported by Automatisch.
|
||||||
|
|
||||||
- [DeepL](/apps/deepl/actions)
|
- [DeepL](/apps/deepl/actions)
|
||||||
|
- [Delay](/apps/delay/actions)
|
||||||
- [Discord](/apps/discord/actions)
|
- [Discord](/apps/discord/actions)
|
||||||
- [Flickr](/apps/flickr/triggers)
|
- [Flickr](/apps/flickr/triggers)
|
||||||
- [Github](/apps/github/triggers)
|
- [Github](/apps/github/triggers)
|
||||||
@@ -18,6 +19,8 @@ Following integrations are currently supported by Automatisch.
|
|||||||
- [SMTP](/apps/smtp/actions)
|
- [SMTP](/apps/smtp/actions)
|
||||||
- [Stripe](/apps/stripe/triggers)
|
- [Stripe](/apps/stripe/triggers)
|
||||||
- [Telegram](/apps/telegram-bot/actions)
|
- [Telegram](/apps/telegram-bot/actions)
|
||||||
|
- [Todoist](/apps/todoist/triggers)
|
||||||
- [Twilio](/apps/twilio/triggers)
|
- [Twilio](/apps/twilio/triggers)
|
||||||
- [Twitter](/apps/twitter/triggers)
|
- [Twitter](/apps/twitter/triggers)
|
||||||
- [Typeform](/apps/typeform/triggers)
|
- [Typeform](/apps/typeform/triggers)
|
||||||
|
- [Webhooks](/apps/webhooks/triggers)
|
||||||
|
@@ -1 +1,53 @@
|
|||||||
TBD
|
# Create Flow
|
||||||
|
|
||||||
|
To understand how we can create a flow, it's better to start with a real use case. Let's say we want to create a flow that will fetch new submissions from Typeform and then send them to a Slack channel. To do that, we will use [Typeform](/apps/typeform/triggers) and [Slack](/apps/slack/actions) apps. Let's start with creating connections for these apps.
|
||||||
|
|
||||||
|
## Typeform connection
|
||||||
|
|
||||||
|
- Go to the **My Apps** page in Automatisch and click on **Add connection** button.
|
||||||
|
- Select the **Typeform** app from the list.
|
||||||
|
- It will ask you `Client ID` and `Client Secret` from Typeform and there is an information box above the fields.
|
||||||
|
- Click on **our documentation** link in the information box and follow the instructions to get the `Client ID` and `Client Secret` from Typeform.
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Whenever you want to create a connection for an app, you can click on **our documentation** link in the information box to learn how to create a connection for that specific app.
|
||||||
|
:::
|
||||||
|
|
||||||
|
- After you get the `Client ID` and `Client Secret` from Typeform, you can paste them to the fields in Automatisch and click on **Submit** button.
|
||||||
|
|
||||||
|
## Slack connection
|
||||||
|
|
||||||
|
- Go to the **My Apps** page in Automatisch and click on **Add connection** button.
|
||||||
|
- Select the **Slack** app from the list.
|
||||||
|
- It will ask you `API Key` and `API Secret` values from Slack and there is an information box above the fields.
|
||||||
|
- Click on **our documentation** link in the information box and follow the instructions to get the `API Key` and `API Secret` from Slack.
|
||||||
|
- After you get the `API Key` and `API Secret` from Slack, you can paste them into the fields in Automatisch and click on **Submit** button.
|
||||||
|
|
||||||
|
## Build the flow
|
||||||
|
|
||||||
|
### Trigger step
|
||||||
|
|
||||||
|
- Go to the **Flows** page in Automatisch and click on **Create flow** button.
|
||||||
|
- It will give you empty trigger and action steps.
|
||||||
|
- For the trigger step (1st step), select the **Typeform** app from `Choose an app` dropdown.
|
||||||
|
- Select the **New entry** as the trigger event and click on the **Continue** button.
|
||||||
|
- It will ask you to select the connection you created for the Typeform app. Select the connection you have just created and click on the **Continue** button.
|
||||||
|
- Select the form you want to get the new entries from and click on the **Continue** button.
|
||||||
|
- Click on **Test & Continue** button to test the trigger step. If you see the data that reflects the recent submission in the form, you can continue to the next (action) step.
|
||||||
|
|
||||||
|
### Action step
|
||||||
|
|
||||||
|
- For the action step (2nd step), select the **Slack** app from `Choose an app` dropdown.
|
||||||
|
- Select the **Send a message to channel** as the action event and click on the **Continue** button.
|
||||||
|
- It will ask you to select the connection you created for the Slack app. Select the connection you have just created and click on the **Continue** button.
|
||||||
|
- Select the channel you want to send the message to.
|
||||||
|
- Write the message you want to send to the channel. You can use variables in the message from the trigger step.
|
||||||
|
- Select `Yes` for the `Send as a bot` option.
|
||||||
|
- Give a name for the bot and click on the **Continue** button.
|
||||||
|
- Click on **Test & Continue** button to test the action step. If you see the message in the Slack channel you selected, we can say that the flow is working as expected and is ready to be published.
|
||||||
|
|
||||||
|
### Publish the flow
|
||||||
|
|
||||||
|
- Click on the **Publish** button to publish the flow.
|
||||||
|
- Published flows will be executed automatically when the trigger event happens or at intervals of 15 minutes depending on the trigger type.
|
||||||
|
- You can not change the flow after it's published. If you want to change the flow, you need to unpublish it first and then make the changes.
|
||||||
|
@@ -74,6 +74,18 @@ REDIS_TLS=
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
## Render
|
||||||
|
|
||||||
|
<a href="https://render.com/deploy?repo=https://github.com/automatisch/automatisch">
|
||||||
|
<img src="https://render.com/images/deploy-to-render-button.svg" alt="Deploy to Render">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
:::info
|
||||||
|
|
||||||
|
We use default values of render plans with the `render.yaml` file, if you want to use the free plan or change the plan, you can change the `render.yaml` file in your fork and use your repository URL while creating a blueprint in Render.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## Production setup
|
## Production setup
|
||||||
|
|
||||||
If you need to change any other environment variables for your production setup, let's check out the [environment variables](/advanced/configuration#environment-variables) section of the configuration page.
|
If you need to change any other environment variables for your production setup, let's check out the [environment variables](/advanced/configuration#environment-variables) section of the configuration page.
|
||||||
|
@@ -28,7 +28,7 @@ You need to prepare the workflow once, and it will run continuously until you st
|
|||||||
|
|
||||||
There are other existing solutions in the market, like Zapier and Integromat, so you might be wondering why you should use Automatisch.
|
There are other existing solutions in the market, like Zapier and Integromat, so you might be wondering why you should use Automatisch.
|
||||||
|
|
||||||
✅ The most significant advantage of having Automatisch is **keeping your data on your own servers**. Not all companies want to use an automation service in the cloud, and the current open-source or self-hosted solutions mainly focus on developers rather than a user without a technical background.
|
✅ One of the main benefits of using Automatisch is that it allows you to **store your data on your own servers**, which is essential for businesses that handle sensitive user information and cannot risk sharing it with external cloud services. This is especially relevant for industries such as healthcare and finance, as well as for European companies that must adhere to the General Data Protection Regulation (GDPR).
|
||||||
|
|
||||||
🤓 Your contributions are vital to the development of Automatisch. As an **open-source software**, anyone can have an impact on how it is being developed.
|
🤓 Your contributions are vital to the development of Automatisch. As an **open-source software**, anyone can have an impact on how it is being developed.
|
||||||
|
|
||||||
|
7
packages/docs/pages/public/favicons/delay.svg
Normal file
7
packages/docs/pages/public/favicons/delay.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 122.88 100.6" style="enable-background:new 0 0 122.88 100.6" xml:space="preserve">
|
||||||
|
<style type="text/css">.st0{fill:#272727;} .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#000000;}</style>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M72.58,0c6.8,0,13.3,1.36,19.23,3.81c6.16,2.55,11.7,6.29,16.33,10.92l0,0c4.63,4.63,8.37,10.17,10.92,16.34 c2.46,5.93,3.81,12.43,3.81,19.23c0,6.8-1.36,13.3-3.81,19.23c-2.55,6.16-6.29,11.7-10.92,16.33l0,0 c-4.63,4.63-10.17,8.37-16.34,10.92c-5.93,2.46-12.43,3.81-19.23,3.81c-6.8,0-13.3-1.36-19.23-3.81 c-6.15-2.55-11.69-6.28-16.33-10.92l-0.01-0.01c-4.64-4.64-8.37-10.17-10.92-16.33c-0.79-1.91-1.47-3.87-2.02-5.89 c1.05,0.1,2.12,0.15,3.2,0.15c2.05,0,4.05-0.19,6-0.54c0.32,0.97,0.67,1.93,1.06,2.87c2.09,5.05,5.17,9.6,8.99,13.43 c3.82,3.82,8.38,6.9,13.43,8.99c4.87,2.02,10.21,3.13,15.83,3.13c5.62,0,10.96-1.11,15.83-3.13c5.05-2.09,9.6-5.17,13.43-8.99 c3.82-3.82,6.9-8.38,8.99-13.43c2.02-4.87,3.13-10.21,3.13-15.83c0-5.62-1.11-10.96-3.13-15.83c-2.09-5.05-5.17-9.6-8.99-13.43 c-3.82-3.82-8.38-6.9-13.43-8.99c-4.87-2.02-10.21-3.13-15.83-3.13c-5.62,0-10.96,1.11-15.83,3.13c-0.44,0.18-0.87,0.37-1.3,0.56 c-1.65-2.61-3.66-4.97-5.95-7.02c1.25-0.65,2.53-1.24,3.84-1.79C59.28,1.36,65.78,0,72.58,0L72.58,0z M66.8,26.39 c0-1.23,0.5-2.35,1.31-3.16c0.81-0.81,1.93-1.31,3.16-1.31c1.23,0,2.35,0.5,3.16,1.31c0.81,0.81,1.31,1.93,1.31,3.16v23.47 l17.54,10.4c1.05,0.62,1.76,1.62,2.05,2.73c0.28,1.1,0.15,2.31-0.47,3.37l0,0.01l0,0c-0.62,1.05-1.62,1.76-2.73,2.05 c-1.1,0.28-2.31,0.15-3.37-0.47l-0.01,0l0,0L69.1,56.29c-0.67-0.38-1.24-0.92-1.64-1.57c-0.42-0.68-0.66-1.48-0.66-2.32V26.39 L66.8,26.39z"/>
|
||||||
|
<path class="st1" d="M27.27,3.18c15.06,0,27.27,12.21,27.27,27.27c0,15.06-12.21,27.27-27.27,27.27C12.21,57.73,0,45.52,0,30.45 C0,15.39,12.21,3.18,27.27,3.18L27.27,3.18z M24.35,41.34h5.82v5.16h-5.82V41.34L24.35,41.34L24.35,41.34z M30.17,37.77h-5.82 c-0.58-7.07-1.8-11.56-1.8-18.63c0-2.61,2.12-4.72,4.72-4.72c2.61,0,4.72,2.12,4.72,4.72C32,26.2,30.76,30.7,30.17,37.77 L30.17,37.77L30.17,37.77L30.17,37.77z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
14
packages/docs/pages/public/favicons/todoist.svg
Normal file
14
packages/docs/pages/public/favicons/todoist.svg
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||||
|
<g>
|
||||||
|
<path d="M224.001997,0 L31.9980026,0 C14.3579381,0.0394964443 0.0614809418,14.336846 0,32 L0,224 C0,241.6 14.3971038,256 31.9980026,256 L224.001997,256 C241.602896,256 256,241.6 256,224 L256,32 C256,14.4 241.602896,0 224.001997,0" fill="#E44332">
|
||||||
|
|
||||||
|
</path>
|
||||||
|
<path d="M54.132778,120.802491 C58.5960224,118.196275 154.476075,62.477451 156.667847,61.1862981 C158.859619,59.9110855 158.97917,55.9898065 156.508446,54.5711324 C154.053661,53.1604284 149.391165,50.4824817 147.661658,49.4543415 C145.192242,48.0957707 142.191169,48.132074 139.755339,49.5499825 C138.527947,50.2672896 56.6035026,97.8486625 53.8697654,99.4107981 C50.5781227,101.291737 46.5372925,101.323617 43.2695601,99.4107981 L0,74.0181257 L0,95.6011002 C10.5205046,101.801822 36.7181549,117.200015 43.062338,120.826401 C46.8481256,122.978322 50.4745117,122.930502 54.1407481,120.802491" fill="#FFFFFF">
|
||||||
|
|
||||||
|
</path>
|
||||||
|
<path d="M54.132778,161.609296 C58.5960224,159.00308 154.476075,103.284257 156.667847,101.993104 C158.859619,100.717891 158.97917,96.7966121 156.508446,95.377938 C154.053661,93.9672339 149.391165,91.2892873 147.661658,90.2611471 C145.192242,88.9025763 142.191169,88.9388796 139.755339,90.3567881 C138.527947,91.0740952 56.6035026,138.655468 53.8697654,140.217604 C50.5781227,142.098542 46.5372925,142.130423 43.2695601,140.217604 L0,114.824931 L0,136.407906 C10.5205046,142.608627 36.7181549,158.00682 43.062338,161.633206 C46.8481256,163.785128 50.4745117,163.737307 54.1407481,161.609296" fill="#FFFFFF">
|
||||||
|
|
||||||
|
</path>
|
||||||
|
<path d="M54.132778,204.966527 C58.5960224,202.360311 154.476075,146.641487 156.667847,145.350335 C158.859619,144.075122 158.97917,140.153843 156.508446,138.735169 C154.053661,137.324465 149.391165,134.646518 147.661658,133.618378 C145.192242,132.259807 142.191169,132.29611 139.755339,133.714019 C138.527947,134.431326 56.6035026,182.012699 53.8697654,183.574835 C50.5781227,185.455773 46.5372925,185.487654 43.2695601,183.574835 L0,158.182162 L0,179.765137 C10.5205046,185.965858 36.7181549,201.364051 43.062338,204.990437 C46.8481256,207.142359 50.4745117,207.094538 54.1407481,204.966527" fill="#FFFFFF">
|
After Width: | Height: | Size: 2.4 KiB |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/e2e-tests",
|
"name": "@automatisch/e2e-tests",
|
||||||
"version": "0.3.0",
|
"version": "0.4.0",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"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.",
|
||||||
|
3
packages/types/index.d.ts
vendored
3
packages/types/index.d.ts
vendored
@@ -76,6 +76,7 @@ export interface IFlow {
|
|||||||
steps: IStep[];
|
steps: IStep[];
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
|
remoteWebhookId: string;
|
||||||
lastInternalId: () => Promise<string>;
|
lastInternalId: () => Promise<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,6 +280,8 @@ export type IGlobalVariable = {
|
|||||||
id: string;
|
id: string;
|
||||||
lastInternalId: string;
|
lastInternalId: string;
|
||||||
isAlreadyProcessed?: (internalId: string) => boolean;
|
isAlreadyProcessed?: (internalId: string) => boolean;
|
||||||
|
remoteWebhookId?: string;
|
||||||
|
setRemoteWebhookId?: (remoteWebhookId: string) => Promise<void>;
|
||||||
};
|
};
|
||||||
step?: {
|
step?: {
|
||||||
id: string;
|
id: string;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/types",
|
"name": "@automatisch/types",
|
||||||
"version": "0.3.0",
|
"version": "0.4.0",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"description": "Type definitions for automatisch",
|
"description": "Type definitions for automatisch",
|
||||||
"homepage": "https://github.com/automatisch/automatisch",
|
"homepage": "https://github.com/automatisch/automatisch",
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/web",
|
"name": "@automatisch/web",
|
||||||
"version": "0.3.0",
|
"version": "0.4.0",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.6.9",
|
"@apollo/client": "^3.6.9",
|
||||||
"@automatisch/types": "^0.3.0",
|
"@automatisch/types": "^0.4.0",
|
||||||
"@emotion/react": "^11.4.1",
|
"@emotion/react": "^11.4.1",
|
||||||
"@emotion/styled": "^11.3.0",
|
"@emotion/styled": "^11.3.0",
|
||||||
"@hookform/resolvers": "^2.8.8",
|
"@hookform/resolvers": "^2.8.8",
|
||||||
|
@@ -116,7 +116,11 @@ export default function AddAppConnection(
|
|||||||
sx={{ mt: 1, fontWeight: 500, wordBreak: 'break-all' }}
|
sx={{ mt: 1, fontWeight: 500, wordBreak: 'break-all' }}
|
||||||
>
|
>
|
||||||
{error.message}
|
{error.message}
|
||||||
<pre>{JSON.stringify(error.details, null, 2)}</pre>
|
{error.details && (
|
||||||
|
<pre style={{ whiteSpace: 'pre-wrap' }}>
|
||||||
|
{JSON.stringify(error.details, null, 2)}
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@@ -18,6 +18,7 @@ import ListItemText from '@mui/material/ListItemText';
|
|||||||
import InputLabel from '@mui/material/InputLabel';
|
import InputLabel from '@mui/material/InputLabel';
|
||||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||||
import FormControl from '@mui/material/FormControl';
|
import FormControl from '@mui/material/FormControl';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
import type { IApp } from '@automatisch/types';
|
import type { IApp } from '@automatisch/types';
|
||||||
|
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
@@ -76,7 +77,7 @@ export default function AddNewAppConnection(
|
|||||||
<Dialog open={true} onClose={onClose} maxWidth="sm" fullWidth>
|
<Dialog open={true} onClose={onClose} maxWidth="sm" fullWidth>
|
||||||
<DialogTitle>{formatMessage('apps.addNewAppConnection')}</DialogTitle>
|
<DialogTitle>{formatMessage('apps.addNewAppConnection')}</DialogTitle>
|
||||||
|
|
||||||
<DialogContent>
|
<Box px={3}>
|
||||||
<FormControl
|
<FormControl
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -103,7 +104,9 @@ export default function AddNewAppConnection(
|
|||||||
data-test="search-for-app-text-field"
|
data-test="search-for-app-text-field"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<DialogContent>
|
||||||
<List sx={{ pt: 2, width: '100%' }}>
|
<List sx={{ pt: 2, width: '100%' }}>
|
||||||
{loading && (
|
{loading && (
|
||||||
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} />
|
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} />
|
||||||
|
@@ -7,6 +7,7 @@ import Collapse from '@mui/material/Collapse';
|
|||||||
import ListItem from '@mui/material/ListItem';
|
import ListItem from '@mui/material/ListItem';
|
||||||
import TextField from '@mui/material/TextField';
|
import TextField from '@mui/material/TextField';
|
||||||
import Autocomplete from '@mui/material/Autocomplete';
|
import Autocomplete from '@mui/material/Autocomplete';
|
||||||
|
import Chip from '@mui/material/Chip';
|
||||||
|
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import { EditorContext } from 'contexts/Editor';
|
import { EditorContext } from 'contexts/Editor';
|
||||||
@@ -38,8 +39,20 @@ const optionGenerator = (app: {
|
|||||||
value: app.key as string,
|
value: app.key as string,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getOption = (options: Record<string, unknown>[], appKey?: IApp['key']) =>
|
const eventOptionGenerator = (app: {
|
||||||
options.find((option) => option.value === (appKey as string)) || null;
|
name: string;
|
||||||
|
key: string;
|
||||||
|
type?: string;
|
||||||
|
}): { label: string; value: string; type: string } => ({
|
||||||
|
label: app.name as string,
|
||||||
|
value: app.key as string,
|
||||||
|
type: app?.type as string,
|
||||||
|
});
|
||||||
|
|
||||||
|
const getOption = <T extends { value: string }>(
|
||||||
|
options: T[],
|
||||||
|
selectedOptionValue?: string
|
||||||
|
) => options.find((option) => option.value === selectedOptionValue);
|
||||||
|
|
||||||
function ChooseAppAndEventSubstep(
|
function ChooseAppAndEventSubstep(
|
||||||
props: ChooseAppAndEventSubstepProps
|
props: ChooseAppAndEventSubstepProps
|
||||||
@@ -72,14 +85,17 @@ function ChooseAppAndEventSubstep(
|
|||||||
);
|
);
|
||||||
const actionsOrTriggers: Array<ITrigger | IAction> =
|
const actionsOrTriggers: Array<ITrigger | IAction> =
|
||||||
(isTrigger ? app?.triggers : app?.actions) || [];
|
(isTrigger ? app?.triggers : app?.actions) || [];
|
||||||
const actionOptions = React.useMemo(
|
const actionOrTriggerOptions = React.useMemo(
|
||||||
() => actionsOrTriggers.map((trigger) => optionGenerator(trigger)),
|
() => actionsOrTriggers.map((trigger) => eventOptionGenerator(trigger)),
|
||||||
[app?.key]
|
[app?.key]
|
||||||
);
|
);
|
||||||
const selectedActionOrTrigger = actionsOrTriggers.find(
|
const selectedActionOrTrigger = actionsOrTriggers.find(
|
||||||
(actionOrTrigger: IAction | ITrigger) => actionOrTrigger.key === step?.key
|
(actionOrTrigger: IAction | ITrigger) => actionOrTrigger.key === step?.key
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isWebhook =
|
||||||
|
isTrigger && (selectedActionOrTrigger as ITrigger)?.type === 'webhook';
|
||||||
|
|
||||||
const { name } = substep;
|
const { name } = substep;
|
||||||
|
|
||||||
const valid: boolean = !!step.key && !!step.appKey;
|
const valid: boolean = !!step.key && !!step.appKey;
|
||||||
@@ -177,14 +193,49 @@ function ChooseAppAndEventSubstep(
|
|||||||
disablePortal
|
disablePortal
|
||||||
disableClearable
|
disableClearable
|
||||||
disabled={editorContext.readOnly}
|
disabled={editorContext.readOnly}
|
||||||
options={actionOptions}
|
options={actionOrTriggerOptions}
|
||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
<TextField
|
<TextField
|
||||||
{...params}
|
{...params}
|
||||||
label={formatMessage('flowEditor.chooseEvent')}
|
label={formatMessage('flowEditor.chooseEvent')}
|
||||||
|
InputProps={{
|
||||||
|
...params.InputProps,
|
||||||
|
endAdornment: (
|
||||||
|
<React.Fragment>
|
||||||
|
{isWebhook && (
|
||||||
|
<Chip
|
||||||
|
label={formatMessage(
|
||||||
|
'flowEditor.instantTriggerType'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{params.InputProps.endAdornment}
|
||||||
|
</React.Fragment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
value={getOption(actionOptions, step.key)}
|
renderOption={(optionProps, option) => (
|
||||||
|
<li
|
||||||
|
{...optionProps}
|
||||||
|
key={option.value.toString()}
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography>{option.label}</Typography>
|
||||||
|
|
||||||
|
{option.type === 'webhook' && (
|
||||||
|
<Chip
|
||||||
|
label={formatMessage('flowEditor.instantTriggerType')}
|
||||||
|
sx={{ mr: 3 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
value={getOption(actionOrTriggerOptions, step.key)}
|
||||||
onChange={onEventChange}
|
onChange={onEventChange}
|
||||||
data-test="choose-event-autocomplete"
|
data-test="choose-event-autocomplete"
|
||||||
/>
|
/>
|
||||||
|
@@ -32,7 +32,7 @@ function serializeErrors(graphQLErrors: any) {
|
|||||||
return {
|
return {
|
||||||
...error,
|
...error,
|
||||||
message: (
|
message: (
|
||||||
<pre style={{ margin: 0 }}>
|
<pre style={{ margin: 0, whiteSpace: 'pre-wrap' }}>
|
||||||
{JSON.stringify(JSON.parse(error.message as string), null, 2)}
|
{JSON.stringify(JSON.parse(error.message as string), null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
),
|
),
|
||||||
@@ -108,7 +108,7 @@ function TestSubstep(props: TestSubstepProps): React.ReactElement {
|
|||||||
{!!error?.graphQLErrors?.length && (
|
{!!error?.graphQLErrors?.length && (
|
||||||
<Alert
|
<Alert
|
||||||
severity="error"
|
severity="error"
|
||||||
sx={{ mb: 1, fontWeight: 500, width: '100%' }}
|
sx={{ mb: 2, fontWeight: 500, width: '100%' }}
|
||||||
>
|
>
|
||||||
{serializeErrors(error.graphQLErrors).map((error: any) => (
|
{serializeErrors(error.graphQLErrors).map((error: any) => (
|
||||||
<div>{error.message}</div>
|
<div>{error.message}</div>
|
||||||
@@ -116,7 +116,9 @@ function TestSubstep(props: TestSubstepProps): React.ReactElement {
|
|||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{step.webhookUrl && <WebhookUrlInfo webhookUrl={step.webhookUrl} />}
|
{step.webhookUrl && (
|
||||||
|
<WebhookUrlInfo webhookUrl={step.webhookUrl} sx={{ mb: 2 }} />
|
||||||
|
)}
|
||||||
|
|
||||||
{hasNoOutput && (
|
{hasNoOutput && (
|
||||||
<Alert severity="warning" sx={{ mb: 1, width: '100%' }}>
|
<Alert severity="warning" sx={{ mb: 1, width: '100%' }}>
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
import type { AlertProps } from '@mui/material/Alert';
|
||||||
|
|
||||||
import { generateExternalLink } from '../../helpers/translation-values';
|
import { generateExternalLink } from '../../helpers/translation-values';
|
||||||
import { WEBHOOK_DOCS } from '../../config/urls';
|
import { WEBHOOK_DOCS } from '../../config/urls';
|
||||||
@@ -9,13 +10,13 @@ import { Alert } from './style';
|
|||||||
|
|
||||||
type WebhookUrlInfoProps = {
|
type WebhookUrlInfoProps = {
|
||||||
webhookUrl: string;
|
webhookUrl: string;
|
||||||
};
|
} & AlertProps;
|
||||||
|
|
||||||
function WebhookUrlInfo(props: WebhookUrlInfoProps): React.ReactElement {
|
function WebhookUrlInfo(props: WebhookUrlInfoProps): React.ReactElement {
|
||||||
const { webhookUrl } = props;
|
const { webhookUrl, ...alertProps } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert icon={false} color="info">
|
<Alert icon={false} color="info" {...alertProps}>
|
||||||
<Typography variant="body2" textAlign="center">
|
<Typography variant="body2" textAlign="center">
|
||||||
<FormattedMessage id="webhookUrlInfo.title" />
|
<FormattedMessage id="webhookUrlInfo.title" />
|
||||||
</Typography>
|
</Typography>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user