Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
06e8f5bfdc |
5
.github/workflows/playwright.yml
vendored
5
.github/workflows/playwright.yml
vendored
@@ -12,9 +12,6 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
BULLMQ_DASHBOARD_USERNAME: root
|
|
||||||
BULLMQ_DASHBOARD_PASSWORD: sample
|
|
||||||
ENABLE_BULLMQ_DASHBOARD: true
|
|
||||||
ENCRYPTION_KEY: sample_encryption_key
|
ENCRYPTION_KEY: sample_encryption_key
|
||||||
WEBHOOK_SECRET_KEY: sample_webhook_secret_key
|
WEBHOOK_SECRET_KEY: sample_webhook_secret_key
|
||||||
APP_SECRET_KEY: sample_app_secret_key
|
APP_SECRET_KEY: sample_app_secret_key
|
||||||
@@ -25,8 +22,8 @@ env:
|
|||||||
POSTGRES_PASSWORD: automatisch_password
|
POSTGRES_PASSWORD: automatisch_password
|
||||||
REDIS_HOST: localhost
|
REDIS_HOST: localhost
|
||||||
APP_ENV: production
|
APP_ENV: production
|
||||||
PORT: 3000
|
|
||||||
LICENSE_KEY: dummy_license_key
|
LICENSE_KEY: dummy_license_key
|
||||||
|
BACKEND_APP_URL: http://localhost:3000
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
@@ -23,6 +23,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bull-board/express": "^3.10.1",
|
"@bull-board/express": "^3.10.1",
|
||||||
"@casl/ability": "^6.5.0",
|
"@casl/ability": "^6.5.0",
|
||||||
|
"@graphql-tools/graphql-file-loader": "^7.3.4",
|
||||||
|
"@graphql-tools/load": "^7.5.2",
|
||||||
"@node-saml/passport-saml": "^4.0.4",
|
"@node-saml/passport-saml": "^4.0.4",
|
||||||
"@rudderstack/rudder-sdk-node": "^1.1.2",
|
"@rudderstack/rudder-sdk-node": "^1.1.2",
|
||||||
"@sentry/node": "^7.42.0",
|
"@sentry/node": "^7.42.0",
|
||||||
@@ -39,7 +41,11 @@
|
|||||||
"express": "~4.18.2",
|
"express": "~4.18.2",
|
||||||
"express-async-errors": "^3.1.1",
|
"express-async-errors": "^3.1.1",
|
||||||
"express-basic-auth": "^1.2.1",
|
"express-basic-auth": "^1.2.1",
|
||||||
|
"express-graphql": "^0.12.0",
|
||||||
"fast-xml-parser": "^4.0.11",
|
"fast-xml-parser": "^4.0.11",
|
||||||
|
"graphql-middleware": "^6.1.15",
|
||||||
|
"graphql-shield": "^7.5.0",
|
||||||
|
"graphql-tools": "^8.2.0",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"http-errors": "~1.6.3",
|
"http-errors": "~1.6.3",
|
||||||
"http-proxy-agent": "^7.0.0",
|
"http-proxy-agent": "^7.0.0",
|
||||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: 'https://api.airtable.com',
|
apiBaseUrl: 'https://api.airtable.com',
|
||||||
iconUrl: '{BASE_URL}/apps/airtable/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/airtable/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/airtable/connection',
|
authDocUrl: '{DOCS_URL}/apps/airtable/connection',
|
||||||
primaryColor: '#FFBF00',
|
primaryColor: 'FFBF00',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: 'https://cloud.appwrite.io',
|
apiBaseUrl: 'https://cloud.appwrite.io',
|
||||||
iconUrl: '{BASE_URL}/apps/appwrite/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/appwrite/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/appwrite/connection',
|
authDocUrl: '{DOCS_URL}/apps/appwrite/connection',
|
||||||
primaryColor: '#FD366E',
|
primaryColor: 'FD366E',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
iconUrl: '{BASE_URL}/apps/azure-openai/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/azure-openai/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/azure-openai/connection',
|
authDocUrl: '{DOCS_URL}/apps/azure-openai/connection',
|
||||||
primaryColor: '#000000',
|
primaryColor: '000000',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://carbone.io',
|
baseUrl: 'https://carbone.io',
|
||||||
apiBaseUrl: 'https://api.carbone.io',
|
apiBaseUrl: 'https://api.carbone.io',
|
||||||
primaryColor: '#6f42c1',
|
primaryColor: '6f42c1',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
actions,
|
actions,
|
||||||
|
@@ -12,8 +12,8 @@ export default defineApp({
|
|||||||
baseUrl: 'https://clickup.com',
|
baseUrl: 'https://clickup.com',
|
||||||
apiBaseUrl: 'https://api.clickup.com/api',
|
apiBaseUrl: 'https://api.clickup.com/api',
|
||||||
iconUrl: '{BASE_URL}/apps/clickup/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/clickup/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/clickup/connection',
|
authDocUrl: 'https://automatisch.io/docs/apps/clickup/connection',
|
||||||
primaryColor: '#FD71AF',
|
primaryColor: 'FD71AF',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -8,7 +8,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
iconUrl: '{BASE_URL}/apps/code/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/code/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/code/connection',
|
authDocUrl: '{DOCS_URL}/apps/code/connection',
|
||||||
primaryColor: '#000000',
|
primaryColor: '000000',
|
||||||
supportsConnections: false,
|
supportsConnections: false,
|
||||||
actions,
|
actions,
|
||||||
});
|
});
|
||||||
|
@@ -9,6 +9,6 @@ export default defineApp({
|
|||||||
supportsConnections: false,
|
supportsConnections: false,
|
||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
primaryColor: '#001F52',
|
primaryColor: '001F52',
|
||||||
actions,
|
actions,
|
||||||
});
|
});
|
||||||
|
@@ -9,6 +9,6 @@ export default defineApp({
|
|||||||
supportsConnections: false,
|
supportsConnections: false,
|
||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
primaryColor: '#001F52',
|
primaryColor: '001F52',
|
||||||
actions,
|
actions,
|
||||||
});
|
});
|
||||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://deepl.com',
|
baseUrl: 'https://deepl.com',
|
||||||
apiBaseUrl: 'https://api.deepl.com',
|
apiBaseUrl: 'https://api.deepl.com',
|
||||||
primaryColor: '#0d2d45',
|
primaryColor: '0d2d45',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
actions,
|
actions,
|
||||||
|
@@ -9,6 +9,6 @@ export default defineApp({
|
|||||||
supportsConnections: false,
|
supportsConnections: false,
|
||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
primaryColor: '#001F52',
|
primaryColor: '001F52',
|
||||||
actions,
|
actions,
|
||||||
});
|
});
|
||||||
|
@@ -14,7 +14,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://discord.com',
|
baseUrl: 'https://discord.com',
|
||||||
apiBaseUrl: 'https://discord.com/api',
|
apiBaseUrl: 'https://discord.com/api',
|
||||||
primaryColor: '#5865f2',
|
primaryColor: '5865f2',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
dynamicData,
|
dynamicData,
|
||||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: 'https://disqus.com/api',
|
apiBaseUrl: 'https://disqus.com/api',
|
||||||
iconUrl: '{BASE_URL}/apps/disqus/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/disqus/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/disqus/connection',
|
authDocUrl: '{DOCS_URL}/apps/disqus/connection',
|
||||||
primaryColor: '#2E9FFF',
|
primaryColor: '2E9FFF',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://dropbox.com',
|
baseUrl: 'https://dropbox.com',
|
||||||
apiBaseUrl: 'https://api.dropboxapi.com',
|
apiBaseUrl: 'https://api.dropboxapi.com',
|
||||||
primaryColor: '#0061ff',
|
primaryColor: '0061ff',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
actions,
|
actions,
|
||||||
|
@@ -9,6 +9,6 @@ export default defineApp({
|
|||||||
supportsConnections: false,
|
supportsConnections: false,
|
||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
primaryColor: '#001F52',
|
primaryColor: '001F52',
|
||||||
actions,
|
actions,
|
||||||
});
|
});
|
||||||
|
@@ -10,7 +10,7 @@ export default defineApp({
|
|||||||
iconUrl: '{BASE_URL}/apps/flickr/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/flickr/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/flickr/connection',
|
authDocUrl: '{DOCS_URL}/apps/flickr/connection',
|
||||||
docUrl: 'https://automatisch.io/docs/flickr',
|
docUrl: 'https://automatisch.io/docs/flickr',
|
||||||
primaryColor: '#000000',
|
primaryColor: '000000',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://www.flickr.com/',
|
baseUrl: 'https://www.flickr.com/',
|
||||||
apiBaseUrl: 'https://www.flickr.com/services',
|
apiBaseUrl: 'https://www.flickr.com/services',
|
||||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://flowers-software.com',
|
baseUrl: 'https://flowers-software.com',
|
||||||
apiBaseUrl: 'https://webapp.flowers-software.com/api',
|
apiBaseUrl: 'https://webapp.flowers-software.com/api',
|
||||||
primaryColor: '#02AFC7',
|
primaryColor: '02AFC7',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
triggers,
|
triggers,
|
||||||
|
@@ -10,7 +10,7 @@ export default defineApp({
|
|||||||
supportsConnections: false,
|
supportsConnections: false,
|
||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
primaryColor: '#001F52',
|
primaryColor: '001F52',
|
||||||
actions,
|
actions,
|
||||||
dynamicFields,
|
dynamicFields,
|
||||||
});
|
});
|
||||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
iconUrl: '{BASE_URL}/apps/ghost/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/ghost/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/ghost/connection',
|
authDocUrl: '{DOCS_URL}/apps/ghost/connection',
|
||||||
primaryColor: '#15171A',
|
primaryColor: '15171A',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: 'https://api.github.com',
|
apiBaseUrl: 'https://api.github.com',
|
||||||
iconUrl: '{BASE_URL}/apps/github/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/github/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/github/connection',
|
authDocUrl: '{DOCS_URL}/apps/github/connection',
|
||||||
primaryColor: '#000000',
|
primaryColor: '000000',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: 'https://gitlab.com',
|
apiBaseUrl: 'https://gitlab.com',
|
||||||
iconUrl: '{BASE_URL}/apps/gitlab/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/gitlab/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/gitlab/connection',
|
authDocUrl: '{DOCS_URL}/apps/gitlab/connection',
|
||||||
primaryColor: '#FC6D26',
|
primaryColor: 'FC6D26',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: 'https://www.googleapis.com/calendar',
|
apiBaseUrl: 'https://www.googleapis.com/calendar',
|
||||||
iconUrl: '{BASE_URL}/apps/google-calendar/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/google-calendar/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/google-calendar/connection',
|
authDocUrl: '{DOCS_URL}/apps/google-calendar/connection',
|
||||||
primaryColor: '#448AFF',
|
primaryColor: '448AFF',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: 'https://www.googleapis.com/drive',
|
apiBaseUrl: 'https://www.googleapis.com/drive',
|
||||||
iconUrl: '{BASE_URL}/apps/google-drive/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/google-drive/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/google-drive/connection',
|
authDocUrl: '{DOCS_URL}/apps/google-drive/connection',
|
||||||
primaryColor: '#1FA463',
|
primaryColor: '1FA463',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: 'https://forms.googleapis.com',
|
apiBaseUrl: 'https://forms.googleapis.com',
|
||||||
iconUrl: '{BASE_URL}/apps/google-forms/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/google-forms/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/google-forms/connection',
|
authDocUrl: '{DOCS_URL}/apps/google-forms/connection',
|
||||||
primaryColor: '#673AB7',
|
primaryColor: '673AB7',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: 'https://sheets.googleapis.com',
|
apiBaseUrl: 'https://sheets.googleapis.com',
|
||||||
iconUrl: '{BASE_URL}/apps/google-sheets/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/google-sheets/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/google-sheets/connection',
|
authDocUrl: '{DOCS_URL}/apps/google-sheets/connection',
|
||||||
primaryColor: '#0F9D58',
|
primaryColor: '0F9D58',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: 'https://tasks.googleapis.com',
|
apiBaseUrl: 'https://tasks.googleapis.com',
|
||||||
iconUrl: '{BASE_URL}/apps/google-tasks/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/google-tasks/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/google-tasks/connection',
|
authDocUrl: '{DOCS_URL}/apps/google-tasks/connection',
|
||||||
primaryColor: '#0066DA',
|
primaryColor: '0066DA',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: 'https://app.tryhelix.ai',
|
apiBaseUrl: 'https://app.tryhelix.ai',
|
||||||
iconUrl: '{BASE_URL}/apps/helix/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/helix/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/helix/connection',
|
authDocUrl: '{DOCS_URL}/apps/helix/connection',
|
||||||
primaryColor: '#000000',
|
primaryColor: '000000',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -9,6 +9,6 @@ export default defineApp({
|
|||||||
supportsConnections: false,
|
supportsConnections: false,
|
||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
primaryColor: '#000000',
|
primaryColor: '000000',
|
||||||
actions,
|
actions,
|
||||||
});
|
});
|
||||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://www.hubspot.com',
|
baseUrl: 'https://www.hubspot.com',
|
||||||
apiBaseUrl: 'https://api.hubapi.com',
|
apiBaseUrl: 'https://api.hubapi.com',
|
||||||
primaryColor: '#F95C35',
|
primaryColor: 'F95C35',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
actions,
|
actions,
|
||||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: 'https://invoicing.co/api',
|
apiBaseUrl: 'https://invoicing.co/api',
|
||||||
iconUrl: '{BASE_URL}/apps/invoice-ninja/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/invoice-ninja/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/invoice-ninja/connection',
|
authDocUrl: '{DOCS_URL}/apps/invoice-ninja/connection',
|
||||||
primaryColor: '#000000',
|
primaryColor: '000000',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -9,11 +9,11 @@ export default defineApp({
|
|||||||
name: 'Jotform',
|
name: 'Jotform',
|
||||||
key: 'jotform',
|
key: 'jotform',
|
||||||
iconUrl: '{BASE_URL}/apps/jotform/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/jotform/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/jotform/connection',
|
authDocUrl: 'https://automatisch.io/docs/apps/jotform/connection',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://www.jotform.com',
|
baseUrl: 'https://www.jotform.com',
|
||||||
apiBaseUrl: 'https://api.jotform.com',
|
apiBaseUrl: 'https://api.jotform.com',
|
||||||
primaryColor: '#FF6100',
|
primaryColor: 'FF6100',
|
||||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
triggers,
|
triggers,
|
||||||
|
@@ -12,8 +12,8 @@ export default defineApp({
|
|||||||
baseUrl: 'https://mailchimp.com',
|
baseUrl: 'https://mailchimp.com',
|
||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
iconUrl: '{BASE_URL}/apps/mailchimp/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/mailchimp/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/mailchimp/connection',
|
authDocUrl: 'https://automatisch.io/docs/apps/mailchimp/connection',
|
||||||
primaryColor: '#000000',
|
primaryColor: '000000',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -7,11 +7,11 @@ export default defineApp({
|
|||||||
name: 'MailerLite',
|
name: 'MailerLite',
|
||||||
key: 'mailerlite',
|
key: 'mailerlite',
|
||||||
iconUrl: '{BASE_URL}/apps/mailerlite/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/mailerlite/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/mailerlite/connection',
|
authDocUrl: 'https://automatisch.io/docs/apps/mailerlite/connection',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://www.mailerlite.com',
|
baseUrl: 'https://www.mailerlite.com',
|
||||||
apiBaseUrl: 'https://connect.mailerlite.com/api',
|
apiBaseUrl: 'https://connect.mailerlite.com/api',
|
||||||
primaryColor: '#09C269',
|
primaryColor: '09C269',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
triggers,
|
triggers,
|
||||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
|||||||
authDocUrl: '{DOCS_URL}/apps/mattermost/connection',
|
authDocUrl: '{DOCS_URL}/apps/mattermost/connection',
|
||||||
baseUrl: 'https://mattermost.com',
|
baseUrl: 'https://mattermost.com',
|
||||||
apiBaseUrl: '', // there is no cloud version of this app, user always need to provide address of own instance when creating connection
|
apiBaseUrl: '', // there is no cloud version of this app, user always need to provide address of own instance when creating connection
|
||||||
primaryColor: '#4a154b',
|
primaryColor: '4a154b',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [setBaseUrl, addXRequestedWithHeader, addAuthHeader],
|
beforeRequest: [setBaseUrl, addXRequestedWithHeader, addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: 'https://api.miro.com',
|
apiBaseUrl: 'https://api.miro.com',
|
||||||
iconUrl: '{BASE_URL}/apps/miro/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/miro/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/miro/connection',
|
authDocUrl: '{DOCS_URL}/apps/miro/connection',
|
||||||
primaryColor: '#F2CA02',
|
primaryColor: 'F2CA02',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: 'https://api.notion.com',
|
apiBaseUrl: 'https://api.notion.com',
|
||||||
iconUrl: '{BASE_URL}/apps/notion/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/notion/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/notion/connection',
|
authDocUrl: '{DOCS_URL}/apps/notion/connection',
|
||||||
primaryColor: '#000000',
|
primaryColor: '000000',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [addAuthHeader, addNotionVersionHeader],
|
beforeRequest: [addAuthHeader, addNotionVersionHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://ntfy.sh',
|
baseUrl: 'https://ntfy.sh',
|
||||||
apiBaseUrl: 'https://ntfy.sh',
|
apiBaseUrl: 'https://ntfy.sh',
|
||||||
primaryColor: '#56bda8',
|
primaryColor: '56bda8',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
actions,
|
actions,
|
||||||
|
@@ -10,7 +10,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://odoo.com',
|
baseUrl: 'https://odoo.com',
|
||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
primaryColor: '#9c5789',
|
primaryColor: '9c5789',
|
||||||
auth,
|
auth,
|
||||||
actions,
|
actions,
|
||||||
});
|
});
|
||||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: 'https://api.openai.com',
|
apiBaseUrl: 'https://api.openai.com',
|
||||||
iconUrl: '{BASE_URL}/apps/openai/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/openai/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/openai/connection',
|
authDocUrl: '{DOCS_URL}/apps/openai/connection',
|
||||||
primaryColor: '#000000',
|
primaryColor: '000000',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
iconUrl: '{BASE_URL}/apps/pipedrive/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/pipedrive/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/pipedrive/connection',
|
authDocUrl: '{DOCS_URL}/apps/pipedrive/connection',
|
||||||
primaryColor: '#FFFFFF',
|
primaryColor: 'FFFFFF',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://placetel.de',
|
baseUrl: 'https://placetel.de',
|
||||||
apiBaseUrl: 'https://api.placetel.de',
|
apiBaseUrl: 'https://api.placetel.de',
|
||||||
primaryColor: '#069dd9',
|
primaryColor: '069dd9',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
triggers,
|
triggers,
|
||||||
|
@@ -10,7 +10,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
primaryColor: '#336791',
|
primaryColor: '336791',
|
||||||
auth,
|
auth,
|
||||||
actions,
|
actions,
|
||||||
});
|
});
|
||||||
|
@@ -10,7 +10,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: 'https://api.pushover.net',
|
apiBaseUrl: 'https://api.pushover.net',
|
||||||
iconUrl: '{BASE_URL}/apps/pushover/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/pushover/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/pushover/connection',
|
authDocUrl: '{DOCS_URL}/apps/pushover/connection',
|
||||||
primaryColor: '#249DF1',
|
primaryColor: '249DF1',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
auth,
|
auth,
|
||||||
actions,
|
actions,
|
||||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: 'https://oauth.reddit.com',
|
apiBaseUrl: 'https://oauth.reddit.com',
|
||||||
iconUrl: '{BASE_URL}/apps/reddit/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/reddit/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/reddit/connection',
|
authDocUrl: '{DOCS_URL}/apps/reddit/connection',
|
||||||
primaryColor: '#FF4500',
|
primaryColor: 'FF4500',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://www.remove.bg',
|
baseUrl: 'https://www.remove.bg',
|
||||||
apiBaseUrl: 'https://api.remove.bg/v1.0',
|
apiBaseUrl: 'https://api.remove.bg/v1.0',
|
||||||
primaryColor: '#55636c',
|
primaryColor: '55636c',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
actions,
|
actions,
|
||||||
|
@@ -9,6 +9,6 @@ export default defineApp({
|
|||||||
supportsConnections: false,
|
supportsConnections: false,
|
||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
primaryColor: '#ff8800',
|
primaryColor: 'ff8800',
|
||||||
triggers,
|
triggers,
|
||||||
});
|
});
|
||||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://salesforce.com',
|
baseUrl: 'https://salesforce.com',
|
||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
primaryColor: '#00A1E0',
|
primaryColor: '00A1E0',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
triggers,
|
triggers,
|
||||||
|
@@ -9,7 +9,7 @@ export default defineApp({
|
|||||||
authDocUrl: '{DOCS_URL}/apps/scheduler/connection',
|
authDocUrl: '{DOCS_URL}/apps/scheduler/connection',
|
||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
primaryColor: '#0059F7',
|
primaryColor: '0059F7',
|
||||||
supportsConnections: false,
|
supportsConnections: false,
|
||||||
triggers,
|
triggers,
|
||||||
});
|
});
|
||||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
iconUrl: '{BASE_URL}/apps/self-hosted-llm/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/self-hosted-llm/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/self-hosted-llm/connection',
|
authDocUrl: '{DOCS_URL}/apps/self-hosted-llm/connection',
|
||||||
primaryColor: '#000000',
|
primaryColor: '000000',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://signalwire.com',
|
baseUrl: 'https://signalwire.com',
|
||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
primaryColor: '#044cf6',
|
primaryColor: '044cf6',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
triggers,
|
triggers,
|
||||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://slack.com',
|
baseUrl: 'https://slack.com',
|
||||||
apiBaseUrl: 'https://slack.com/api',
|
apiBaseUrl: 'https://slack.com/api',
|
||||||
primaryColor: '#4a154b',
|
primaryColor: '4a154b',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
actions,
|
actions,
|
||||||
|
@@ -10,7 +10,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
primaryColor: '#2DAAE1',
|
primaryColor: '2DAAE1',
|
||||||
auth,
|
auth,
|
||||||
actions,
|
actions,
|
||||||
});
|
});
|
||||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://spotify.com',
|
baseUrl: 'https://spotify.com',
|
||||||
apiBaseUrl: 'https://api.spotify.com',
|
apiBaseUrl: 'https://api.spotify.com',
|
||||||
primaryColor: '#000000',
|
primaryColor: '000000',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
actions,
|
actions,
|
||||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://www.strava.com',
|
baseUrl: 'https://www.strava.com',
|
||||||
apiBaseUrl: 'https://www.strava.com/api',
|
apiBaseUrl: 'https://www.strava.com/api',
|
||||||
primaryColor: '#fc4c01',
|
primaryColor: 'fc4c01',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
actions,
|
actions,
|
||||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://stripe.com',
|
baseUrl: 'https://stripe.com',
|
||||||
apiBaseUrl: 'https://api.stripe.com',
|
apiBaseUrl: 'https://api.stripe.com',
|
||||||
primaryColor: '#635bff',
|
primaryColor: '635bff',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
triggers,
|
triggers,
|
||||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://telegram.org',
|
baseUrl: 'https://telegram.org',
|
||||||
apiBaseUrl: 'https://api.telegram.org',
|
apiBaseUrl: 'https://api.telegram.org',
|
||||||
primaryColor: '#2AABEE',
|
primaryColor: '2AABEE',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
actions,
|
actions,
|
||||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://todoist.com',
|
baseUrl: 'https://todoist.com',
|
||||||
apiBaseUrl: 'https://api.todoist.com/rest/v2',
|
apiBaseUrl: 'https://api.todoist.com/rest/v2',
|
||||||
primaryColor: '#e44332',
|
primaryColor: 'e44332',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
triggers,
|
triggers,
|
||||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
|||||||
iconUrl: '{BASE_URL}/apps/trello/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/trello/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/trello/connection',
|
authDocUrl: '{DOCS_URL}/apps/trello/connection',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
primaryColor: '#0079bf',
|
primaryColor: '0079bf',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
actions,
|
actions,
|
||||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://twilio.com',
|
baseUrl: 'https://twilio.com',
|
||||||
apiBaseUrl: 'https://api.twilio.com',
|
apiBaseUrl: 'https://api.twilio.com',
|
||||||
primaryColor: '#e1000f',
|
primaryColor: 'e1000f',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
triggers,
|
triggers,
|
||||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://twitter.com',
|
baseUrl: 'https://twitter.com',
|
||||||
apiBaseUrl: 'https://api.twitter.com',
|
apiBaseUrl: 'https://api.twitter.com',
|
||||||
primaryColor: '#1da1f2',
|
primaryColor: '1da1f2',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
triggers,
|
triggers,
|
||||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://typeform.com',
|
baseUrl: 'https://typeform.com',
|
||||||
apiBaseUrl: 'https://api.typeform.com',
|
apiBaseUrl: 'https://api.typeform.com',
|
||||||
primaryColor: '#262627',
|
primaryColor: '262627',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
triggers,
|
triggers,
|
||||||
|
@@ -14,7 +14,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
primaryColor: '#39a86d',
|
primaryColor: '39a86d',
|
||||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
triggers,
|
triggers,
|
||||||
|
@@ -10,7 +10,7 @@ export default defineApp({
|
|||||||
supportsConnections: false,
|
supportsConnections: false,
|
||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
primaryColor: '#0059F7',
|
primaryColor: '0059F7',
|
||||||
actions,
|
actions,
|
||||||
triggers,
|
triggers,
|
||||||
});
|
});
|
||||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
baseUrl: 'https://wordpress.com',
|
baseUrl: 'https://wordpress.com',
|
||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
primaryColor: '#464342',
|
primaryColor: '464342',
|
||||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
triggers,
|
triggers,
|
||||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: 'https://api.xero.com',
|
apiBaseUrl: 'https://api.xero.com',
|
||||||
iconUrl: '{BASE_URL}/apps/xero/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/xero/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/xero/connection',
|
authDocUrl: '{DOCS_URL}/apps/xero/connection',
|
||||||
primaryColor: '#13B5EA',
|
primaryColor: '13B5EA',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -10,7 +10,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: 'https://api.ynab.com/v1',
|
apiBaseUrl: 'https://api.ynab.com/v1',
|
||||||
iconUrl: '{BASE_URL}/apps/you-need-a-budget/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/you-need-a-budget/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/you-need-a-budget/connection',
|
authDocUrl: '{DOCS_URL}/apps/you-need-a-budget/connection',
|
||||||
primaryColor: '#19223C',
|
primaryColor: '19223C',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -10,7 +10,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: 'https://www.googleapis.com/youtube',
|
apiBaseUrl: 'https://www.googleapis.com/youtube',
|
||||||
iconUrl: '{BASE_URL}/apps/youtube/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/youtube/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/youtube/connection',
|
authDocUrl: '{DOCS_URL}/apps/youtube/connection',
|
||||||
primaryColor: '#FF0000',
|
primaryColor: 'FF0000',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
|||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
iconUrl: '{BASE_URL}/apps/zendesk/assets/favicon.svg',
|
iconUrl: '{BASE_URL}/apps/zendesk/assets/favicon.svg',
|
||||||
authDocUrl: '{DOCS_URL}/apps/zendesk/connection',
|
authDocUrl: '{DOCS_URL}/apps/zendesk/connection',
|
||||||
primaryColor: '#17494d',
|
primaryColor: '17494d',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
|
@@ -1,28 +1,23 @@
|
|||||||
|
import pick from 'lodash/pick.js';
|
||||||
import { renderObject } from '../../../../../helpers/renderer.js';
|
import { renderObject } from '../../../../../helpers/renderer.js';
|
||||||
import Config from '../../../../../models/config.js';
|
import Config from '../../../../../models/config.js';
|
||||||
|
|
||||||
export default async (request, response) => {
|
export default async (request, response) => {
|
||||||
const config = await Config.query().updateFirstOrInsert(
|
const config = configParams(request);
|
||||||
configParams(request)
|
|
||||||
);
|
await Config.batchUpdate(config);
|
||||||
|
|
||||||
renderObject(response, config);
|
renderObject(response, config);
|
||||||
};
|
};
|
||||||
|
|
||||||
const configParams = (request) => {
|
const configParams = (request) => {
|
||||||
const {
|
const updatableConfigurationKeys = [
|
||||||
logoSvgData,
|
'logo.svgData',
|
||||||
palettePrimaryDark,
|
'palette.primary.dark',
|
||||||
palettePrimaryLight,
|
'palette.primary.light',
|
||||||
palettePrimaryMain,
|
'palette.primary.main',
|
||||||
title,
|
'title',
|
||||||
} = request.body;
|
];
|
||||||
|
|
||||||
return {
|
return pick(request.body, updatableConfigurationKeys);
|
||||||
logoSvgData,
|
|
||||||
palettePrimaryDark,
|
|
||||||
palettePrimaryLight,
|
|
||||||
palettePrimaryMain,
|
|
||||||
title,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
@@ -5,7 +5,7 @@ import app from '../../../../../app.js';
|
|||||||
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
|
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
|
||||||
import { createUser } from '../../../../../../test/factories/user.js';
|
import { createUser } from '../../../../../../test/factories/user.js';
|
||||||
import { createRole } from '../../../../../../test/factories/role.js';
|
import { createRole } from '../../../../../../test/factories/role.js';
|
||||||
import { updateConfig } from '../../../../../../test/factories/config.js';
|
import { createBulkConfig } from '../../../../../../test/factories/config.js';
|
||||||
import * as license from '../../../../../helpers/license.ee.js';
|
import * as license from '../../../../../helpers/license.ee.js';
|
||||||
|
|
||||||
describe('PATCH /api/v1/admin/config', () => {
|
describe('PATCH /api/v1/admin/config', () => {
|
||||||
@@ -30,13 +30,13 @@ describe('PATCH /api/v1/admin/config', () => {
|
|||||||
|
|
||||||
const appConfig = {
|
const appConfig = {
|
||||||
title,
|
title,
|
||||||
palettePrimaryMain: palettePrimaryMain,
|
'palette.primary.main': palettePrimaryMain,
|
||||||
palettePrimaryDark: palettePrimaryDark,
|
'palette.primary.dark': palettePrimaryDark,
|
||||||
palettePrimaryLight: palettePrimaryLight,
|
'palette.primary.light': palettePrimaryLight,
|
||||||
logoSvgData: logoSvgData,
|
'logo.svgData': logoSvgData,
|
||||||
};
|
};
|
||||||
|
|
||||||
await updateConfig(appConfig);
|
await createBulkConfig(appConfig);
|
||||||
|
|
||||||
const newTitle = 'Updated title';
|
const newTitle = 'Updated title';
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ describe('PATCH /api/v1/admin/config', () => {
|
|||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
expect(response.body.data.title).toEqual(newTitle);
|
expect(response.body.data.title).toEqual(newTitle);
|
||||||
expect(response.body.meta.type).toEqual('Config');
|
expect(response.body.meta.type).toEqual('Object');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return created config for unexisting config', async () => {
|
it('should return created config for unexisting config', async () => {
|
||||||
@@ -68,7 +68,7 @@ describe('PATCH /api/v1/admin/config', () => {
|
|||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
expect(response.body.data.title).toEqual(newTitle);
|
expect(response.body.data.title).toEqual(newTitle);
|
||||||
expect(response.body.meta.type).toEqual('Config');
|
expect(response.body.meta.type).toEqual('Object');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null for deleted config entry', async () => {
|
it('should return null for deleted config entry', async () => {
|
||||||
@@ -83,6 +83,6 @@ describe('PATCH /api/v1/admin/config', () => {
|
|||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
expect(response.body.data.title).toBeNull();
|
expect(response.body.data.title).toBeNull();
|
||||||
expect(response.body.meta.type).toEqual('Config');
|
expect(response.body.meta.type).toEqual('Object');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,8 +1,25 @@
|
|||||||
|
import appConfig from '../../../../config/app.js';
|
||||||
import Config from '../../../../models/config.js';
|
import Config from '../../../../models/config.js';
|
||||||
import { renderObject } from '../../../../helpers/renderer.js';
|
import { renderObject } from '../../../../helpers/renderer.js';
|
||||||
|
|
||||||
export default async (request, response) => {
|
export default async (request, response) => {
|
||||||
const config = await Config.get();
|
const defaultConfig = {
|
||||||
|
disableNotificationsPage: appConfig.disableNotificationsPage,
|
||||||
|
disableFavicon: appConfig.disableFavicon,
|
||||||
|
additionalDrawerLink: appConfig.additionalDrawerLink,
|
||||||
|
additionalDrawerLinkIcon: appConfig.additionalDrawerLinkIcon,
|
||||||
|
additionalDrawerLinkText: appConfig.additionalDrawerLinkText,
|
||||||
|
};
|
||||||
|
|
||||||
|
let config = await Config.query().orderBy('key', 'asc');
|
||||||
|
|
||||||
|
config = config.reduce((computedConfig, configEntry) => {
|
||||||
|
const { key, value } = configEntry;
|
||||||
|
|
||||||
|
computedConfig[key] = value?.data;
|
||||||
|
|
||||||
|
return computedConfig;
|
||||||
|
}, defaultConfig);
|
||||||
|
|
||||||
renderObject(response, config);
|
renderObject(response, config);
|
||||||
};
|
};
|
||||||
|
@@ -1,47 +1,66 @@
|
|||||||
import { vi, expect, describe, it } from 'vitest';
|
import { vi, expect, describe, it } from 'vitest';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
import { updateConfig } from '../../../../../test/factories/config.js';
|
import { createConfig } from '../../../../../test/factories/config.js';
|
||||||
import app from '../../../../app.js';
|
import app from '../../../../app.js';
|
||||||
import configMock from '../../../../../test/mocks/rest/api/v1/automatisch/config.js';
|
import configMock from '../../../../../test/mocks/rest/api/v1/automatisch/config.js';
|
||||||
import * as license from '../../../../helpers/license.ee.js';
|
import * as license from '../../../../helpers/license.ee.js';
|
||||||
import appConfig from '../../../../config/app.js';
|
import appConfig from '../../../../config/app.js';
|
||||||
|
|
||||||
describe('GET /api/v1/automatisch/config', () => {
|
describe('GET /api/v1/automatisch/config', () => {
|
||||||
it('should return Automatisch config along with static config', async () => {
|
it('should return Automatisch config', async () => {
|
||||||
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
||||||
vi.spyOn(appConfig, 'disableNotificationsPage', 'get').mockReturnValue(
|
|
||||||
true
|
|
||||||
);
|
|
||||||
vi.spyOn(appConfig, 'disableFavicon', 'get').mockReturnValue(true);
|
|
||||||
vi.spyOn(appConfig, 'additionalDrawerLink', 'get').mockReturnValue('link');
|
|
||||||
vi.spyOn(appConfig, 'additionalDrawerLinkIcon', 'get').mockReturnValue(
|
|
||||||
'icon'
|
|
||||||
);
|
|
||||||
vi.spyOn(appConfig, 'additionalDrawerLinkText', 'get').mockReturnValue(
|
|
||||||
'text'
|
|
||||||
);
|
|
||||||
|
|
||||||
const config = await updateConfig({
|
const logoConfig = await createConfig({
|
||||||
logoSvgData: '<svg>Sample</svg>',
|
key: 'logo.svgData',
|
||||||
palettePrimaryDark: '#001f52',
|
value: { data: '<svg>Sample</svg>' },
|
||||||
palettePrimaryLight: '#4286FF',
|
});
|
||||||
palettePrimaryMain: '#0059F7',
|
|
||||||
title: 'Sample Title',
|
const primaryDarkConfig = await createConfig({
|
||||||
|
key: 'palette.primary.dark',
|
||||||
|
value: { data: '#001F52' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const primaryLightConfig = await createConfig({
|
||||||
|
key: 'palette.primary.light',
|
||||||
|
value: { data: '#4286FF' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const primaryMainConfig = await createConfig({
|
||||||
|
key: 'palette.primary.main',
|
||||||
|
value: { data: '#0059F7' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const titleConfig = await createConfig({
|
||||||
|
key: 'title',
|
||||||
|
value: { data: 'Sample Title' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await request(app)
|
const response = await request(app)
|
||||||
.get('/api/v1/automatisch/config')
|
.get('/api/v1/automatisch/config')
|
||||||
.expect(200);
|
.expect(200);
|
||||||
|
|
||||||
const expectedPayload = configMock({
|
const expectedPayload = configMock(
|
||||||
...config,
|
logoConfig,
|
||||||
disableNotificationsPage: true,
|
primaryDarkConfig,
|
||||||
disableFavicon: true,
|
primaryLightConfig,
|
||||||
additionalDrawerLink: 'link',
|
primaryMainConfig,
|
||||||
additionalDrawerLinkIcon: 'icon',
|
titleConfig
|
||||||
additionalDrawerLinkText: 'text',
|
);
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedPayload);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.body).toStrictEqual(expectedPayload);
|
it('should return additional environment variables', async () => {
|
||||||
|
vi.spyOn(appConfig, 'disableNotificationsPage', 'get').mockReturnValue(true);
|
||||||
|
vi.spyOn(appConfig, 'disableFavicon', 'get').mockReturnValue(true);
|
||||||
|
vi.spyOn(appConfig, 'additionalDrawerLink', 'get').mockReturnValue('link');
|
||||||
|
vi.spyOn(appConfig, 'additionalDrawerLinkIcon', 'get').mockReturnValue('icon');
|
||||||
|
vi.spyOn(appConfig, 'additionalDrawerLinkText', 'get').mockReturnValue('text');
|
||||||
|
|
||||||
|
expect(appConfig.disableNotificationsPage).toEqual(true);
|
||||||
|
expect(appConfig.disableFavicon).toEqual(true);
|
||||||
|
expect(appConfig.additionalDrawerLink).toEqual('link');
|
||||||
|
expect(appConfig.additionalDrawerLinkIcon).toEqual('icon');
|
||||||
|
expect(appConfig.additionalDrawerLinkText).toEqual('text');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -5,7 +5,7 @@ import Config from '../../../../../models/config.js';
|
|||||||
import User from '../../../../../models/user.js';
|
import User from '../../../../../models/user.js';
|
||||||
import { createRole } from '../../../../../../test/factories/role';
|
import { createRole } from '../../../../../../test/factories/role';
|
||||||
import { createUser } from '../../../../../../test/factories/user';
|
import { createUser } from '../../../../../../test/factories/user';
|
||||||
import { markInstallationCompleted } from '../../../../../../test/factories/config';
|
import { createInstallationCompletedConfig } from '../../../../../../test/factories/config';
|
||||||
|
|
||||||
describe('POST /api/v1/installation/users', () => {
|
describe('POST /api/v1/installation/users', () => {
|
||||||
let adminRole;
|
let adminRole;
|
||||||
@@ -59,7 +59,7 @@ describe('POST /api/v1/installation/users', () => {
|
|||||||
|
|
||||||
describe('for completed installations', () => {
|
describe('for completed installations', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await markInstallationCompleted();
|
await createInstallationCompletedConfig();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should respond with HTTP 403 when installation completed', async () => {
|
it('should respond with HTTP 403 when installation completed', async () => {
|
||||||
|
@@ -169,7 +169,7 @@ describe('POST /api/v1/steps/:stepId/dynamic-data', () => {
|
|||||||
dynamicDataKey: 'listRepos',
|
dynamicDataKey: 'listRepos',
|
||||||
parameters: {},
|
parameters: {},
|
||||||
})
|
})
|
||||||
.expect(422);
|
.expect(200);
|
||||||
|
|
||||||
expect(response.body.errors).toEqual(errors);
|
expect(response.body.errors).toEqual(errors);
|
||||||
});
|
});
|
||||||
|
@@ -1,105 +0,0 @@
|
|||||||
export async function up(knex) {
|
|
||||||
await knex.schema.alterTable('config', (table) => {
|
|
||||||
table.dropUnique('key');
|
|
||||||
|
|
||||||
table.string('key').nullable().alter();
|
|
||||||
table.boolean('installation_completed').defaultTo(false);
|
|
||||||
table.text('logo_svg_data');
|
|
||||||
table.text('palette_primary_dark');
|
|
||||||
table.text('palette_primary_light');
|
|
||||||
table.text('palette_primary_main');
|
|
||||||
table.string('title');
|
|
||||||
});
|
|
||||||
|
|
||||||
const config = await knex('config').select('key', 'value');
|
|
||||||
|
|
||||||
const newConfigData = {
|
|
||||||
logo_svg_data: getValueForKey(config, 'logo.svgData'),
|
|
||||||
palette_primary_dark: getValueForKey(config, 'palette.primary.dark'),
|
|
||||||
palette_primary_light: getValueForKey(config, 'palette.primary.light'),
|
|
||||||
palette_primary_main: getValueForKey(config, 'palette.primary.main'),
|
|
||||||
title: getValueForKey(config, 'title'),
|
|
||||||
installation_completed: getValueForKey(config, 'installation.completed'),
|
|
||||||
};
|
|
||||||
|
|
||||||
const [configEntry] = await knex('config')
|
|
||||||
.insert(newConfigData)
|
|
||||||
.select('id')
|
|
||||||
.returning('id');
|
|
||||||
|
|
||||||
await knex('config').where('id', '!=', configEntry.id).delete();
|
|
||||||
|
|
||||||
await knex.schema.alterTable('config', (table) => {
|
|
||||||
table.dropColumn('key');
|
|
||||||
table.dropColumn('value');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex) {
|
|
||||||
await knex.schema.alterTable('config', (table) => {
|
|
||||||
table.string('key');
|
|
||||||
table.jsonb('value').notNullable().defaultTo({});
|
|
||||||
});
|
|
||||||
|
|
||||||
const configRow = await knex('config').first();
|
|
||||||
|
|
||||||
const config = [
|
|
||||||
{
|
|
||||||
key: 'logo.svgData',
|
|
||||||
value: {
|
|
||||||
data: configRow.logo_svg_data,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'palette.primary.dark',
|
|
||||||
value: {
|
|
||||||
data: configRow.palette_primary_dark,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'palette.primary.light',
|
|
||||||
value: {
|
|
||||||
data: configRow.palette_primary_light,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'palette.primary.main',
|
|
||||||
value: {
|
|
||||||
data: configRow.palette_primary_main,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'title',
|
|
||||||
value: {
|
|
||||||
data: configRow.title,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'installation.completed',
|
|
||||||
value: {
|
|
||||||
data: configRow.installation_completed,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
await knex('config').insert(config).returning('id');
|
|
||||||
|
|
||||||
await knex('config').where('id', '=', configRow.id).delete();
|
|
||||||
|
|
||||||
await knex.schema.alterTable('config', (table) => {
|
|
||||||
table.dropColumn('installation_completed');
|
|
||||||
table.dropColumn('logo_svg_data');
|
|
||||||
table.dropColumn('palette_primary_dark');
|
|
||||||
table.dropColumn('palette_primary_light');
|
|
||||||
table.dropColumn('palette_primary_main');
|
|
||||||
table.dropColumn('title');
|
|
||||||
|
|
||||||
table.string('key').unique().notNullable().alter();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getValueForKey(rows, key) {
|
|
||||||
const row = rows.find((row) => row.key === key);
|
|
||||||
|
|
||||||
return row?.value?.data || null;
|
|
||||||
}
|
|
20
packages/backend/src/graphql/mutation-resolvers.js
Normal file
20
packages/backend/src/graphql/mutation-resolvers.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// Converted mutations
|
||||||
|
import executeFlow from './mutations/execute-flow.js';
|
||||||
|
import verifyConnection from './mutations/verify-connection.js';
|
||||||
|
import updateCurrentUser from './mutations/update-current-user.js';
|
||||||
|
import generateAuthUrl from './mutations/generate-auth-url.js';
|
||||||
|
import createConnection from './mutations/create-connection.js';
|
||||||
|
import resetConnection from './mutations/reset-connection.js';
|
||||||
|
import updateConnection from './mutations/update-connection.js';
|
||||||
|
|
||||||
|
const mutationResolvers = {
|
||||||
|
createConnection,
|
||||||
|
executeFlow,
|
||||||
|
generateAuthUrl,
|
||||||
|
resetConnection,
|
||||||
|
updateConnection,
|
||||||
|
updateCurrentUser,
|
||||||
|
verifyConnection,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default mutationResolvers;
|
48
packages/backend/src/graphql/mutations/create-connection.js
Normal file
48
packages/backend/src/graphql/mutations/create-connection.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import App from '../../models/app.js';
|
||||||
|
import AppConfig from '../../models/app-config.js';
|
||||||
|
|
||||||
|
const createConnection = async (_parent, params, context) => {
|
||||||
|
context.currentUser.can('create', 'Connection');
|
||||||
|
|
||||||
|
const { key, appAuthClientId } = params.input;
|
||||||
|
|
||||||
|
const app = await App.findOneByKey(key);
|
||||||
|
|
||||||
|
const appConfig = await AppConfig.query().findOne({ key });
|
||||||
|
|
||||||
|
let formattedData = params.input.formattedData;
|
||||||
|
if (appConfig) {
|
||||||
|
if (appConfig.disabled)
|
||||||
|
throw new Error(
|
||||||
|
'This application has been disabled for new connections!'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!appConfig.allowCustomConnection && formattedData)
|
||||||
|
throw new Error(`Custom connections cannot be created for ${app.name}!`);
|
||||||
|
|
||||||
|
if (appConfig.shared && !formattedData) {
|
||||||
|
const authClient = await appConfig
|
||||||
|
.$relatedQuery('appAuthClients')
|
||||||
|
.findById(appAuthClientId)
|
||||||
|
.where({
|
||||||
|
active: true,
|
||||||
|
})
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
formattedData = authClient.formattedAuthDefaults;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdConnection = await context.currentUser
|
||||||
|
.$relatedQuery('connections')
|
||||||
|
.insert({
|
||||||
|
key,
|
||||||
|
appAuthClientId,
|
||||||
|
formattedData,
|
||||||
|
verified: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return createdConnection;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createConnection;
|
@@ -0,0 +1,16 @@
|
|||||||
|
import AppAuthClient from '../../models/app-auth-client';
|
||||||
|
|
||||||
|
const deleteAppAuthClient = async (_parent, params, context) => {
|
||||||
|
context.currentUser.can('delete', 'App');
|
||||||
|
|
||||||
|
await AppAuthClient.query()
|
||||||
|
.delete()
|
||||||
|
.findOne({
|
||||||
|
id: params.input.id,
|
||||||
|
})
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default deleteAppAuthClient;
|
28
packages/backend/src/graphql/mutations/execute-flow.js
Normal file
28
packages/backend/src/graphql/mutations/execute-flow.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import testRun from '../../services/test-run.js';
|
||||||
|
import Step from '../../models/step.js';
|
||||||
|
|
||||||
|
const executeFlow = async (_parent, params, context) => {
|
||||||
|
const conditions = context.currentUser.can('update', 'Flow');
|
||||||
|
const isCreator = conditions.isCreator;
|
||||||
|
const allSteps = Step.query();
|
||||||
|
const userSteps = context.currentUser.$relatedQuery('steps');
|
||||||
|
const baseQuery = isCreator ? userSteps : allSteps;
|
||||||
|
|
||||||
|
const { stepId } = params.input;
|
||||||
|
|
||||||
|
const untilStep = await baseQuery.clone().findById(stepId).throwIfNotFound();
|
||||||
|
|
||||||
|
const { executionStep } = await testRun({ stepId });
|
||||||
|
|
||||||
|
if (executionStep.isFailed) {
|
||||||
|
throw new Error(JSON.stringify(executionStep.errorDetails));
|
||||||
|
}
|
||||||
|
|
||||||
|
await untilStep.$query().patch({
|
||||||
|
status: 'completed',
|
||||||
|
});
|
||||||
|
|
||||||
|
return { data: executionStep.dataOut, step: untilStep };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default executeFlow;
|
30
packages/backend/src/graphql/mutations/generate-auth-url.js
Normal file
30
packages/backend/src/graphql/mutations/generate-auth-url.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import globalVariable from '../../helpers/global-variable.js';
|
||||||
|
import App from '../../models/app.js';
|
||||||
|
|
||||||
|
const generateAuthUrl = async (_parent, params, context) => {
|
||||||
|
context.currentUser.can('create', 'Connection');
|
||||||
|
|
||||||
|
const connection = await context.currentUser
|
||||||
|
.$relatedQuery('connections')
|
||||||
|
.findOne({
|
||||||
|
id: params.input.id,
|
||||||
|
})
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
if (!connection.formattedData) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const authInstance = (
|
||||||
|
await import(`../../apps/${connection.key}/auth/index.js`)
|
||||||
|
).default;
|
||||||
|
|
||||||
|
const app = await App.findOneByKey(connection.key);
|
||||||
|
|
||||||
|
const $ = await globalVariable({ connection, app });
|
||||||
|
await authInstance.generateAuthUrl($);
|
||||||
|
|
||||||
|
return connection.formattedData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default generateAuthUrl;
|
22
packages/backend/src/graphql/mutations/reset-connection.js
Normal file
22
packages/backend/src/graphql/mutations/reset-connection.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
const resetConnection = async (_parent, params, context) => {
|
||||||
|
context.currentUser.can('create', 'Connection');
|
||||||
|
|
||||||
|
let connection = await context.currentUser
|
||||||
|
.$relatedQuery('connections')
|
||||||
|
.findOne({
|
||||||
|
id: params.input.id,
|
||||||
|
})
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
if (!connection.formattedData) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
connection = await connection.$query().patchAndFetch({
|
||||||
|
formattedData: { screenName: connection.formattedData.screenName },
|
||||||
|
});
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default resetConnection;
|
33
packages/backend/src/graphql/mutations/update-connection.js
Normal file
33
packages/backend/src/graphql/mutations/update-connection.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import AppAuthClient from '../../models/app-auth-client.js';
|
||||||
|
|
||||||
|
const updateConnection = async (_parent, params, context) => {
|
||||||
|
context.currentUser.can('create', 'Connection');
|
||||||
|
|
||||||
|
let connection = await context.currentUser
|
||||||
|
.$relatedQuery('connections')
|
||||||
|
.findOne({
|
||||||
|
id: params.input.id,
|
||||||
|
})
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
let formattedData = params.input.formattedData;
|
||||||
|
|
||||||
|
if (params.input.appAuthClientId) {
|
||||||
|
const appAuthClient = await AppAuthClient.query()
|
||||||
|
.findById(params.input.appAuthClientId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
formattedData = appAuthClient.formattedAuthDefaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
connection = await connection.$query().patchAndFetch({
|
||||||
|
formattedData: {
|
||||||
|
...connection.formattedData,
|
||||||
|
...formattedData,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default updateConnection;
|
@@ -0,0 +1,11 @@
|
|||||||
|
const updateCurrentUser = async (_parent, params, context) => {
|
||||||
|
const user = await context.currentUser.$query().patchAndFetch({
|
||||||
|
email: params.input.email,
|
||||||
|
password: params.input.password,
|
||||||
|
fullName: params.input.fullName,
|
||||||
|
});
|
||||||
|
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default updateCurrentUser;
|
18
packages/backend/src/graphql/mutations/update-flow.js
Normal file
18
packages/backend/src/graphql/mutations/update-flow.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
const updateFlow = async (_parent, params, context) => {
|
||||||
|
context.currentUser.can('update', 'Flow');
|
||||||
|
|
||||||
|
let flow = await context.currentUser
|
||||||
|
.$relatedQuery('flows')
|
||||||
|
.findOne({
|
||||||
|
id: params.input.id,
|
||||||
|
})
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
flow = await flow.$query().patchAndFetch({
|
||||||
|
name: params.input.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
return flow;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default updateFlow;
|
62
packages/backend/src/graphql/mutations/update-role.ee.js
Normal file
62
packages/backend/src/graphql/mutations/update-role.ee.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import Role from '../../models/role.js';
|
||||||
|
import Permission from '../../models/permission.js';
|
||||||
|
import permissionCatalog from '../../helpers/permission-catalog.ee.js';
|
||||||
|
|
||||||
|
const updateRole = async (_parent, params, context) => {
|
||||||
|
context.currentUser.can('update', 'Role');
|
||||||
|
|
||||||
|
const { id, name, description, permissions } = params.input;
|
||||||
|
|
||||||
|
const role = await Role.query().findById(id).throwIfNotFound();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updatedRole = await Role.transaction(async (trx) => {
|
||||||
|
await role.$relatedQuery('permissions', trx).delete();
|
||||||
|
|
||||||
|
if (permissions?.length) {
|
||||||
|
const sanitizedPermissions = permissions
|
||||||
|
.filter((permission) => {
|
||||||
|
const { action, subject, conditions } = permission;
|
||||||
|
|
||||||
|
const relevantAction = permissionCatalog.actions.find(
|
||||||
|
(actionCatalogItem) => actionCatalogItem.key === action
|
||||||
|
);
|
||||||
|
const validSubject = relevantAction.subjects.includes(subject);
|
||||||
|
const validConditions = conditions.every((condition) => {
|
||||||
|
return !!permissionCatalog.conditions.find(
|
||||||
|
(conditionCatalogItem) => conditionCatalogItem.key === condition
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return validSubject && validConditions;
|
||||||
|
})
|
||||||
|
.map((permission) => ({
|
||||||
|
...permission,
|
||||||
|
roleId: role.id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await Permission.query().insert(sanitizedPermissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
await role.$query(trx).patch({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
});
|
||||||
|
|
||||||
|
return await Role.query(trx)
|
||||||
|
.leftJoinRelated({
|
||||||
|
permissions: true,
|
||||||
|
})
|
||||||
|
.withGraphFetched({
|
||||||
|
permissions: true,
|
||||||
|
})
|
||||||
|
.findById(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedRole;
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error('The role could not be updated!');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default updateRole;
|
29
packages/backend/src/graphql/mutations/verify-connection.js
Normal file
29
packages/backend/src/graphql/mutations/verify-connection.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import App from '../../models/app.js';
|
||||||
|
import globalVariable from '../../helpers/global-variable.js';
|
||||||
|
|
||||||
|
const verifyConnection = async (_parent, params, context) => {
|
||||||
|
context.currentUser.can('create', 'Connection');
|
||||||
|
|
||||||
|
let connection = await context.currentUser
|
||||||
|
.$relatedQuery('connections')
|
||||||
|
.findOne({
|
||||||
|
id: params.input.id,
|
||||||
|
})
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const app = await App.findOneByKey(connection.key);
|
||||||
|
const $ = await globalVariable({ connection, app });
|
||||||
|
await app.auth.verifyCredentials($);
|
||||||
|
|
||||||
|
connection = await connection.$query().patchAndFetch({
|
||||||
|
verified: true,
|
||||||
|
draft: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...connection,
|
||||||
|
app,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default verifyConnection;
|
7
packages/backend/src/graphql/resolvers.js
Normal file
7
packages/backend/src/graphql/resolvers.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import mutationResolvers from './mutation-resolvers.js';
|
||||||
|
|
||||||
|
const resolvers = {
|
||||||
|
Mutation: mutationResolvers,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default resolvers;
|
382
packages/backend/src/graphql/schema.graphql
Normal file
382
packages/backend/src/graphql/schema.graphql
Normal file
@@ -0,0 +1,382 @@
|
|||||||
|
type Query {
|
||||||
|
placeholderQuery(name: String): Boolean
|
||||||
|
}
|
||||||
|
type Mutation {
|
||||||
|
createConnection(input: CreateConnectionInput): Connection
|
||||||
|
executeFlow(input: ExecuteFlowInput): executeFlowType
|
||||||
|
generateAuthUrl(input: GenerateAuthUrlInput): AuthLink
|
||||||
|
resetConnection(input: ResetConnectionInput): Connection
|
||||||
|
updateConnection(input: UpdateConnectionInput): Connection
|
||||||
|
updateCurrentUser(input: UpdateCurrentUserInput): User
|
||||||
|
verifyConnection(input: VerifyConnectionInput): Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Exposes a URL that specifies the behaviour of this scalar.
|
||||||
|
"""
|
||||||
|
directive @specifiedBy(
|
||||||
|
"""
|
||||||
|
The URL that specifies the behaviour of this scalar.
|
||||||
|
"""
|
||||||
|
url: String!
|
||||||
|
) on SCALAR
|
||||||
|
|
||||||
|
type Trigger {
|
||||||
|
name: String
|
||||||
|
key: String
|
||||||
|
description: String
|
||||||
|
showWebhookUrl: Boolean
|
||||||
|
pollInterval: Int
|
||||||
|
type: String
|
||||||
|
substeps: [Substep]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Action {
|
||||||
|
name: String
|
||||||
|
key: String
|
||||||
|
description: String
|
||||||
|
substeps: [Substep]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Substep {
|
||||||
|
key: String
|
||||||
|
name: String
|
||||||
|
arguments: [SubstepArgument]
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubstepArgument {
|
||||||
|
label: String
|
||||||
|
key: String
|
||||||
|
type: String
|
||||||
|
description: String
|
||||||
|
required: Boolean
|
||||||
|
variables: Boolean
|
||||||
|
options: [SubstepArgumentOption]
|
||||||
|
source: SubstepArgumentSource
|
||||||
|
additionalFields: SubstepArgumentAdditionalFields
|
||||||
|
dependsOn: [String]
|
||||||
|
fields: [SubstepArgument]
|
||||||
|
value: JSONObject
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubstepArgumentOption {
|
||||||
|
label: String
|
||||||
|
value: JSONObject
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubstepArgumentSource {
|
||||||
|
type: String
|
||||||
|
name: String
|
||||||
|
arguments: [SubstepArgumentSourceArgument]
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubstepArgumentSourceArgument {
|
||||||
|
name: String
|
||||||
|
value: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubstepArgumentAdditionalFields {
|
||||||
|
type: String
|
||||||
|
name: String
|
||||||
|
arguments: [SubstepArgumentAdditionalFieldsArgument]
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubstepArgumentAdditionalFieldsArgument {
|
||||||
|
name: String
|
||||||
|
value: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type App {
|
||||||
|
name: String
|
||||||
|
key: String
|
||||||
|
connectionCount: Int
|
||||||
|
flowCount: Int
|
||||||
|
iconUrl: String
|
||||||
|
docUrl: String
|
||||||
|
authDocUrl: String
|
||||||
|
primaryColor: String
|
||||||
|
supportsConnections: Boolean
|
||||||
|
auth: AppAuth
|
||||||
|
triggers: [Trigger]
|
||||||
|
actions: [Action]
|
||||||
|
connections: [Connection]
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppAuth {
|
||||||
|
fields: [Field]
|
||||||
|
authenticationSteps: [AuthenticationStep]
|
||||||
|
sharedAuthenticationSteps: [AuthenticationStep]
|
||||||
|
reconnectionSteps: [ReconnectionStep]
|
||||||
|
sharedReconnectionSteps: [ReconnectionStep]
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ArgumentEnumType {
|
||||||
|
integer
|
||||||
|
string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthenticationStep {
|
||||||
|
type: String
|
||||||
|
name: String
|
||||||
|
arguments: [AuthenticationStepArgument]
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthenticationStepArgument {
|
||||||
|
name: String
|
||||||
|
value: String
|
||||||
|
type: ArgumentEnumType
|
||||||
|
properties: [AuthenticationStepProperty]
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthenticationStepProperty {
|
||||||
|
name: String
|
||||||
|
value: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthLink {
|
||||||
|
url: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type Connection {
|
||||||
|
id: String
|
||||||
|
key: String
|
||||||
|
reconnectable: Boolean
|
||||||
|
appAuthClientId: String
|
||||||
|
formattedData: ConnectionData
|
||||||
|
verified: Boolean
|
||||||
|
app: App
|
||||||
|
createdAt: String
|
||||||
|
flowCount: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectionData {
|
||||||
|
screenName: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type executeFlowType {
|
||||||
|
data: JSONObject
|
||||||
|
step: Step
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExecutionStep {
|
||||||
|
id: String
|
||||||
|
executionId: String
|
||||||
|
stepId: String
|
||||||
|
step: Step
|
||||||
|
status: String
|
||||||
|
dataIn: JSONObject
|
||||||
|
dataOut: JSONObject
|
||||||
|
errorDetails: JSONObject
|
||||||
|
createdAt: String
|
||||||
|
updatedAt: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type Field {
|
||||||
|
key: String
|
||||||
|
label: String
|
||||||
|
type: String
|
||||||
|
required: Boolean
|
||||||
|
readOnly: Boolean
|
||||||
|
value: String
|
||||||
|
placeholder: String
|
||||||
|
description: String
|
||||||
|
docUrl: String
|
||||||
|
clickToCopy: Boolean
|
||||||
|
options: [SubstepArgumentOption]
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FlowStatus {
|
||||||
|
paused
|
||||||
|
published
|
||||||
|
draft
|
||||||
|
}
|
||||||
|
|
||||||
|
type Flow {
|
||||||
|
id: String
|
||||||
|
name: String
|
||||||
|
active: Boolean
|
||||||
|
steps: [Step]
|
||||||
|
createdAt: String
|
||||||
|
updatedAt: String
|
||||||
|
status: FlowStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
type SamlAuthProvidersRoleMapping {
|
||||||
|
id: String
|
||||||
|
samlAuthProviderId: String
|
||||||
|
roleId: String
|
||||||
|
remoteRoleName: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input CreateConnectionInput {
|
||||||
|
key: String!
|
||||||
|
appAuthClientId: String
|
||||||
|
formattedData: JSONObject
|
||||||
|
}
|
||||||
|
|
||||||
|
input GenerateAuthUrlInput {
|
||||||
|
id: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateConnectionInput {
|
||||||
|
id: String!
|
||||||
|
formattedData: JSONObject
|
||||||
|
appAuthClientId: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input ResetConnectionInput {
|
||||||
|
id: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input VerifyConnectionInput {
|
||||||
|
id: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input ExecuteFlowInput {
|
||||||
|
stepId: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input UserRoleInput {
|
||||||
|
id: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateCurrentUserInput {
|
||||||
|
email: String
|
||||||
|
password: String
|
||||||
|
fullName: String
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
|
||||||
|
"""
|
||||||
|
scalar JSONObject
|
||||||
|
|
||||||
|
input PreviousStepInput {
|
||||||
|
id: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReconnectionStep {
|
||||||
|
type: String
|
||||||
|
name: String
|
||||||
|
arguments: [ReconnectionStepArgument]
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReconnectionStepArgument {
|
||||||
|
name: String
|
||||||
|
value: String
|
||||||
|
type: ArgumentEnumType
|
||||||
|
properties: [ReconnectionStepProperty]
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReconnectionStepProperty {
|
||||||
|
name: String
|
||||||
|
value: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type Step {
|
||||||
|
id: String
|
||||||
|
previousStepId: String
|
||||||
|
key: String
|
||||||
|
appKey: String
|
||||||
|
iconUrl: String
|
||||||
|
webhookUrl: String
|
||||||
|
type: StepEnumType
|
||||||
|
parameters: JSONObject
|
||||||
|
connection: Connection
|
||||||
|
flow: Flow
|
||||||
|
position: Int
|
||||||
|
status: String
|
||||||
|
executionSteps: [ExecutionStep]
|
||||||
|
}
|
||||||
|
|
||||||
|
input StepConnectionInput {
|
||||||
|
id: String
|
||||||
|
}
|
||||||
|
|
||||||
|
enum StepEnumType {
|
||||||
|
trigger
|
||||||
|
action
|
||||||
|
}
|
||||||
|
|
||||||
|
input StepFlowInput {
|
||||||
|
id: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input StepInput {
|
||||||
|
id: String
|
||||||
|
previousStepId: String
|
||||||
|
key: String
|
||||||
|
appKey: String
|
||||||
|
connection: StepConnectionInput
|
||||||
|
flow: StepFlowInput
|
||||||
|
parameters: JSONObject
|
||||||
|
previousStep: PreviousStepInput
|
||||||
|
}
|
||||||
|
|
||||||
|
type User {
|
||||||
|
id: String
|
||||||
|
fullName: String
|
||||||
|
email: String
|
||||||
|
role: Role
|
||||||
|
permissions: [Permission]
|
||||||
|
createdAt: String
|
||||||
|
updatedAt: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type Role {
|
||||||
|
id: String
|
||||||
|
name: String
|
||||||
|
key: String
|
||||||
|
description: String
|
||||||
|
isAdmin: Boolean
|
||||||
|
permissions: [Permission]
|
||||||
|
}
|
||||||
|
|
||||||
|
type PageInfo {
|
||||||
|
currentPage: Int!
|
||||||
|
totalPages: Int!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExecutionStepEdge {
|
||||||
|
node: ExecutionStep
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExecutionStepConnection {
|
||||||
|
edges: [ExecutionStepEdge]
|
||||||
|
pageInfo: PageInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type License {
|
||||||
|
id: String
|
||||||
|
name: String
|
||||||
|
expireAt: String
|
||||||
|
verified: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type Permission {
|
||||||
|
id: String
|
||||||
|
action: String
|
||||||
|
subject: String
|
||||||
|
conditions: [String]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Action {
|
||||||
|
label: String
|
||||||
|
key: String
|
||||||
|
subjects: [String]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Condition {
|
||||||
|
key: String
|
||||||
|
label: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type Subject {
|
||||||
|
label: String
|
||||||
|
key: String
|
||||||
|
}
|
||||||
|
|
||||||
|
schema {
|
||||||
|
query: Query
|
||||||
|
mutation: Mutation
|
||||||
|
}
|
@@ -27,7 +27,12 @@ const authenticationStepsWithoutAuthUrl = [
|
|||||||
{
|
{
|
||||||
type: 'mutation',
|
type: 'mutation',
|
||||||
name: 'verifyConnection',
|
name: 'verifyConnection',
|
||||||
arguments: [],
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
value: '{createConnection.id}',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -49,7 +54,12 @@ const authenticationStepsWithAuthUrl = [
|
|||||||
{
|
{
|
||||||
type: 'mutation',
|
type: 'mutation',
|
||||||
name: 'generateAuthUrl',
|
name: 'generateAuthUrl',
|
||||||
arguments: [],
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
value: '{createConnection.id}',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'openWithPopup',
|
type: 'openWithPopup',
|
||||||
@@ -65,6 +75,10 @@ const authenticationStepsWithAuthUrl = [
|
|||||||
type: 'mutation',
|
type: 'mutation',
|
||||||
name: 'updateConnection',
|
name: 'updateConnection',
|
||||||
arguments: [
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
value: '{createConnection.id}',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'formattedData',
|
name: 'formattedData',
|
||||||
value: '{openAuthPopup.all}',
|
value: '{openAuthPopup.all}',
|
||||||
@@ -74,7 +88,12 @@ const authenticationStepsWithAuthUrl = [
|
|||||||
{
|
{
|
||||||
type: 'mutation',
|
type: 'mutation',
|
||||||
name: 'verifyConnection',
|
name: 'verifyConnection',
|
||||||
arguments: [],
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
value: '{createConnection.id}',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -96,7 +115,12 @@ const sharedAuthenticationStepsWithAuthUrl = [
|
|||||||
{
|
{
|
||||||
type: 'mutation',
|
type: 'mutation',
|
||||||
name: 'generateAuthUrl',
|
name: 'generateAuthUrl',
|
||||||
arguments: [],
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
value: '{createConnection.id}',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'openWithPopup',
|
type: 'openWithPopup',
|
||||||
@@ -112,6 +136,10 @@ const sharedAuthenticationStepsWithAuthUrl = [
|
|||||||
type: 'mutation',
|
type: 'mutation',
|
||||||
name: 'updateConnection',
|
name: 'updateConnection',
|
||||||
arguments: [
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
value: '{createConnection.id}',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'formattedData',
|
name: 'formattedData',
|
||||||
value: '{openAuthPopup.all}',
|
value: '{openAuthPopup.all}',
|
||||||
@@ -121,7 +149,12 @@ const sharedAuthenticationStepsWithAuthUrl = [
|
|||||||
{
|
{
|
||||||
type: 'mutation',
|
type: 'mutation',
|
||||||
name: 'verifyConnection',
|
name: 'verifyConnection',
|
||||||
arguments: [],
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
value: '{createConnection.id}',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -1,23 +1,54 @@
|
|||||||
import cloneDeep from 'lodash/cloneDeep.js';
|
import cloneDeep from 'lodash/cloneDeep.js';
|
||||||
|
|
||||||
|
const connectionIdArgument = {
|
||||||
|
name: 'id',
|
||||||
|
value: '{connection.id}',
|
||||||
|
};
|
||||||
|
|
||||||
const resetConnectionStep = {
|
const resetConnectionStep = {
|
||||||
type: 'mutation',
|
type: 'mutation',
|
||||||
name: 'resetConnection',
|
name: 'resetConnection',
|
||||||
arguments: [],
|
arguments: [connectionIdArgument],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function replaceCreateConnection(string) {
|
||||||
|
return string.replace('{createConnection.id}', '{connection.id}');
|
||||||
|
}
|
||||||
|
|
||||||
function removeAppKeyArgument(args) {
|
function removeAppKeyArgument(args) {
|
||||||
return args.filter((argument) => argument.name !== 'key');
|
return args.filter((argument) => argument.name !== 'key');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addConnectionId(step) {
|
||||||
|
step.arguments = step.arguments.map((argument) => {
|
||||||
|
if (typeof argument.value === 'string') {
|
||||||
|
argument.value = replaceCreateConnection(argument.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argument.properties) {
|
||||||
|
argument.properties = argument.properties.map((property) => {
|
||||||
|
return {
|
||||||
|
name: property.name,
|
||||||
|
value: replaceCreateConnection(property.value),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return argument;
|
||||||
|
});
|
||||||
|
|
||||||
|
return step;
|
||||||
|
}
|
||||||
|
|
||||||
function replaceCreateConnectionsWithUpdate(steps) {
|
function replaceCreateConnectionsWithUpdate(steps) {
|
||||||
const updatedSteps = cloneDeep(steps);
|
const updatedSteps = cloneDeep(steps);
|
||||||
return updatedSteps.map((step) => {
|
return updatedSteps.map((step) => {
|
||||||
const updatedStep = { ...step };
|
const updatedStep = addConnectionId(step);
|
||||||
|
|
||||||
if (step.name === 'createConnection') {
|
if (step.name === 'createConnection') {
|
||||||
updatedStep.name = 'updateConnection';
|
updatedStep.name = 'updateConnection';
|
||||||
updatedStep.arguments = removeAppKeyArgument(updatedStep.arguments);
|
updatedStep.arguments = removeAppKeyArgument(updatedStep.arguments);
|
||||||
|
updatedStep.arguments.unshift(connectionIdArgument);
|
||||||
|
|
||||||
return updatedStep;
|
return updatedStep;
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
|
import { rule, shield } from 'graphql-shield';
|
||||||
import User from '../models/user.js';
|
import User from '../models/user.js';
|
||||||
import AccessToken from '../models/access-token.js';
|
import AccessToken from '../models/access-token.js';
|
||||||
|
|
||||||
export const isAuthenticated = async (req) => {
|
export const isAuthenticated = async (_parent, _args, req) => {
|
||||||
const token = req.headers['authorization'];
|
const token = req.headers['authorization'];
|
||||||
|
|
||||||
if (token == null) return false;
|
if (token == null) return false;
|
||||||
@@ -40,9 +41,25 @@ export const isAuthenticated = async (req) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const authenticateUser = async (request, response, next) => {
|
export const authenticateUser = async (request, response, next) => {
|
||||||
if (await isAuthenticated(request)) {
|
if (await isAuthenticated(null, null, request)) {
|
||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
return response.status(401).end();
|
return response.status(401).end();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isAuthenticatedRule = rule()(isAuthenticated);
|
||||||
|
|
||||||
|
export const authenticationRules = {
|
||||||
|
Mutation: {
|
||||||
|
'*': isAuthenticatedRule,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const authenticationOptions = {
|
||||||
|
allowExternalErrors: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const authentication = shield(authenticationRules, authenticationOptions);
|
||||||
|
|
||||||
|
export default authentication;
|
||||||
|
@@ -1,17 +1,18 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { isAuthenticated } from './authentication.js';
|
import { allow } from 'graphql-shield';
|
||||||
|
import { isAuthenticated, authenticationRules } from './authentication.js';
|
||||||
import { createUser } from '../../test/factories/user.js';
|
import { createUser } from '../../test/factories/user.js';
|
||||||
import createAuthTokenByUserId from '../helpers/create-auth-token-by-user-id.js';
|
import createAuthTokenByUserId from '../helpers/create-auth-token-by-user-id.js';
|
||||||
|
|
||||||
describe('isAuthenticated', () => {
|
describe('isAuthenticated', () => {
|
||||||
it('should return false if no token is provided', async () => {
|
it('should return false if no token is provided', async () => {
|
||||||
const req = { headers: {} };
|
const req = { headers: {} };
|
||||||
expect(await isAuthenticated(req)).toBe(false);
|
expect(await isAuthenticated(null, null, req)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false if token is invalid', async () => {
|
it('should return false if token is invalid', async () => {
|
||||||
const req = { headers: { authorization: 'invalidToken' } };
|
const req = { headers: { authorization: 'invalidToken' } };
|
||||||
expect(await isAuthenticated(req)).toBe(false);
|
expect(await isAuthenticated(null, null, req)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true if token is valid and there is a user', async () => {
|
it('should return true if token is valid and there is a user', async () => {
|
||||||
@@ -19,7 +20,7 @@ describe('isAuthenticated', () => {
|
|||||||
const token = await createAuthTokenByUserId(user.id);
|
const token = await createAuthTokenByUserId(user.id);
|
||||||
|
|
||||||
const req = { headers: { authorization: token } };
|
const req = { headers: { authorization: token } };
|
||||||
expect(await isAuthenticated(req)).toBe(true);
|
expect(await isAuthenticated(null, null, req)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false if token is valid and but there is no user', async () => {
|
it('should return false if token is valid and but there is no user', async () => {
|
||||||
@@ -28,6 +29,46 @@ describe('isAuthenticated', () => {
|
|||||||
await user.$query().delete();
|
await user.$query().delete();
|
||||||
|
|
||||||
const req = { headers: { authorization: token } };
|
const req = { headers: { authorization: token } };
|
||||||
expect(await isAuthenticated(req)).toBe(false);
|
expect(await isAuthenticated(null, null, req)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('authentication rules', () => {
|
||||||
|
const getQueryAndMutationNames = (rules) => {
|
||||||
|
const queries = Object.keys(rules.Query || {});
|
||||||
|
const mutations = Object.keys(rules.Mutation || {});
|
||||||
|
return { queries, mutations };
|
||||||
|
};
|
||||||
|
|
||||||
|
const { queries, mutations } = getQueryAndMutationNames(authenticationRules);
|
||||||
|
|
||||||
|
if (queries.length) {
|
||||||
|
describe('for queries', () => {
|
||||||
|
queries.forEach((query) => {
|
||||||
|
it(`should apply correct rule for query: ${query}`, () => {
|
||||||
|
const ruleApplied = authenticationRules.Query[query];
|
||||||
|
|
||||||
|
if (query === '*') {
|
||||||
|
expect(ruleApplied.func).toBe(isAuthenticated);
|
||||||
|
} else {
|
||||||
|
expect(ruleApplied).toEqual(allow);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('for mutations', () => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
it(`should apply correct rule for mutation: ${mutation}`, () => {
|
||||||
|
const ruleApplied = authenticationRules.Mutation[mutation];
|
||||||
|
|
||||||
|
if (mutation === '*') {
|
||||||
|
expect(ruleApplied.func).toBe(isAuthenticated);
|
||||||
|
} else {
|
||||||
|
expect(ruleApplied).toBe(allow);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -50,7 +50,7 @@ const errorHandler = (error, request, response, next) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
response.status(422).json(httpErrorPayload);
|
response.status(200).json(httpErrorPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error instanceof NotAuthorizedError) {
|
if (error instanceof NotAuthorizedError) {
|
||||||
|
53
packages/backend/src/helpers/graphql-instance.js
Normal file
53
packages/backend/src/helpers/graphql-instance.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import path, { join } from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { graphqlHTTP } from 'express-graphql';
|
||||||
|
import { loadSchemaSync } from '@graphql-tools/load';
|
||||||
|
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
|
||||||
|
import { addResolversToSchema } from '@graphql-tools/schema';
|
||||||
|
import { applyMiddleware } from 'graphql-middleware';
|
||||||
|
|
||||||
|
import appConfig from '../config/app.js';
|
||||||
|
import logger from './logger.js';
|
||||||
|
import authentication from './authentication.js';
|
||||||
|
import * as Sentry from './sentry.ee.js';
|
||||||
|
import resolvers from '../graphql/resolvers.js';
|
||||||
|
import HttpError from '../errors/http.js';
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
const schema = loadSchemaSync(join(__dirname, '../graphql/schema.graphql'), {
|
||||||
|
loaders: [new GraphQLFileLoader()],
|
||||||
|
});
|
||||||
|
|
||||||
|
const schemaWithResolvers = addResolversToSchema({
|
||||||
|
schema,
|
||||||
|
resolvers,
|
||||||
|
});
|
||||||
|
|
||||||
|
const graphQLInstance = graphqlHTTP({
|
||||||
|
schema: applyMiddleware(
|
||||||
|
schemaWithResolvers,
|
||||||
|
authentication.generate(schemaWithResolvers)
|
||||||
|
),
|
||||||
|
graphiql: appConfig.isDev,
|
||||||
|
customFormatErrorFn: (error) => {
|
||||||
|
logger.error(error.path + ' : ' + error.message + '\n' + error.stack);
|
||||||
|
|
||||||
|
if (error.originalError instanceof HttpError) {
|
||||||
|
delete error.originalError.response;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sentry.captureException(error, {
|
||||||
|
tags: { graphql: true },
|
||||||
|
extra: {
|
||||||
|
source: error.source?.body,
|
||||||
|
positions: error.positions,
|
||||||
|
path: error.path,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return error;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default graphQLInstance;
|
@@ -6,8 +6,18 @@ const stream = {
|
|||||||
logger.http(message.substring(0, message.lastIndexOf('\n'))),
|
logger.http(message.substring(0, message.lastIndexOf('\n'))),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const registerGraphQLToken = () => {
|
||||||
|
morgan.token('graphql-query', (req) => {
|
||||||
|
if (req.body.query) {
|
||||||
|
return `\n GraphQL ${req.body.query}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
registerGraphQLToken();
|
||||||
|
|
||||||
const morganMiddleware = morgan(
|
const morganMiddleware = morgan(
|
||||||
':method :url :status :res[content-length] - :response-time ms',
|
':method :url :status :res[content-length] - :response-time ms :graphql-query',
|
||||||
{ stream }
|
{ stream }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -17,6 +17,7 @@ export function init(app) {
|
|||||||
integrations: [
|
integrations: [
|
||||||
app && new Sentry.Integrations.Http({ tracing: true }),
|
app && new Sentry.Integrations.Http({ tracing: true }),
|
||||||
app && new Tracing.Integrations.Express({ app }),
|
app && new Tracing.Integrations.Express({ app }),
|
||||||
|
app && new Tracing.Integrations.GraphQL(),
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
tracesSampleRate: 1.0,
|
tracesSampleRate: 1.0,
|
||||||
});
|
});
|
||||||
|
@@ -1,41 +0,0 @@
|
|||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
||||||
|
|
||||||
exports[`AccessToken model > jsonSchema should have correct validations 1`] = `
|
|
||||||
{
|
|
||||||
"properties": {
|
|
||||||
"expiresIn": {
|
|
||||||
"type": "integer",
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"format": "uuid",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"revokedAt": {
|
|
||||||
"format": "date-time",
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"samlSessionId": {
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"token": {
|
|
||||||
"minLength": 32,
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"userId": {
|
|
||||||
"format": "uuid",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"token",
|
|
||||||
"expiresIn",
|
|
||||||
],
|
|
||||||
"type": "object",
|
|
||||||
}
|
|
||||||
`;
|
|
@@ -1,39 +0,0 @@
|
|||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
||||||
|
|
||||||
exports[`AppAuthClient model > jsonSchema should have correct validations 1`] = `
|
|
||||||
{
|
|
||||||
"properties": {
|
|
||||||
"active": {
|
|
||||||
"type": "boolean",
|
|
||||||
},
|
|
||||||
"appKey": {
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"authDefaults": {
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"createdAt": {
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"formattedAuthDefaults": {
|
|
||||||
"type": "object",
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"format": "uuid",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"updatedAt": {
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"name",
|
|
||||||
"appKey",
|
|
||||||
"formattedAuthDefaults",
|
|
||||||
],
|
|
||||||
"type": "object",
|
|
||||||
}
|
|
||||||
`;
|
|
@@ -1,73 +0,0 @@
|
|||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
||||||
|
|
||||||
exports[`App model > list should have list of applications keys 1`] = `
|
|
||||||
[
|
|
||||||
"airtable",
|
|
||||||
"appwrite",
|
|
||||||
"azure-openai",
|
|
||||||
"carbone",
|
|
||||||
"clickup",
|
|
||||||
"code",
|
|
||||||
"cryptography",
|
|
||||||
"datastore",
|
|
||||||
"deepl",
|
|
||||||
"delay",
|
|
||||||
"discord",
|
|
||||||
"disqus",
|
|
||||||
"dropbox",
|
|
||||||
"filter",
|
|
||||||
"flickr",
|
|
||||||
"flowers-software",
|
|
||||||
"formatter",
|
|
||||||
"ghost",
|
|
||||||
"github",
|
|
||||||
"gitlab",
|
|
||||||
"google-calendar",
|
|
||||||
"google-drive",
|
|
||||||
"google-forms",
|
|
||||||
"google-sheets",
|
|
||||||
"google-tasks",
|
|
||||||
"helix",
|
|
||||||
"http-request",
|
|
||||||
"hubspot",
|
|
||||||
"invoice-ninja",
|
|
||||||
"jotform",
|
|
||||||
"mailchimp",
|
|
||||||
"mailerlite",
|
|
||||||
"mattermost",
|
|
||||||
"miro",
|
|
||||||
"notion",
|
|
||||||
"ntfy",
|
|
||||||
"odoo",
|
|
||||||
"openai",
|
|
||||||
"pipedrive",
|
|
||||||
"placetel",
|
|
||||||
"postgresql",
|
|
||||||
"pushover",
|
|
||||||
"reddit",
|
|
||||||
"removebg",
|
|
||||||
"rss",
|
|
||||||
"salesforce",
|
|
||||||
"scheduler",
|
|
||||||
"self-hosted-llm",
|
|
||||||
"signalwire",
|
|
||||||
"slack",
|
|
||||||
"smtp",
|
|
||||||
"spotify",
|
|
||||||
"strava",
|
|
||||||
"stripe",
|
|
||||||
"telegram-bot",
|
|
||||||
"todoist",
|
|
||||||
"trello",
|
|
||||||
"twilio",
|
|
||||||
"twitter",
|
|
||||||
"typeform",
|
|
||||||
"vtiger-crm",
|
|
||||||
"webhook",
|
|
||||||
"wordpress",
|
|
||||||
"xero",
|
|
||||||
"you-need-a-budget",
|
|
||||||
"youtube",
|
|
||||||
"zendesk",
|
|
||||||
]
|
|
||||||
`;
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user