diff --git a/.eslintignore b/.eslintignore
index d8443a88..11c7540f 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -3,6 +3,7 @@ dist
build
coverage
packages/docs/*
+packages/e2e-tests
.eslintrc.js
husky.config.js
diff --git a/.gitignore b/.gitignore
index 0125458e..ba7f9dc3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -74,6 +74,15 @@ web_modules/
.env.test
.env.production
+# cypress environment variables file
+cypress.env.json
+
+# cypress screenshots
+packages/e2e-tests/cypress/screenshots
+
+# cypress videos
+packages/e2e-tests/cypress/videos/
+
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
diff --git a/README.md b/README.md
index 5976538a..a55216f5 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ There are other existing solutions in the market, like Zapier and Integromat, so
## Documentation
-The official documentation can be found here: [https://automatisch.io/docs](https://automatisch.io/docs)
+The official documentation can be found here: [https://automatisch.io/docs/](https://automatisch.io/docs/)
## Installation
@@ -47,10 +47,6 @@ If you have any questions or problems, please visit our GitHub discussions page,
[https://github.com/automatisch/automatisch/discussions](https://github.com/automatisch/automatisch/discussions)
-## Contribution Guide
-
-You can access to the [contribution guide](https://docs.automatisch.io) page from the documentation website.
-
## License
Automatisch is an open-source software with an [AGPL 3.0 license](https://github.com/automatisch/automatisch/blob/main/LICENSE.md).
diff --git a/docker/images/plain/Dockerfile b/docker/images/plain/Dockerfile
index 71657d57..48129e55 100644
--- a/docker/images/plain/Dockerfile
+++ b/docker/images/plain/Dockerfile
@@ -2,4 +2,4 @@
FROM node:16
WORKDIR /automatisch
-RUN yarn global add @automatisch/cli@0.1.4
+RUN yarn global add @automatisch/cli@0.1.5
diff --git a/docker/images/wait-for-postgres/Dockerfile b/docker/images/wait-for-postgres/Dockerfile
index 1ef998f2..33c66b6d 100644
--- a/docker/images/wait-for-postgres/Dockerfile
+++ b/docker/images/wait-for-postgres/Dockerfile
@@ -9,7 +9,7 @@ RUN mkdir -p /automatisch/storage
RUN touch /automatisch/storage/.env
RUN echo "ENCRYPTION_KEY=$(openssl rand -base64 36)" >> /automatisch/storage/.env
RUN echo "APP_SECRET_KEY=$(openssl rand -base64 36)" >> /automatisch/storage/.env
-RUN yarn global add @automatisch/cli@0.1.4
+RUN yarn global add @automatisch/cli@0.1.5
EXPOSE 3000
CMD sh /automatisch/wait-for-postgres.sh automatisch start --env-file=/automatisch/storage/.env
diff --git a/lerna.json b/lerna.json
index 6766f74a..d68132d1 100644
--- a/lerna.json
+++ b/lerna.json
@@ -2,7 +2,7 @@
"packages": [
"packages/*"
],
- "version": "0.1.4",
+ "version": "0.1.5",
"npmClient": "yarn",
"useWorkspaces": true,
"command": {
diff --git a/packages/backend/package.json b/packages/backend/package.json
index c29a3398..03ec212a 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@automatisch/backend",
- "version": "0.1.4",
+ "version": "0.1.5",
"license": "AGPL-3.0",
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
"scripts": {
@@ -22,7 +22,7 @@
"prebuild": "rm -rf ./dist"
},
"dependencies": {
- "@automatisch/web": "^0.1.4",
+ "@automatisch/web": "^0.1.5",
"@bull-board/express": "^3.10.1",
"@gitbeaker/node": "^35.6.0",
"@graphql-tools/graphql-file-loader": "^7.3.4",
@@ -100,7 +100,7 @@
"url": "https://github.com/automatisch/automatisch/issues"
},
"devDependencies": {
- "@automatisch/types": "^0.1.4",
+ "@automatisch/types": "^0.1.5",
"@types/bcrypt": "^5.0.0",
"@types/bull": "^3.15.8",
"@types/cors": "^2.8.12",
diff --git a/packages/backend/src/apps/discord/assets/favicon.svg b/packages/backend/src/apps/discord/assets/favicon.svg
deleted file mode 100644
index 0483a9d3..00000000
--- a/packages/backend/src/apps/discord/assets/favicon.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
\ No newline at end of file
diff --git a/packages/backend/src/apps/discord/authentication.ts b/packages/backend/src/apps/discord/authentication.ts
deleted file mode 100644
index ebcc8239..00000000
--- a/packages/backend/src/apps/discord/authentication.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-import type {
- IAuthentication,
- IApp,
- IField,
- IJSONObject,
-} from '@automatisch/types';
-import { URLSearchParams } from 'url';
-import axios, { AxiosInstance } from 'axios';
-
-export default class Authentication implements IAuthentication {
- appData: IApp;
- connectionData: IJSONObject;
- client: AxiosInstance = axios.create({
- baseURL: 'https://discord.com/api/',
- });
-
- scope: string[] = ['identify', 'email'];
-
- constructor(appData: IApp, connectionData: IJSONObject) {
- this.appData = appData;
- this.connectionData = connectionData;
- }
-
- get oauthRedirectUrl() {
- return this.appData.fields.find(
- (field: IField) => field.key == 'oAuthRedirectUrl'
- ).value;
- }
-
- async createAuthData() {
- const searchParams = new URLSearchParams({
- client_id: this.connectionData.consumerKey as string,
- redirect_uri: this.oauthRedirectUrl,
- response_type: 'code',
- scope: this.scope.join(' '),
- });
-
- const url = `https://discord.com/api/oauth2/authorize?${searchParams.toString()}`;
-
- return { url };
- }
-
- async verifyCredentials() {
- const params = new URLSearchParams({
- client_id: this.connectionData.consumerKey as string,
- redirect_uri: this.oauthRedirectUrl,
- response_type: 'code',
- scope: this.scope.join(' '),
- client_secret: this.connectionData.consumerSecret as string,
- code: this.connectionData.oauthVerifier as string,
- grant_type: 'authorization_code',
- });
- const { data: verifiedCredentials } = await this.client.post(
- '/oauth2/token',
- params.toString()
- );
-
- const {
- access_token: accessToken,
- refresh_token: refreshToken,
- expires_in: expiresIn,
- scope: scope,
- token_type: tokenType,
- } = verifiedCredentials;
-
- const { data: user } = await this.client.get('/users/@me', {
- headers: {
- Authorization: `${tokenType} ${accessToken}`,
- },
- });
-
- return {
- consumerKey: this.connectionData.consumerKey,
- consumerSecret: this.connectionData.consumerSecret,
- accessToken,
- refreshToken,
- expiresIn,
- scope,
- tokenType,
- userId: user.id,
- screenName: user.username,
- email: user.email,
- };
- }
-
- async isStillVerified() {
- try {
- await this.client.get('/users/@me', {
- headers: {
- Authorization: `${this.connectionData.tokenType} ${this.connectionData.accessToken}`,
- },
- });
-
- return true;
- } catch {
- return false;
- }
- }
-}
diff --git a/packages/backend/src/apps/discord/index.d.ts b/packages/backend/src/apps/discord/index.d.ts
deleted file mode 100644
index e69de29b..00000000
diff --git a/packages/backend/src/apps/discord/index.ts b/packages/backend/src/apps/discord/index.ts
deleted file mode 100644
index 80de7f88..00000000
--- a/packages/backend/src/apps/discord/index.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import Authentication from './authentication';
-import {
- IService,
- IAuthentication,
- IApp,
- IJSONObject,
-} from '@automatisch/types';
-
-export default class Discord implements IService {
- authenticationClient: IAuthentication;
-
- constructor(appData: IApp, connectionData: IJSONObject) {
- this.authenticationClient = new Authentication(appData, connectionData);
- }
-}
diff --git a/packages/backend/src/apps/discord/info.json b/packages/backend/src/apps/discord/info.json
deleted file mode 100644
index fbcf9ac2..00000000
--- a/packages/backend/src/apps/discord/info.json
+++ /dev/null
@@ -1,219 +0,0 @@
-{
- "name": "Discord",
- "key": "discord",
- "iconUrl": "{BASE_URL}/apps/discord/assets/favicon.svg",
- "docUrl": "https://automatisch.io/docs/discord",
- "primaryColor": "5865f2",
- "supportsConnections": true,
- "fields": [
- {
- "key": "oAuthRedirectUrl",
- "label": "OAuth Redirect URL",
- "type": "string",
- "required": true,
- "readOnly": true,
- "value": "{WEB_APP_URL}/app/discord/connections/add",
- "placeholder": null,
- "description": "When asked to input an OAuth callback or redirect URL in Discord OAuth, enter the URL above.",
- "docUrl": "https://automatisch.io/docs/discord#oauth-redirect-url",
- "clickToCopy": true
- },
- {
- "key": "consumerKey",
- "label": "Consumer Key",
- "type": "string",
- "required": true,
- "readOnly": false,
- "value": null,
- "placeholder": null,
- "description": null,
- "docUrl": "https://automatisch.io/docs/discord#consumer-key",
- "clickToCopy": false
- },
- {
- "key": "consumerSecret",
- "label": "Consumer Secret",
- "type": "string",
- "required": true,
- "readOnly": false,
- "value": null,
- "placeholder": null,
- "description": null,
- "docUrl": "https://automatisch.io/docs/discord#consumer-secret",
- "clickToCopy": false
- }
- ],
- "authenticationSteps": [
- {
- "step": 1,
- "type": "mutation",
- "name": "createConnection",
- "arguments": [
- {
- "name": "key",
- "value": "{key}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "consumerKey",
- "value": "{fields.consumerKey}"
- },
- {
- "name": "consumerSecret",
- "value": "{fields.consumerSecret}"
- }
- ]
- }
- ]
- },
- {
- "step": 2,
- "type": "mutation",
- "name": "createAuthData",
- "arguments": [
- {
- "name": "id",
- "value": "{createConnection.id}"
- }
- ]
- },
- {
- "step": 3,
- "type": "openWithPopup",
- "name": "openAuthPopup",
- "arguments": [
- {
- "name": "url",
- "value": "{createAuthData.url}"
- }
- ]
- },
- {
- "step": 4,
- "type": "mutation",
- "name": "updateConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{createConnection.id}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "oauthVerifier",
- "value": "{openAuthPopup.code}"
- }
- ]
- }
- ]
- },
- {
- "step": 5,
- "type": "mutation",
- "name": "verifyConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{createConnection.id}"
- }
- ]
- }
- ],
- "reconnectionSteps": [
- {
- "step": 1,
- "type": "mutation",
- "name": "resetConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- }
- ]
- },
- {
- "step": 2,
- "type": "mutation",
- "name": "updateConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "consumerKey",
- "value": "{fields.consumerKey}"
- },
- {
- "name": "consumerSecret",
- "value": "{fields.consumerSecret}"
- }
- ]
- }
- ]
- },
- {
- "step": 3,
- "type": "mutation",
- "name": "createAuthData",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- }
- ]
- },
- {
- "step": 4,
- "type": "openWithPopup",
- "name": "openAuthPopup",
- "arguments": [
- {
- "name": "url",
- "value": "{createAuthData.url}"
- }
- ]
- },
- {
- "step": 5,
- "type": "mutation",
- "name": "updateConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "oauthVerifier",
- "value": "{openAuthPopup.code}"
- }
- ]
- }
- ]
- },
- {
- "step": 6,
- "type": "mutation",
- "name": "verifyConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- }
- ]
- }
- ]
-}
diff --git a/packages/backend/src/apps/firebase/assets/favicon.svg b/packages/backend/src/apps/firebase/assets/favicon.svg
deleted file mode 100644
index bd2ee11b..00000000
--- a/packages/backend/src/apps/firebase/assets/favicon.svg
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
diff --git a/packages/backend/src/apps/firebase/authentication.ts b/packages/backend/src/apps/firebase/authentication.ts
deleted file mode 100644
index 921598ac..00000000
--- a/packages/backend/src/apps/firebase/authentication.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-import type {
- IAuthentication,
- IApp,
- IField,
- IJSONObject,
-} from '@automatisch/types';
-import { google as GoogleApi } from 'googleapis';
-import { OAuth2Client } from 'google-auth-library';
-
-export default class Authentication implements IAuthentication {
- appData: IApp;
- connectionData: IJSONObject;
- client: OAuth2Client;
-
- scopes: string[] = [
- 'https://www.googleapis.com/auth/datastore',
- 'https://www.googleapis.com/auth/firebase',
- 'https://www.googleapis.com/auth/user.emails.read',
- 'profile',
- ];
-
- constructor(appData: IApp, connectionData: IJSONObject) {
- this.appData = appData;
- this.connectionData = connectionData;
-
- this.client = new GoogleApi.auth.OAuth2(
- connectionData.consumerKey as string,
- connectionData.consumerSecret as string,
- this.oauthRedirectUrl
- );
-
- GoogleApi.options({ auth: this.client });
- }
-
- get oauthRedirectUrl() {
- return this.appData.fields.find(
- (field: IField) => field.key == 'oAuthRedirectUrl'
- ).value;
- }
-
- async createAuthData() {
- const url = this.client.generateAuthUrl({
- access_type: 'offline',
- scope: this.scopes,
- });
-
- return { url };
- }
-
- async verifyCredentials() {
- const { tokens } = await this.client.getToken(
- this.connectionData.oauthVerifier as string
- );
- this.client.setCredentials(tokens);
-
- const people = GoogleApi.people('v1');
-
- const { data } = await people.people.get({
- resourceName: 'people/me',
- personFields: 'emailAddresses',
- });
-
- const { emailAddresses, resourceName: userId } = data;
- const primaryEmailAddress = emailAddresses.find(
- (emailAddress) => emailAddress.metadata.primary
- );
-
- return {
- consumerKey: this.connectionData.consumerKey,
- consumerSecret: this.connectionData.consumerSecret,
- accessToken: tokens.access_token,
- refreshToken: tokens.refresh_token,
- tokenType: tokens.token_type,
- expiryDate: tokens.expiry_date,
- scope: tokens.scope,
- screenName: primaryEmailAddress.value,
- userId,
- };
- }
-
- async isStillVerified() {
- try {
- await this.client.getTokenInfo(this.connectionData.accessToken as string);
- return true;
- } catch {
- return false;
- }
- }
-}
diff --git a/packages/backend/src/apps/firebase/index.d.ts b/packages/backend/src/apps/firebase/index.d.ts
deleted file mode 100644
index e69de29b..00000000
diff --git a/packages/backend/src/apps/firebase/index.ts b/packages/backend/src/apps/firebase/index.ts
deleted file mode 100644
index db7a01e4..00000000
--- a/packages/backend/src/apps/firebase/index.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import Authentication from './authentication';
-import {
- IService,
- IAuthentication,
- IApp,
- IJSONObject,
-} from '@automatisch/types';
-
-export default class Firebase implements IService {
- authenticationClient: IAuthentication;
-
- constructor(appData: IApp, connectionData: IJSONObject) {
- this.authenticationClient = new Authentication(appData, connectionData);
- }
-}
diff --git a/packages/backend/src/apps/firebase/info.json b/packages/backend/src/apps/firebase/info.json
deleted file mode 100644
index c3b8104c..00000000
--- a/packages/backend/src/apps/firebase/info.json
+++ /dev/null
@@ -1,219 +0,0 @@
-{
- "name": "Firebase",
- "key": "firebase",
- "iconUrl": "{BASE_URL}/apps/firebase/assets/favicon.svg",
- "docUrl": "https://automatisch.io/docs/firebase",
- "primaryColor": "ffca28",
- "supportsConnections": true,
- "fields": [
- {
- "key": "oAuthRedirectUrl",
- "label": "OAuth Redirect URL",
- "type": "string",
- "required": true,
- "readOnly": true,
- "value": "{WEB_APP_URL}/app/firebase/connections/add",
- "placeholder": null,
- "description": "When asked to input an OAuth callback or redirect URL in Firebase OAuth, enter the URL above.",
- "docUrl": "https://automatisch.io/docs/firebase#oauth-redirect-url",
- "clickToCopy": true
- },
- {
- "key": "consumerKey",
- "label": "Consumer Key",
- "type": "string",
- "required": true,
- "readOnly": false,
- "value": null,
- "placeholder": null,
- "description": null,
- "docUrl": "https://automatisch.io/docs/firebase#consumer-key",
- "clickToCopy": false
- },
- {
- "key": "consumerSecret",
- "label": "Consumer Secret",
- "type": "string",
- "required": true,
- "readOnly": false,
- "value": null,
- "placeholder": null,
- "description": null,
- "docUrl": "https://automatisch.io/docs/firebase#consumer-secret",
- "clickToCopy": false
- }
- ],
- "authenticationSteps": [
- {
- "step": 1,
- "type": "mutation",
- "name": "createConnection",
- "arguments": [
- {
- "name": "key",
- "value": "{key}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "consumerKey",
- "value": "{fields.consumerKey}"
- },
- {
- "name": "consumerSecret",
- "value": "{fields.consumerSecret}"
- }
- ]
- }
- ]
- },
- {
- "step": 2,
- "type": "mutation",
- "name": "createAuthData",
- "arguments": [
- {
- "name": "id",
- "value": "{createConnection.id}"
- }
- ]
- },
- {
- "step": 3,
- "type": "openWithPopup",
- "name": "openAuthPopup",
- "arguments": [
- {
- "name": "url",
- "value": "{createAuthData.url}"
- }
- ]
- },
- {
- "step": 4,
- "type": "mutation",
- "name": "updateConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{createConnection.id}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "oauthVerifier",
- "value": "{openAuthPopup.code}"
- }
- ]
- }
- ]
- },
- {
- "step": 5,
- "type": "mutation",
- "name": "verifyConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{createConnection.id}"
- }
- ]
- }
- ],
- "reconnectionSteps": [
- {
- "step": 1,
- "type": "mutation",
- "name": "resetConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- }
- ]
- },
- {
- "step": 2,
- "type": "mutation",
- "name": "updateConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "consumerKey",
- "value": "{fields.consumerKey}"
- },
- {
- "name": "consumerSecret",
- "value": "{fields.consumerSecret}"
- }
- ]
- }
- ]
- },
- {
- "step": 3,
- "type": "mutation",
- "name": "createAuthData",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- }
- ]
- },
- {
- "step": 4,
- "type": "openWithPopup",
- "name": "openAuthPopup",
- "arguments": [
- {
- "name": "url",
- "value": "{createAuthData.url}"
- }
- ]
- },
- {
- "step": 5,
- "type": "mutation",
- "name": "updateConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "oauthVerifier",
- "value": "{openAuthPopup.code}"
- }
- ]
- }
- ]
- },
- {
- "step": 6,
- "type": "mutation",
- "name": "verifyConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- }
- ]
- }
- ]
-}
diff --git a/packages/backend/src/apps/gitlab/assets/favicon.svg b/packages/backend/src/apps/gitlab/assets/favicon.svg
deleted file mode 100644
index 65b9e02c..00000000
--- a/packages/backend/src/apps/gitlab/assets/favicon.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
\ No newline at end of file
diff --git a/packages/backend/src/apps/gitlab/authentication.ts b/packages/backend/src/apps/gitlab/authentication.ts
deleted file mode 100644
index caa7c776..00000000
--- a/packages/backend/src/apps/gitlab/authentication.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-import { URLSearchParams } from 'url';
-import axios from 'axios';
-import crypto from 'crypto';
-import { Gitlab } from '@gitbeaker/node';
-import type {
- IAuthentication,
- IApp,
- IField,
- IJSONObject,
-} from '@automatisch/types';
-
-export default class Authentication implements IAuthentication {
- appData: IApp;
- connectionData: IJSONObject;
- client: any;
- scopes = [
- 'api',
- 'profile',
- 'email',
- ];
-
- constructor(appData: IApp, connectionData: IJSONObject) {
- this.connectionData = connectionData;
- this.appData = appData;
-
- if (connectionData?.accessToken) {
- this.client = new Gitlab({
- host: this.host,
- oauthToken: connectionData?.accessToken as string,
- });
- }
- }
-
- get host() {
- return `https://${this.connectionData.host}`;
- }
-
- get oauthRedirectUrl() {
- return this.appData.fields.find(
- (field: IField) => field.key == 'oAuthRedirectUrl'
- ).value;
- }
-
- async createAuthData() {
- const state = crypto.randomUUID();
-
- const searchParams = new URLSearchParams({
- client_id: this.connectionData.applicationId as string,
- redirect_uri: this.oauthRedirectUrl,
- response_type: 'code',
- scope: this.scopes.join(' '),
- state,
- });
-
- const url = `${this.host}/oauth/authorize?${searchParams.toString()}`;
-
- return { url };
- }
-
- async verifyCredentials() {
- const params = new URLSearchParams({
- client_id: this.connectionData.applicationId as string,
- grant_type: 'authorization_code',
- redirect_uri: this.oauthRedirectUrl,
- code: this.connectionData.oauthVerifier as string,
- client_secret: this.connectionData.secret as string,
- });
- const { data } = await axios.post(`${this.host}/oauth/token`, params.toString());
- const accessToken = data.access_token;
-
- const client = new Gitlab({
- host: this.host,
- oauthToken: accessToken,
- });
-
- const user = await client.Users.current();
-
- return {
- host: this.connectionData.host as string,
- applicationId: this.connectionData.applicationId as string,
- secret: this.connectionData.secret,
- accessToken: data.access_token,
- refreshToken: data.refresh_token,
- expiresIn: data.expires_in,
- tokenType: data.token_type,
- userId: user.id,
- screenName: user.name,
- username: user.username,
- };
- }
-
- async isStillVerified() {
- try {
- await this.client.Users.current();
-
- return true;
- } catch (err) {
- return false;
- }
- }
-}
diff --git a/packages/backend/src/apps/gitlab/index.d.ts b/packages/backend/src/apps/gitlab/index.d.ts
deleted file mode 100644
index e69de29b..00000000
diff --git a/packages/backend/src/apps/gitlab/index.ts b/packages/backend/src/apps/gitlab/index.ts
deleted file mode 100644
index 592ea6e9..00000000
--- a/packages/backend/src/apps/gitlab/index.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import Authentication from './authentication';
-import {
- IService,
- IAuthentication,
- IApp,
- IJSONObject,
-} from '@automatisch/types';
-
-export default class Gitlab implements IService {
- authenticationClient: IAuthentication;
-
- constructor(appData: IApp, connectionData: IJSONObject) {
- this.authenticationClient = new Authentication(appData, connectionData);
- }
-}
diff --git a/packages/backend/src/apps/gitlab/info.json b/packages/backend/src/apps/gitlab/info.json
deleted file mode 100644
index c9703cb5..00000000
--- a/packages/backend/src/apps/gitlab/info.json
+++ /dev/null
@@ -1,239 +0,0 @@
-{
- "name": "GitLab",
- "key": "gitlab",
- "iconUrl": "{BASE_URL}/apps/gitlab/assets/favicon.svg",
- "docUrl": "https://automatisch.io/docs/gitlab",
- "primaryColor": "2DAAE1",
- "supportsConnections": true,
- "fields": [
- {
- "key": "oAuthRedirectUrl",
- "label": "OAuth Redirect URL",
- "type": "string",
- "required": true,
- "readOnly": true,
- "value": "{WEB_APP_URL}/app/gitlab/connections/add",
- "placeholder": null,
- "description": "When asked to input an OAuth callback or redirect URL in GitLab OAuth, enter the URL above.",
- "docUrl": "https://automatisch.io/docs/gitlab#oauth-redirect-url",
- "clickToCopy": true
- },
- {
- "key": "host",
- "label": "GitLab Host",
- "type": "string",
- "required": true,
- "readOnly": false,
- "value": "gitlab.com",
- "placeholder": null,
- "description": "Only required if you host your own instance of GitLab. If you access GitLab by going to https://www.example.com, enter www.example.com here.",
- "docUrl": "https://automatisch.io/docs/gitlab#host",
- "clickToCopy": false
- },
- {
- "key": "applicationId",
- "label": "Application ID",
- "type": "string",
- "required": true,
- "readOnly": false,
- "value": null,
- "placeholder": null,
- "description": null,
- "docUrl": "https://automatisch.io/docs/gitlab#application-id",
- "clickToCopy": false
- },
- {
- "key": "secret",
- "label": "Secret",
- "type": "string",
- "required": true,
- "readOnly": false,
- "value": null,
- "placeholder": null,
- "description": null,
- "docUrl": "https://automatisch.io/docs/gitlab#secret",
- "clickToCopy": false
- }
- ],
- "authenticationSteps": [
- {
- "step": 1,
- "type": "mutation",
- "name": "createConnection",
- "arguments": [
- {
- "name": "key",
- "value": "{key}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "host",
- "value": "{fields.host}"
- },
- {
- "name": "applicationId",
- "value": "{fields.applicationId}"
- },
- {
- "name": "secret",
- "value": "{fields.secret}"
- }
- ]
- }
- ]
- },
- {
- "step": 2,
- "type": "mutation",
- "name": "createAuthData",
- "arguments": [
- {
- "name": "id",
- "value": "{createConnection.id}"
- }
- ]
- },
- {
- "step": 3,
- "type": "openWithPopup",
- "name": "openAuthPopup",
- "arguments": [
- {
- "name": "url",
- "value": "{createAuthData.url}"
- }
- ]
- },
- {
- "step": 4,
- "type": "mutation",
- "name": "updateConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{createConnection.id}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "oauthVerifier",
- "value": "{openAuthPopup.code}"
- }
- ]
- }
- ]
- },
- {
- "step": 5,
- "type": "mutation",
- "name": "verifyConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{createConnection.id}"
- }
- ]
- }
- ],
- "reconnectionSteps": [
- {
- "step": 1,
- "type": "mutation",
- "name": "resetConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- }
- ]
- },
- {
- "step": 2,
- "type": "mutation",
- "name": "updateConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "host",
- "value": "{fields.host}"
- },
- {
- "name": "applicationId",
- "value": "{fields.applicationId}"
- },
- {
- "name": "secret",
- "value": "{fields.secret}"
- }
- ]
- }
- ]
- },
- {
- "step": 3,
- "type": "mutation",
- "name": "createAuthData",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- }
- ]
- },
- {
- "step": 4,
- "type": "openWithPopup",
- "name": "openAuthPopup",
- "arguments": [
- {
- "name": "url",
- "value": "{createAuthData.url}"
- }
- ]
- },
- {
- "step": 5,
- "type": "mutation",
- "name": "updateConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "oauthVerifier",
- "value": "{openAuthPopup.code}"
- }
- ]
- }
- ]
- },
- {
- "step": 6,
- "type": "mutation",
- "name": "verifyConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- }
- ]
- }
- ]
-}
diff --git a/packages/backend/src/apps/smtp/assets/favicon.svg b/packages/backend/src/apps/smtp/assets/favicon.svg
deleted file mode 100644
index 44b0edb4..00000000
--- a/packages/backend/src/apps/smtp/assets/favicon.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
diff --git a/packages/backend/src/apps/smtp/authentication.ts b/packages/backend/src/apps/smtp/authentication.ts
deleted file mode 100644
index 65dfd609..00000000
--- a/packages/backend/src/apps/smtp/authentication.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import nodemailer, { Transporter, TransportOptions } from 'nodemailer';
-import type {
- IAuthentication,
- IApp,
- IJSONObject,
-} from '@automatisch/types';
-
-export default class Authentication implements IAuthentication {
- appData: IApp;
- connectionData: IJSONObject;
- client: Transporter;
-
- constructor(appData: IApp, connectionData: IJSONObject) {
- this.client = nodemailer.createTransport({
- host: connectionData.host,
- port: connectionData.port,
- secure: connectionData.useTls,
- auth: {
- user: connectionData.username,
- pass: connectionData.password,
- },
- } as TransportOptions);
-
- this.connectionData = connectionData;
- this.appData = appData;
- }
-
- async verifyCredentials() {
- await this.client.verify();
-
- return {
- screenName: this.connectionData.username,
- };
- }
-
- async isStillVerified() {
- try {
- await this.client.verify();
- return true;
- } catch (error) {
- return false;
- }
- }
-}
diff --git a/packages/backend/src/apps/smtp/index.d.ts b/packages/backend/src/apps/smtp/index.d.ts
deleted file mode 100644
index e69de29b..00000000
diff --git a/packages/backend/src/apps/smtp/index.ts b/packages/backend/src/apps/smtp/index.ts
deleted file mode 100644
index 8698d03b..00000000
--- a/packages/backend/src/apps/smtp/index.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import Authentication from './authentication';
-import {
- IService,
- IAuthentication,
- IApp,
- IJSONObject,
-} from '@automatisch/types';
-
-export default class SMTP implements IService {
- authenticationClient: IAuthentication;
-
- constructor(appData: IApp, connectionData: IJSONObject) {
- this.authenticationClient = new Authentication(appData, connectionData);
- }
-}
diff --git a/packages/backend/src/apps/smtp/info.json b/packages/backend/src/apps/smtp/info.json
deleted file mode 100644
index e0317d17..00000000
--- a/packages/backend/src/apps/smtp/info.json
+++ /dev/null
@@ -1,201 +0,0 @@
-{
- "name": "SMTP",
- "key": "smtp",
- "iconUrl": "{BASE_URL}/apps/smtp/assets/favicon.svg",
- "docUrl": "https://automatisch.io/docs/smtp",
- "primaryColor": "2DAAE1",
- "supportsConnections": true,
- "fields": [
- {
- "key": "host",
- "label": "Host",
- "type": "string",
- "required": true,
- "readOnly": false,
- "value": null,
- "placeholder": null,
- "description": "The host information Automatisch will connect to.",
- "docUrl": "https://automatisch.io/docs/smtp#host",
- "clickToCopy": false
- },
- {
- "key": "username",
- "label": "Email/Username",
- "type": "string",
- "required": true,
- "readOnly": false,
- "value": null,
- "placeholder": null,
- "description": "Your SMTP login credentials.",
- "docUrl": "https://automatisch.io/docs/smtp#username",
- "clickToCopy": false
- },
- {
- "key": "password",
- "label": "Password",
- "type": "string",
- "required": true,
- "readOnly": false,
- "value": null,
- "placeholder": null,
- "description": null,
- "docUrl": "https://automatisch.io/docs/smtp#password",
- "clickToCopy": false
- },
- {
- "key": "useTls",
- "label": "Use TLS?",
- "type": "boolean",
- "required": false,
- "readOnly": false,
- "value": false,
- "placeholder": null,
- "description": null,
- "docUrl": "https://automatisch.io/docs/smtp#use-tls",
- "clickToCopy": false
- },
- {
- "key": "port",
- "label": "Port",
- "type": "integer",
- "required": false,
- "readOnly": false,
- "value": 25,
- "placeholder": null,
- "description": null,
- "docUrl": "https://automatisch.io/docs/smtp#port",
- "clickToCopy": false
- },
- {
- "key": "fromEmail",
- "label": "From Email",
- "type": "string",
- "required": false,
- "readOnly": false,
- "value": null,
- "placeholder": null,
- "description": null,
- "docUrl": "https://automatisch.io/docs/smtp#from-email",
- "clickToCopy": false
- }
- ],
- "authenticationSteps": [
- {
- "step": 1,
- "type": "mutation",
- "name": "createConnection",
- "arguments": [
- {
- "name": "key",
- "value": "{key}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "host",
- "value": "{fields.host}"
- },
- {
- "name": "username",
- "value": "{fields.username}"
- },
- {
- "name": "password",
- "value": "{fields.password}"
- },
- {
- "name": "useTLS",
- "value": "{fields.useTls}"
- },
- {
- "name": "port",
- "value": "{fields.port}"
- },
- {
- "name": "fromEmail",
- "value": "{fields.fromEmail}"
- }
- ]
- }
- ]
- },
- {
- "step": 2,
- "type": "mutation",
- "name": "verifyConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{createConnection.id}"
- }
- ]
- }
- ],
- "reconnectionSteps": [
- {
- "step": 1,
- "type": "mutation",
- "name": "resetConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- }
- ]
- },
- {
- "step": 2,
- "type": "mutation",
- "name": "updateConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "host",
- "value": "{fields.host}"
- },
- {
- "name": "username",
- "value": "{fields.username}"
- },
- {
- "name": "password",
- "value": "{fields.password}"
- },
- {
- "name": "useTLS",
- "value": "{fields.useTls}"
- },
- {
- "name": "port",
- "value": "{fields.port}"
- },
- {
- "name": "fromEmail",
- "value": "{fields.fromEmail}"
- }
- ]
- }
- ]
- },
- {
- "step": 3,
- "type": "mutation",
- "name": "verifyConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- }
- ]
- }
- ]
-}
diff --git a/packages/backend/src/apps/twilio/assets/favicon.svg b/packages/backend/src/apps/twilio/assets/favicon.svg
deleted file mode 100644
index 3d3a8971..00000000
--- a/packages/backend/src/apps/twilio/assets/favicon.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
\ No newline at end of file
diff --git a/packages/backend/src/apps/twilio/authentication.ts b/packages/backend/src/apps/twilio/authentication.ts
deleted file mode 100644
index a2b1d8ca..00000000
--- a/packages/backend/src/apps/twilio/authentication.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import type {
- IAuthentication,
- IApp,
- IJSONObject,
-} from '@automatisch/types';
-import TwilioApi from 'twilio';
-
-export default class Authentication implements IAuthentication {
- appData: IApp;
- connectionData: IJSONObject;
- client: TwilioApi.Twilio;
-
- constructor(appData: IApp, connectionData: IJSONObject) {
- this.client = TwilioApi(
- connectionData.accountSid as string,
- connectionData.authToken as string
- );
-
- this.connectionData = connectionData;
- this.appData = appData;
- }
-
- async verifyCredentials() {
- await this.verify();
-
- return {
- screenName: this.connectionData.accountSid,
- };
- }
-
- async verify() {
- try {
- await this.client.keys.list({ limit: 1 });
- return true;
- } catch (error) {
- // Test credentials throw HTTP 403 and thus, we need to have an exception.
- return error?.status === 403;
- }
- }
-
- async isStillVerified() {
- return this.verify();
- }
-}
diff --git a/packages/backend/src/apps/twilio/index.ts b/packages/backend/src/apps/twilio/index.ts
deleted file mode 100644
index f25658d5..00000000
--- a/packages/backend/src/apps/twilio/index.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import Authentication from './authentication';
-import {
- IService,
- IAuthentication,
- IApp,
- IJSONObject,
-} from '@automatisch/types';
-
-export default class Twilio implements IService {
- authenticationClient: IAuthentication;
-
- constructor(appData: IApp, connectionData: IJSONObject) {
- this.authenticationClient = new Authentication(appData, connectionData);
- }
-}
diff --git a/packages/backend/src/apps/twilio/info.json b/packages/backend/src/apps/twilio/info.json
deleted file mode 100644
index ac68b688..00000000
--- a/packages/backend/src/apps/twilio/info.json
+++ /dev/null
@@ -1,121 +0,0 @@
-{
- "name": "Twilio",
- "key": "twilio",
- "iconUrl": "{BASE_URL}/apps/twilio/assets/favicon.svg",
- "docUrl": "https://automatisch.io/docs/twilio",
- "primaryColor": "f22f46",
- "supportsConnections": true,
- "fields": [
- {
- "key": "accountSid",
- "label": "Account SID",
- "type": "string",
- "required": true,
- "readOnly": false,
- "value": null,
- "placeholder": null,
- "description": "Log into your Twilio account and find \"API Credentials\" on this page https://www.twilio.com/user/account/settings",
- "docUrl": "https://automatisch.io/docs/twilio#host",
- "clickToCopy": false
- },
- {
- "key": "authToken",
- "label": "Auth Token",
- "type": "string",
- "required": true,
- "readOnly": false,
- "value": null,
- "placeholder": null,
- "description": "Found directly below your Account SID.",
- "docUrl": "https://automatisch.io/docs/twilio#username",
- "clickToCopy": false
- }
- ],
- "authenticationSteps": [
- {
- "step": 1,
- "type": "mutation",
- "name": "createConnection",
- "arguments": [
- {
- "name": "key",
- "value": "{key}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "accountSid",
- "value": "{fields.accountSid}"
- },
- {
- "name": "authToken",
- "value": "{fields.authToken}"
- }
- ]
- }
- ]
- },
- {
- "step": 2,
- "type": "mutation",
- "name": "verifyConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{createConnection.id}"
- }
- ]
- }
- ],
- "reconnectionSteps": [
- {
- "step": 1,
- "type": "mutation",
- "name": "resetConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- }
- ]
- },
- {
- "step": 2,
- "type": "mutation",
- "name": "updateConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "accountSid",
- "value": "{fields.accountSid}"
- },
- {
- "name": "authToken",
- "value": "{fields.authToken}"
- }
- ]
- }
- ]
- },
- {
- "step": 3,
- "type": "mutation",
- "name": "verifyConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- }
- ]
- }
- ]
-}
diff --git a/packages/backend/src/apps/twitch/assets/favicon.svg b/packages/backend/src/apps/twitch/assets/favicon.svg
deleted file mode 100644
index 8adbef1a..00000000
--- a/packages/backend/src/apps/twitch/assets/favicon.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
diff --git a/packages/backend/src/apps/twitch/authentication.ts b/packages/backend/src/apps/twitch/authentication.ts
deleted file mode 100644
index 26780bc9..00000000
--- a/packages/backend/src/apps/twitch/authentication.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import type {
- IAuthentication,
- IApp,
- IField,
- IJSONObject,
-} from '@automatisch/types';
-import TwitchApi, { TwitchJsOptions } from 'twitch-js';
-import fetchUtil from 'twitch-js/lib/utils/fetch';
-
-type TwitchTokenResponse = {
- accessToken: string;
- refreshToken: string;
- expiresIn: string;
- tokenType: string;
-};
-
-export default class Authentication implements IAuthentication {
- appData: IApp;
- connectionData: IJSONObject;
- client: TwitchApi;
-
- constructor(appData: IApp, connectionData: IJSONObject) {
- this.connectionData = connectionData;
- this.appData = appData;
-
- if (this.clientOptions.token) {
- this.client = new TwitchApi(this.clientOptions as TwitchJsOptions);
- }
- }
-
- get clientOptions() {
- return {
- token: this.connectionData.accessToken,
- clientId: this.connectionData.consumerKey,
- log: { enabled: true },
- };
- }
-
- get oauthRedirectUrl() {
- return this.appData.fields.find(
- (field: IField) => field.key == 'oAuthRedirectUrl'
- ).value;
- }
-
- async createAuthData() {
- const { url } = await fetchUtil('https://id.twitch.tv/oauth2/authorize', {
- search: {
- client_id: this.connectionData.consumerKey,
- redirect_uri: this.oauthRedirectUrl,
- response_type: 'code',
- scope: 'user:read:email',
- },
- });
-
- return { url };
- }
-
- async verifyCredentials() {
- const verifiedCredentials = (await fetchUtil(
- 'https://id.twitch.tv/oauth2/token',
- {
- method: 'post',
- search: {
- client_id: this.connectionData.consumerKey,
- client_secret: this.connectionData.consumerSecret,
- code: this.connectionData.oauthVerifier,
- grant_type: 'authorization_code',
- redirect_uri: this.oauthRedirectUrl,
- },
- }
- )) as TwitchTokenResponse;
-
- this.connectionData.accessToken = verifiedCredentials.accessToken;
-
- const { api } = new TwitchApi(this.clientOptions as TwitchJsOptions);
-
- const { data } = await api.get('users');
- const [user] = data;
-
- return {
- consumerKey: this.connectionData.consumerKey,
- consumerSecret: this.connectionData.consumerSecret,
- accessToken: verifiedCredentials.accessToken,
- refreshToken: verifiedCredentials.refreshToken,
- expiresIn: verifiedCredentials.expiresIn,
- tokenType: verifiedCredentials.tokenType,
- userId: user.id,
- screenName: user.displayName,
- };
- }
-
- async isStillVerified() {
- try {
- await fetchUtil('https://id.twitch.tv/oauth2/userinfo', {
- headers: {
- Authorization: `Bearer ${this.connectionData.accessToken}`,
- },
- });
-
- return true;
- } catch (err) {
- return false;
- }
- }
-}
diff --git a/packages/backend/src/apps/twitch/index.d.ts b/packages/backend/src/apps/twitch/index.d.ts
deleted file mode 100644
index e69de29b..00000000
diff --git a/packages/backend/src/apps/twitch/index.ts b/packages/backend/src/apps/twitch/index.ts
deleted file mode 100644
index 42d13c05..00000000
--- a/packages/backend/src/apps/twitch/index.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import Authentication from './authentication';
-import {
- IService,
- IAuthentication,
- IApp,
- IJSONObject,
-} from '@automatisch/types';
-
-export default class Twitch implements IService {
- authenticationClient: IAuthentication;
-
- constructor(appData: IApp, connectionData: IJSONObject) {
- this.authenticationClient = new Authentication(appData, connectionData);
- }
-}
diff --git a/packages/backend/src/apps/twitch/info.json b/packages/backend/src/apps/twitch/info.json
deleted file mode 100644
index 2931af88..00000000
--- a/packages/backend/src/apps/twitch/info.json
+++ /dev/null
@@ -1,219 +0,0 @@
-{
- "name": "Twitch",
- "key": "twitch",
- "iconUrl": "{BASE_URL}/apps/twitch/assets/favicon.svg",
- "docUrl": "https://automatisch.io/docs/twitch",
- "primaryColor": "2DAAE1",
- "supportsConnections": true,
- "fields": [
- {
- "key": "oAuthRedirectUrl",
- "label": "OAuth Redirect URL",
- "type": "string",
- "required": true,
- "readOnly": true,
- "value": "{WEB_APP_URL}/app/twitch/connections/add",
- "placeholder": null,
- "description": "When asked to input an OAuth callback or redirect URL in Twitch OAuth, enter the URL above.",
- "docUrl": "https://automatisch.io/docs/twitch#oauth-redirect-url",
- "clickToCopy": true
- },
- {
- "key": "consumerKey",
- "label": "Consumer Key",
- "type": "string",
- "required": true,
- "readOnly": false,
- "value": null,
- "placeholder": null,
- "description": null,
- "docUrl": "https://automatisch.io/docs/twitch#consumer-key",
- "clickToCopy": false
- },
- {
- "key": "consumerSecret",
- "label": "Consumer Secret",
- "type": "string",
- "required": true,
- "readOnly": false,
- "value": null,
- "placeholder": null,
- "description": null,
- "docUrl": "https://automatisch.io/docs/twitch#consumer-secret",
- "clickToCopy": false
- }
- ],
- "authenticationSteps": [
- {
- "step": 1,
- "type": "mutation",
- "name": "createConnection",
- "arguments": [
- {
- "name": "key",
- "value": "{key}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "consumerKey",
- "value": "{fields.consumerKey}"
- },
- {
- "name": "consumerSecret",
- "value": "{fields.consumerSecret}"
- }
- ]
- }
- ]
- },
- {
- "step": 2,
- "type": "mutation",
- "name": "createAuthData",
- "arguments": [
- {
- "name": "id",
- "value": "{createConnection.id}"
- }
- ]
- },
- {
- "step": 3,
- "type": "openWithPopup",
- "name": "openAuthPopup",
- "arguments": [
- {
- "name": "url",
- "value": "{createAuthData.url}"
- }
- ]
- },
- {
- "step": 4,
- "type": "mutation",
- "name": "updateConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{createConnection.id}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "oauthVerifier",
- "value": "{openAuthPopup.code}"
- }
- ]
- }
- ]
- },
- {
- "step": 5,
- "type": "mutation",
- "name": "verifyConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{createConnection.id}"
- }
- ]
- }
- ],
- "reconnectionSteps": [
- {
- "step": 1,
- "type": "mutation",
- "name": "resetConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- }
- ]
- },
- {
- "step": 2,
- "type": "mutation",
- "name": "updateConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "consumerKey",
- "value": "{fields.consumerKey}"
- },
- {
- "name": "consumerSecret",
- "value": "{fields.consumerSecret}"
- }
- ]
- }
- ]
- },
- {
- "step": 3,
- "type": "mutation",
- "name": "createAuthData",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- }
- ]
- },
- {
- "step": 4,
- "type": "openWithPopup",
- "name": "openAuthPopup",
- "arguments": [
- {
- "name": "url",
- "value": "{createAuthData.url}"
- }
- ]
- },
- {
- "step": 5,
- "type": "mutation",
- "name": "updateConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "oauthVerifier",
- "value": "{openAuthPopup.code}"
- }
- ]
- }
- ]
- },
- {
- "step": 6,
- "type": "mutation",
- "name": "verifyConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- }
- ]
- }
- ]
-}
diff --git a/packages/backend/src/apps/typeform/assets/favicon.svg b/packages/backend/src/apps/typeform/assets/favicon.svg
deleted file mode 100644
index 56381390..00000000
--- a/packages/backend/src/apps/typeform/assets/favicon.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
diff --git a/packages/backend/src/apps/typeform/authentication.ts b/packages/backend/src/apps/typeform/authentication.ts
deleted file mode 100644
index 0229b42c..00000000
--- a/packages/backend/src/apps/typeform/authentication.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-import type {
- IAuthentication,
- IApp,
- IField,
- IJSONObject,
-} from '@automatisch/types';
-import { URLSearchParams } from 'url';
-import createHttpClient, { IHttpClient } from '../../helpers/http-client';
-
-export default class Authentication implements IAuthentication {
- appData: IApp;
- connectionData: IJSONObject;
- client: IHttpClient;
-
- scope: string[] = [
- 'forms:read',
- 'forms:write',
- 'webhooks:read',
- 'webhooks:write',
- 'responses:read',
- 'accounts:read',
- 'workspaces:read',
- ];
-
- constructor(appData: IApp, connectionData: IJSONObject) {
- this.connectionData = connectionData;
- this.appData = appData;
- this.client = createHttpClient({ baseURL: 'https://api.typeform.com' });
- }
-
- get oauthRedirectUrl() {
- return this.appData.fields.find(
- (field: IField) => field.key == 'oAuthRedirectUrl'
- ).value;
- }
-
- async createAuthData() {
- const searchParams = new URLSearchParams({
- client_id: this.connectionData.consumerKey as string,
- redirect_uri: this.oauthRedirectUrl,
- scope: this.scope.join(' '),
- });
-
- const url = `https://api.typeform.com/oauth/authorize?${searchParams.toString()}`;
-
- return { url };
- }
-
- async verifyCredentials() {
- const params = new URLSearchParams({
- grant_type: 'authorization_code',
- code: this.connectionData.oauthVerifier as string,
- client_id: this.connectionData.consumerKey as string,
- client_secret: this.connectionData.consumerSecret as string,
- redirect_uri: this.oauthRedirectUrl,
- });
-
- const { data: verifiedCredentials } = await this.client.post(
- '/oauth/token',
- params.toString()
- );
-
- const {
- access_token: accessToken,
- expires_in: expiresIn,
- token_type: tokenType,
- } = verifiedCredentials;
-
- const { data: user } = await this.client.get('/me', {
- headers: {
- Authorization: `Bearer ${accessToken}`,
- },
- });
-
- return {
- consumerKey: this.connectionData.consumerKey,
- consumerSecret: this.connectionData.consumerSecret,
- accessToken,
- expiresIn,
- tokenType,
- userId: user.user_id,
- screenName: user.alias,
- email: user.email,
- };
- }
-
- async isStillVerified() {
- try {
- await this.client.get('/me', {
- headers: {
- Authorization: `Bearer ${this.connectionData.accessToken}`,
- },
- });
-
- return true;
- } catch {
- return false;
- }
- }
-}
diff --git a/packages/backend/src/apps/typeform/index.d.ts b/packages/backend/src/apps/typeform/index.d.ts
deleted file mode 100644
index e69de29b..00000000
diff --git a/packages/backend/src/apps/typeform/index.ts b/packages/backend/src/apps/typeform/index.ts
deleted file mode 100644
index 75291db8..00000000
--- a/packages/backend/src/apps/typeform/index.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import Authentication from './authentication';
-import {
- IService,
- IAuthentication,
- IApp,
- IJSONObject,
-} from '@automatisch/types';
-
-export default class Typeform implements IService {
- authenticationClient: IAuthentication;
-
- constructor(appData: IApp, connectionData: IJSONObject) {
- this.authenticationClient = new Authentication(appData, connectionData);
- }
-}
diff --git a/packages/backend/src/apps/typeform/info.json b/packages/backend/src/apps/typeform/info.json
deleted file mode 100644
index 3aecd315..00000000
--- a/packages/backend/src/apps/typeform/info.json
+++ /dev/null
@@ -1,219 +0,0 @@
-{
- "name": "Typeform",
- "key": "typeform",
- "iconUrl": "{BASE_URL}/apps/typeform/assets/favicon.svg",
- "docUrl": "https://automatisch.io/docs/typeform",
- "primaryColor": "5865f2",
- "supportsConnections": true,
- "fields": [
- {
- "key": "oAuthRedirectUrl",
- "label": "OAuth Redirect URL",
- "type": "string",
- "required": true,
- "readOnly": true,
- "value": "{WEB_APP_URL}/app/typeform/connections/add",
- "placeholder": null,
- "description": "When asked to input an OAuth callback or redirect URL in Typeform OAuth, enter the URL above.",
- "docUrl": "https://automatisch.io/docs/typeform#oauth-redirect-url",
- "clickToCopy": true
- },
- {
- "key": "consumerKey",
- "label": "Client ID",
- "type": "string",
- "required": true,
- "readOnly": false,
- "value": null,
- "placeholder": null,
- "description": null,
- "docUrl": "https://automatisch.io/docs/typeform#consumer-key",
- "clickToCopy": false
- },
- {
- "key": "consumerSecret",
- "label": "Client Secret",
- "type": "string",
- "required": true,
- "readOnly": false,
- "value": null,
- "placeholder": null,
- "description": null,
- "docUrl": "https://automatisch.io/docs/typeform#consumer-secret",
- "clickToCopy": false
- }
- ],
- "authenticationSteps": [
- {
- "step": 1,
- "type": "mutation",
- "name": "createConnection",
- "arguments": [
- {
- "name": "key",
- "value": "{key}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "consumerKey",
- "value": "{fields.consumerKey}"
- },
- {
- "name": "consumerSecret",
- "value": "{fields.consumerSecret}"
- }
- ]
- }
- ]
- },
- {
- "step": 2,
- "type": "mutation",
- "name": "createAuthData",
- "arguments": [
- {
- "name": "id",
- "value": "{createConnection.id}"
- }
- ]
- },
- {
- "step": 3,
- "type": "openWithPopup",
- "name": "openAuthPopup",
- "arguments": [
- {
- "name": "url",
- "value": "{createAuthData.url}"
- }
- ]
- },
- {
- "step": 4,
- "type": "mutation",
- "name": "updateConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{createConnection.id}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "oauthVerifier",
- "value": "{openAuthPopup.code}"
- }
- ]
- }
- ]
- },
- {
- "step": 5,
- "type": "mutation",
- "name": "verifyConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{createConnection.id}"
- }
- ]
- }
- ],
- "reconnectionSteps": [
- {
- "step": 1,
- "type": "mutation",
- "name": "resetConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- }
- ]
- },
- {
- "step": 2,
- "type": "mutation",
- "name": "updateConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "consumerKey",
- "value": "{fields.consumerKey}"
- },
- {
- "name": "consumerSecret",
- "value": "{fields.consumerSecret}"
- }
- ]
- }
- ]
- },
- {
- "step": 3,
- "type": "mutation",
- "name": "createAuthData",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- }
- ]
- },
- {
- "step": 4,
- "type": "openWithPopup",
- "name": "openAuthPopup",
- "arguments": [
- {
- "name": "url",
- "value": "{createAuthData.url}"
- }
- ]
- },
- {
- "step": 5,
- "type": "mutation",
- "name": "updateConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- },
- {
- "name": "formattedData",
- "value": null,
- "properties": [
- {
- "name": "oauthVerifier",
- "value": "{openAuthPopup.code}"
- }
- ]
- }
- ]
- },
- {
- "step": 6,
- "type": "mutation",
- "name": "verifyConnection",
- "arguments": [
- {
- "name": "id",
- "value": "{connection.id}"
- }
- ]
- }
- ]
-}
diff --git a/packages/backend/src/services/processor.ts b/packages/backend/src/services/processor.ts
index 224fcceb..896cbf38 100644
--- a/packages/backend/src/services/processor.ts
+++ b/packages/backend/src/services/processor.ts
@@ -102,13 +102,14 @@ class Processor {
priorExecutionSteps
);
- step.parameters = computedParameters;
+ const clonedStep = Object.assign({}, step);
+ clonedStep.parameters = computedParameters;
const $ = await globalVariable(
step.connection,
app,
this.flow,
- step,
+ clonedStep
);
if (!isTrigger && key) {
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 0b17bf99..b3b51fbf 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@automatisch/cli",
- "version": "0.1.4",
+ "version": "0.1.5",
"license": "AGPL-3.0",
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
"contributors": [
@@ -33,7 +33,7 @@
"version": "oclif readme && git add README.md"
},
"dependencies": {
- "@automatisch/backend": "^0.1.4",
+ "@automatisch/backend": "^0.1.5",
"@oclif/core": "^1",
"@oclif/plugin-help": "^5",
"@oclif/plugin-plugins": "^2.0.1",
diff --git a/packages/docs/package.json b/packages/docs/package.json
index e3906f75..87be15e9 100644
--- a/packages/docs/package.json
+++ b/packages/docs/package.json
@@ -1,6 +1,6 @@
{
"name": "@automatisch/docs",
- "version": "0.1.4",
+ "version": "0.1.5",
"license": "AGPL-3.0",
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
"private": true,
diff --git a/packages/docs/pages/connections/slack.md b/packages/docs/pages/connections/slack.md
index 891df064..92db6c18 100644
--- a/packages/docs/pages/connections/slack.md
+++ b/packages/docs/pages/connections/slack.md
@@ -1,7 +1,7 @@
# Slack
-:::info This page explains the steps you need to follow to set up the Slack
-connection in Automatisch. If any of the steps are outdated, please let us know!
+:::info
+This page explains the steps you need to follow to set up the Slack connection in Automatisch. If any of the steps are outdated, please let us know!
:::
1. Go to the [link](https://api.slack.com/apps?new_app=1) to **create an app**
diff --git a/packages/docs/pages/guide/create-flow.md b/packages/docs/pages/guide/create-flow.md
index e69de29b..a0990367 100644
--- a/packages/docs/pages/guide/create-flow.md
+++ b/packages/docs/pages/guide/create-flow.md
@@ -0,0 +1 @@
+TBD
diff --git a/packages/docs/pages/guide/installation.md b/packages/docs/pages/guide/installation.md
index b4f95e61..a0b623bc 100644
--- a/packages/docs/pages/guide/installation.md
+++ b/packages/docs/pages/guide/installation.md
@@ -23,4 +23,4 @@ You can use `user@automatisch.io` email address and `sample` password to login t
:::
-If you see any problems while installing Automatisch, let us know via [github issues](#) or our [discord server](https://discord.gg/dJSah9CVrC).
+If you see any problems while installing Automatisch, let us know via [github issues](https://github.com/automatisch/automatisch/issues) or our [discord server](https://discord.gg/dJSah9CVrC).
diff --git a/packages/e2e-tests/cypress.config.js b/packages/e2e-tests/cypress.config.js
new file mode 100644
index 00000000..2acda4eb
--- /dev/null
+++ b/packages/e2e-tests/cypress.config.js
@@ -0,0 +1,17 @@
+const { defineConfig } = require("cypress");
+
+const TO_BE_PROVIDED = 'HAS_TO_BE_PROVIDED_IN_cypress.env.json';
+
+module.exports = defineConfig({
+ e2e: {
+ baseUrl: 'http://localhost:3001',
+ env: {
+ login_email: "user@automatisch.io",
+ login_password: "sample",
+ slack_user_token: TO_BE_PROVIDED,
+ },
+ specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
+ viewportWidth: 1280,
+ viewportHeight: 768
+ },
+});
diff --git a/packages/e2e-tests/cypress/e2e/apps/list-apps.js b/packages/e2e-tests/cypress/e2e/apps/list-apps.js
new file mode 100644
index 00000000..d5dfb7b2
--- /dev/null
+++ b/packages/e2e-tests/cypress/e2e/apps/list-apps.js
@@ -0,0 +1,50 @@
+///
+
+describe('Apps page', () => {
+ before(() => {
+ cy.login();
+
+ cy.og('apps-page-drawer-link').click();
+ });
+
+ after(() => {
+ cy.logout();
+ });
+
+ it('displays applications', () => {
+ cy.og('apps-loader').should('not.exist');
+ cy.og('app-row').should('have.length', 3);
+
+ cy.ss('Applications');
+ });
+
+ context('can add connection', () => {
+ before(() => {
+ cy.og('add-connection-button').click();
+ });
+
+ it('lists applications', () => {
+ cy.og('app-list-item').should('have.length', 3);
+ });
+
+ it('searches an application', () => {
+ cy.og('search-for-app-text-field').type('Slack');
+ cy.og('app-list-item').should('have.length', 1);
+ });
+
+ it('goes to app page to create a connection', () => {
+ cy.og('app-list-item').first().click();
+
+ cy.location('pathname').should('equal', '/app/slack/connections/add');
+
+ cy.og('add-app-connection-dialog').should('be.visible');
+ });
+
+ it('closes the dialog on backdrop click', () => {
+ cy.clickOutside();
+
+ cy.location('pathname').should('equal', '/app/slack/connections');
+ cy.og('add-app-connection-dialog').should('not.exist');
+ });
+ });
+});
\ No newline at end of file
diff --git a/packages/e2e-tests/cypress/e2e/connections/create-connection.js b/packages/e2e-tests/cypress/e2e/connections/create-connection.js
new file mode 100644
index 00000000..570dab31
--- /dev/null
+++ b/packages/e2e-tests/cypress/e2e/connections/create-connection.js
@@ -0,0 +1,52 @@
+///
+
+describe('Connections page', () => {
+ before(() => {
+ cy.login();
+
+ cy.og('apps-page-drawer-link').click();
+ });
+
+ after(() => {
+ cy.logout();
+ });
+
+ it('opens via applications page', () => {
+ cy.og('apps-loader').should('not.exist');
+
+ cy.og('app-row').contains('Slack').click();
+
+ cy.og('app-connection-row').should('be.visible');
+
+ cy.ss('Slack connections before creating a connection');
+ });
+
+ context('can add connection', () => {
+ it('has a button to open add connection dialog', () => {
+ cy
+ .og('add-connection-button')
+ .scrollIntoView()
+ .should('be.visible');
+ });
+
+ it('add connection button takes user to add connection page', () => {
+ cy
+ .og('add-connection-button')
+ .click({ force: true });
+
+ cy.location('pathname').should('equal', '/app/slack/connections/add');
+ });
+
+ it('shows add connection dialog to create a new connection', () => {
+ cy
+ .get('input[name="accessToken"]')
+ .type(Cypress.env('slack_user_token'));
+
+ cy.og('create-connection-button').click();
+
+ cy.og('create-connection-button').should('not.exist');
+
+ cy.ss('Slack connections after creating a connection');
+ });
+ });
+});
diff --git a/packages/e2e-tests/cypress/e2e/executions/display-execution.js b/packages/e2e-tests/cypress/e2e/executions/display-execution.js
new file mode 100644
index 00000000..647f97ca
--- /dev/null
+++ b/packages/e2e-tests/cypress/e2e/executions/display-execution.js
@@ -0,0 +1,34 @@
+///
+
+describe('Execution page', () => {
+ before(() => {
+ cy.login();
+
+ cy.og('executions-page-drawer-link').click();
+ cy.og('execution-row').first().click({ force: true });
+
+ cy.location('pathname').should('match', /^\/executions\//);
+ });
+
+ after(() => {
+ cy.logout();
+ });
+
+ it('displays data in by default', () => {
+ cy.og('execution-step').should('have.length', 2);
+
+ cy.ss('Execution - data in');
+ });
+
+ it('displays data out', () => {
+ cy.og('data-out-tab').click({ multiple: true });
+
+ cy.ss('Execution - data out');
+ });
+
+ it('displays error', () => {
+ cy.og('error-tab').click({ multiple: true, force: true });
+
+ cy.ss('Execution - error');
+ });
+});
\ No newline at end of file
diff --git a/packages/e2e-tests/cypress/e2e/executions/list-executions.js b/packages/e2e-tests/cypress/e2e/executions/list-executions.js
new file mode 100644
index 00000000..66fcb026
--- /dev/null
+++ b/packages/e2e-tests/cypress/e2e/executions/list-executions.js
@@ -0,0 +1,20 @@
+///
+
+describe('Executions page', () => {
+ before(() => {
+ cy.login();
+
+ cy.og('executions-page-drawer-link').click();
+ });
+
+ after(() => {
+ cy.logout();
+ });
+
+ it('displays executions', () => {
+ cy.og('executions-loader').should('not.exist');
+ cy.og('execution-row').should('exist');
+
+ cy.ss('Executions');
+ });
+});
\ No newline at end of file
diff --git a/packages/e2e-tests/cypress/e2e/flow-editor/create-flow.js b/packages/e2e-tests/cypress/e2e/flow-editor/create-flow.js
new file mode 100644
index 00000000..77d08af9
--- /dev/null
+++ b/packages/e2e-tests/cypress/e2e/flow-editor/create-flow.js
@@ -0,0 +1,229 @@
+///
+
+describe('Flow editor page', () => {
+ before(() => {
+ cy.login();
+ });
+
+ after(() => {
+ cy.logout();
+ });
+
+ it('create flow', () => {
+ cy.og('create-flow-button').click({ force: true });
+ });
+
+ it('has two steps by default', () => {
+ cy.og('flow-step').should('have.length', 2);
+ });
+
+ context('edit flow', () => {
+ context('arrange Scheduler trigger', () => {
+ context('choose app and event substep', () => {
+ it('choose application', () => {
+ cy.og('choose-app-autocomplete').click();
+
+ cy.get('li[role="option"]:contains("Scheduler")').click();
+ });
+
+ it('choose an event', () => {
+ cy
+ .og('choose-event-autocomplete')
+ .should('be.visible')
+ .click();
+
+ cy.get('li[role="option"]:contains("Every hour")').click();
+ });
+
+ it('continue to next step', () => {
+ cy.og('flow-substep-continue-button').click();
+ });
+
+ it('collapses the substep', () => {
+ cy.og('choose-app-autocomplete').should('not.be.visible');
+ cy.og('choose-event-autocomplete').should('not.be.visible');
+ })
+ });
+
+ context('set up a trigger', () => {
+ it('choose "yes" in "trigger on weekends?"', () => {
+ cy
+ .og('parameters.triggersOnWeekend-autocomplete')
+ .should('be.visible')
+ .click();
+
+ cy.get('li[role="option"]:contains("Yes")').click();
+ });
+
+ it('continue to next step', () => {
+ cy.og('flow-substep-continue-button').click();
+ });
+
+ it('collapses the substep', () => {
+ cy.og('parameters.triggersOnWeekend-autocomplete').should('not.exist');
+ });
+ });
+
+ context('test trigger', () => {
+ it('show sample output', () => {
+ cy.og('flow-test-substep-output').should('not.exist');
+
+ cy.og('flow-substep-continue-button').click();
+
+ cy.og('flow-test-substep-output').should('be.visible');
+
+ cy.ss('Scheduler trigger test output');
+
+ cy.og('flow-substep-continue-button').click();
+ });
+ });
+ });
+
+ context('arrange Slack action', () => {
+ context('choose app and event substep', () => {
+ it('choose application', () => {
+ cy.og('choose-app-autocomplete').click();
+
+ cy.get('li[role="option"]:contains("Slack")').click();
+ });
+
+ it('choose an event', () => {
+ cy
+ .og('choose-event-autocomplete')
+ .should('be.visible')
+ .click();
+
+ cy.get('li[role="option"]:contains("Send a message to channel")').click();
+ });
+
+ it('continue to next step', () => {
+ cy.og('flow-substep-continue-button').click();
+ });
+
+ it('collapses the substep', () => {
+ cy.og('choose-app-autocomplete').should('not.be.visible');
+ cy.og('choose-event-autocomplete').should('not.be.visible');
+ });
+ });
+
+ context('choose connection', () => {
+ it('choose connection', () => {
+ cy.og('choose-connection-autocomplete').click();
+
+ cy.get('li[role="option"]').first().click();
+ });
+
+ it('continue to next step', () => {
+ cy.og('flow-substep-continue-button').click();
+ });
+
+ it('collapses the substep', () => {
+ cy.og('choose-connection-autocomplete').should('not.be.visible');
+ });
+ });
+
+ context('set up action', () => {
+ it('choose channel', () => {
+ cy.og('parameters.channel-autocomplete').click();
+
+ cy.get('li[role="option"]').last().click();
+ });
+
+ it('arrange message text', () => {
+ cy
+ .og('power-input', ' [contenteditable]')
+ .click()
+ .type(`Hello from e2e tests! Here is the first suggested variable's value; `);
+
+ cy
+ .og('power-input-suggestion-group').first()
+ .og('power-input-suggestion-item').first()
+ .click();
+
+ cy.clickOutside();
+
+ cy.ss('Slack action message text');
+ });
+
+ it('continue to next step', () => {
+ cy.og('flow-substep-continue-button').click();
+ });
+
+ it('collapses the substep', () => {
+ cy
+ .og('power-input', ' [contenteditable]')
+ .should('not.exist');
+ });
+ });
+
+ context('test trigger', () => {
+ it('show sample output', () => {
+ cy.og('flow-test-substep-output').should('not.exist');
+
+ cy.og('flow-substep-continue-button').click();
+
+ cy.og('flow-test-substep-output').should('be.visible');
+
+ cy.ss('Slack action test output');
+
+ cy.og('flow-substep-continue-button').click();
+ });
+ });
+ });
+ });
+
+ context('publish and unpublish', () => {
+ it('publish flow', () => {
+ cy.og('unpublish-flow-button').should('not.exist');
+
+ cy
+ .og('publish-flow-button')
+ .should('be.visible')
+ .click();
+
+ cy.og('publish-flow-button').should('not.exist');
+ });
+
+ it('shows read-only sticky snackbar', () => {
+ cy.og('flow-cannot-edit-info-snackbar').should('be.visible');
+
+ cy.ss('Published flow');
+ });
+
+ it('unpublish from snackbar', () => {
+ cy
+ .og('unpublish-flow-from-snackbar')
+ .click();
+
+ cy.og('flow-cannot-edit-info-snackbar').should('not.exist');
+ })
+
+ it('publish once again', () => {
+ cy
+ .og('publish-flow-button')
+ .should('be.visible')
+ .click();
+
+ cy.og('publish-flow-button').should('not.exist');
+ });
+
+ it('unpublish from layout top bar', () => {
+ cy
+ .og('unpublish-flow-button')
+ .should('be.visible')
+ .click();
+
+ cy.og('unpublish-flow-button').should('not.exist');
+
+ cy.ss('Unpublished flow');
+ });
+ });
+
+ context('in layout', () => {
+ it('can go back to flows page', () => {
+ cy.og('editor-go-back-button').click();
+
+ cy.location('pathname').should('equal', '/flows');
+ });
+ });
+});
diff --git a/packages/e2e-tests/cypress/support/commands.js b/packages/e2e-tests/cypress/support/commands.js
new file mode 100644
index 00000000..7d28515e
--- /dev/null
+++ b/packages/e2e-tests/cypress/support/commands.js
@@ -0,0 +1,45 @@
+Cypress.Commands.add('og', { prevSubject: 'optional' }, (subject, selector, suffix = '') => {
+ if (subject) {
+ return cy.wrap(subject).get(`[data-test="${selector}"]${suffix}`);
+ }
+
+ return cy.get(`[data-test="${selector}"]${suffix}`);
+});
+
+Cypress.Commands.add('login', () => {
+ cy.visit('/login');
+
+ cy.og('email-text-field').type(Cypress.env('login_email'));
+ cy.og('password-text-field').type(Cypress.env('login_password'));
+
+ cy
+ .intercept('/graphql')
+ .as('graphqlCalls');
+ cy
+ .intercept('https://notifications.automatisch.io/notifications.json')
+ .as('notificationsCall');
+ cy.og('login-button').click();
+
+ cy.wait(['@graphqlCalls', '@notificationsCall']);
+});
+
+Cypress.Commands.add('logout', () => {
+ cy.og('profile-menu-button').click();
+
+ cy.og('logout-item').click();
+});
+
+Cypress.Commands.add('ss', (name, opts = {}) => {
+ return cy.screenshot(
+ name,
+ {
+ overwrite: true,
+ capture: 'viewport',
+ ...opts,
+ }
+ );
+});
+
+Cypress.Commands.add('clickOutside', () => {
+ return cy.get('body').click(0, 0);
+});
diff --git a/packages/e2e-tests/cypress/support/e2e.js b/packages/e2e-tests/cypress/support/e2e.js
new file mode 100644
index 00000000..0e7290a1
--- /dev/null
+++ b/packages/e2e-tests/cypress/support/e2e.js
@@ -0,0 +1,20 @@
+// ***********************************************************
+// This example support/e2e.js is processed and
+// loaded automatically before your test files.
+//
+// This is a great place to put global configuration and
+// behavior that modifies Cypress.
+//
+// You can change the location of this file or turn off
+// automatically serving support files with the
+// 'supportFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/configuration
+// ***********************************************************
+
+// Import commands.js using ES2015 syntax:
+import './commands'
+
+// Alternatively you can use CommonJS syntax:
+// require('./commands')
\ No newline at end of file
diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json
new file mode 100644
index 00000000..59fcbc36
--- /dev/null
+++ b/packages/e2e-tests/package.json
@@ -0,0 +1,27 @@
+{
+ "name": "@automatisch/e2e-tests",
+ "version": "0.1.5",
+ "license": "AGPL-3.0",
+ "private": true,
+ "description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
+ "scripts": {
+ "open": "cypress open"
+ },
+ "contributors": [
+ {
+ "name": "automatisch contributors",
+ "url": "https://github.com/automatisch/automatisch/graphs/contributors"
+ }
+ ],
+ "homepage": "https://github.com/automatisch/automatisch#readme",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/automatisch/automatisch.git"
+ },
+ "bugs": {
+ "url": "https://github.com/automatisch/automatisch/issues"
+ },
+ "devDependencies": {
+ "cypress": "^10.9.0"
+ }
+}
diff --git a/packages/types/package.json b/packages/types/package.json
index 69bcd380..8d4cd979 100644
--- a/packages/types/package.json
+++ b/packages/types/package.json
@@ -1,6 +1,6 @@
{
"name": "@automatisch/types",
- "version": "0.1.4",
+ "version": "0.1.5",
"license": "AGPL-3.0",
"description": "Type definitions for automatisch",
"homepage": "https://github.com/automatisch/automatisch",
diff --git a/packages/web/package.json b/packages/web/package.json
index bd1f88ca..cce0b924 100644
--- a/packages/web/package.json
+++ b/packages/web/package.json
@@ -1,11 +1,11 @@
{
"name": "@automatisch/web",
- "version": "0.1.4",
+ "version": "0.1.5",
"license": "AGPL-3.0",
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
"dependencies": {
"@apollo/client": "^3.6.9",
- "@automatisch/types": "^0.1.4",
+ "@automatisch/types": "^0.1.5",
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
"@hookform/resolvers": "^2.8.8",
diff --git a/packages/web/src/components/AccountDropdownMenu/index.tsx b/packages/web/src/components/AccountDropdownMenu/index.tsx
index 31ee564e..4f2dbbf7 100644
--- a/packages/web/src/components/AccountDropdownMenu/index.tsx
+++ b/packages/web/src/components/AccountDropdownMenu/index.tsx
@@ -62,6 +62,7 @@ function AccountDropdownMenu(props: AccountDropdownMenuProps): React.ReactElemen
diff --git a/packages/web/src/components/AddAppConnection/index.tsx b/packages/web/src/components/AddAppConnection/index.tsx
index 3759514b..193bcde9 100644
--- a/packages/web/src/components/AddAppConnection/index.tsx
+++ b/packages/web/src/components/AddAppConnection/index.tsx
@@ -84,7 +84,7 @@ export default function AddAppConnection(props: AddAppConnectionProps): React.Re
}, [connectionId, key, steps, onClose]);
return (
-