Compare commits
1 Commits
AUT-1324
...
dependabot
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1f93f3b262 |
4
.github/workflows/playwright.yml
vendored
4
.github/workflows/playwright.yml
vendored
@@ -12,9 +12,6 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
BULLMQ_DASHBOARD_USERNAME: root
|
||||
BULLMQ_DASHBOARD_PASSWORD: sample
|
||||
ENABLE_BULLMQ_DASHBOARD: true
|
||||
ENCRYPTION_KEY: sample_encryption_key
|
||||
WEBHOOK_SECRET_KEY: sample_webhook_secret_key
|
||||
APP_SECRET_KEY: sample_app_secret_key
|
||||
@@ -25,7 +22,6 @@ env:
|
||||
POSTGRES_PASSWORD: automatisch_password
|
||||
REDIS_HOST: localhost
|
||||
APP_ENV: production
|
||||
PORT: 3000
|
||||
LICENSE_KEY: dummy_license_key
|
||||
|
||||
jobs:
|
||||
|
@@ -36,7 +36,7 @@
|
||||
"crypto-js": "^4.1.1",
|
||||
"debug": "~2.6.9",
|
||||
"dotenv": "^10.0.0",
|
||||
"express": "~4.18.2",
|
||||
"express": "~4.20.0",
|
||||
"express-async-errors": "^3.1.1",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"fast-xml-parser": "^4.0.11",
|
||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
||||
apiBaseUrl: 'https://api.airtable.com',
|
||||
iconUrl: '{BASE_URL}/apps/airtable/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/airtable/connection',
|
||||
primaryColor: '#FFBF00',
|
||||
primaryColor: 'FFBF00',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
||||
apiBaseUrl: 'https://cloud.appwrite.io',
|
||||
iconUrl: '{BASE_URL}/apps/appwrite/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/appwrite/connection',
|
||||
primaryColor: '#FD366E',
|
||||
primaryColor: 'FD366E',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||
auth,
|
||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
||||
apiBaseUrl: '',
|
||||
iconUrl: '{BASE_URL}/apps/azure-openai/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/azure-openai/connection',
|
||||
primaryColor: '#000000',
|
||||
primaryColor: '000000',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||
auth,
|
||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://carbone.io',
|
||||
apiBaseUrl: 'https://api.carbone.io',
|
||||
primaryColor: '#6f42c1',
|
||||
primaryColor: '6f42c1',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
actions,
|
||||
|
@@ -12,8 +12,8 @@ export default defineApp({
|
||||
baseUrl: 'https://clickup.com',
|
||||
apiBaseUrl: 'https://api.clickup.com/api',
|
||||
iconUrl: '{BASE_URL}/apps/clickup/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/clickup/connection',
|
||||
primaryColor: '#FD71AF',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/clickup/connection',
|
||||
primaryColor: 'FD71AF',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
|
@@ -8,7 +8,7 @@ export default defineApp({
|
||||
apiBaseUrl: '',
|
||||
iconUrl: '{BASE_URL}/apps/code/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/code/connection',
|
||||
primaryColor: '#000000',
|
||||
primaryColor: '000000',
|
||||
supportsConnections: false,
|
||||
actions,
|
||||
});
|
||||
|
@@ -9,6 +9,6 @@ export default defineApp({
|
||||
supportsConnections: false,
|
||||
baseUrl: '',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '#001F52',
|
||||
primaryColor: '001F52',
|
||||
actions,
|
||||
});
|
||||
|
@@ -9,6 +9,6 @@ export default defineApp({
|
||||
supportsConnections: false,
|
||||
baseUrl: '',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '#001F52',
|
||||
primaryColor: '001F52',
|
||||
actions,
|
||||
});
|
||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://deepl.com',
|
||||
apiBaseUrl: 'https://api.deepl.com',
|
||||
primaryColor: '#0d2d45',
|
||||
primaryColor: '0d2d45',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
actions,
|
||||
|
@@ -9,6 +9,6 @@ export default defineApp({
|
||||
supportsConnections: false,
|
||||
baseUrl: '',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '#001F52',
|
||||
primaryColor: '001F52',
|
||||
actions,
|
||||
});
|
||||
|
@@ -14,7 +14,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://discord.com',
|
||||
apiBaseUrl: 'https://discord.com/api',
|
||||
primaryColor: '#5865f2',
|
||||
primaryColor: '5865f2',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
dynamicData,
|
||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
||||
apiBaseUrl: 'https://disqus.com/api',
|
||||
iconUrl: '{BASE_URL}/apps/disqus/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/disqus/connection',
|
||||
primaryColor: '#2E9FFF',
|
||||
primaryColor: '2E9FFF',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://dropbox.com',
|
||||
apiBaseUrl: 'https://api.dropboxapi.com',
|
||||
primaryColor: '#0061ff',
|
||||
primaryColor: '0061ff',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
actions,
|
||||
|
@@ -9,6 +9,6 @@ export default defineApp({
|
||||
supportsConnections: false,
|
||||
baseUrl: '',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '#001F52',
|
||||
primaryColor: '001F52',
|
||||
actions,
|
||||
});
|
||||
|
@@ -10,7 +10,7 @@ export default defineApp({
|
||||
iconUrl: '{BASE_URL}/apps/flickr/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/flickr/connection',
|
||||
docUrl: 'https://automatisch.io/docs/flickr',
|
||||
primaryColor: '#000000',
|
||||
primaryColor: '000000',
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://www.flickr.com/',
|
||||
apiBaseUrl: 'https://www.flickr.com/services',
|
||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://flowers-software.com',
|
||||
apiBaseUrl: 'https://webapp.flowers-software.com/api',
|
||||
primaryColor: '#02AFC7',
|
||||
primaryColor: '02AFC7',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
|
@@ -10,7 +10,7 @@ export default defineApp({
|
||||
supportsConnections: false,
|
||||
baseUrl: '',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '#001F52',
|
||||
primaryColor: '001F52',
|
||||
actions,
|
||||
dynamicFields,
|
||||
});
|
||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
||||
apiBaseUrl: '',
|
||||
iconUrl: '{BASE_URL}/apps/ghost/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/ghost/connection',
|
||||
primaryColor: '#15171A',
|
||||
primaryColor: '15171A',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||
auth,
|
||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
||||
apiBaseUrl: 'https://api.github.com',
|
||||
iconUrl: '{BASE_URL}/apps/github/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/github/connection',
|
||||
primaryColor: '#000000',
|
||||
primaryColor: '000000',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
||||
apiBaseUrl: 'https://gitlab.com',
|
||||
iconUrl: '{BASE_URL}/apps/gitlab/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/gitlab/connection',
|
||||
primaryColor: '#FC6D26',
|
||||
primaryColor: 'FC6D26',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||
auth,
|
||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
||||
apiBaseUrl: 'https://www.googleapis.com/calendar',
|
||||
iconUrl: '{BASE_URL}/apps/google-calendar/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/google-calendar/connection',
|
||||
primaryColor: '#448AFF',
|
||||
primaryColor: '448AFF',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
||||
apiBaseUrl: 'https://www.googleapis.com/drive',
|
||||
iconUrl: '{BASE_URL}/apps/google-drive/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/google-drive/connection',
|
||||
primaryColor: '#1FA463',
|
||||
primaryColor: '1FA463',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
||||
apiBaseUrl: 'https://forms.googleapis.com',
|
||||
iconUrl: '{BASE_URL}/apps/google-forms/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/google-forms/connection',
|
||||
primaryColor: '#673AB7',
|
||||
primaryColor: '673AB7',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
||||
apiBaseUrl: 'https://sheets.googleapis.com',
|
||||
iconUrl: '{BASE_URL}/apps/google-sheets/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/google-sheets/connection',
|
||||
primaryColor: '#0F9D58',
|
||||
primaryColor: '0F9D58',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
||||
apiBaseUrl: 'https://tasks.googleapis.com',
|
||||
iconUrl: '{BASE_URL}/apps/google-tasks/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/google-tasks/connection',
|
||||
primaryColor: '#0066DA',
|
||||
primaryColor: '0066DA',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
||||
apiBaseUrl: 'https://app.tryhelix.ai',
|
||||
iconUrl: '{BASE_URL}/apps/helix/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/helix/connection',
|
||||
primaryColor: '#000000',
|
||||
primaryColor: '000000',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||
auth,
|
||||
|
@@ -9,6 +9,6 @@ export default defineApp({
|
||||
supportsConnections: false,
|
||||
baseUrl: '',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '#000000',
|
||||
primaryColor: '000000',
|
||||
actions,
|
||||
});
|
||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://www.hubspot.com',
|
||||
apiBaseUrl: 'https://api.hubapi.com',
|
||||
primaryColor: '#F95C35',
|
||||
primaryColor: 'F95C35',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
actions,
|
||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
||||
apiBaseUrl: 'https://invoicing.co/api',
|
||||
iconUrl: '{BASE_URL}/apps/invoice-ninja/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/invoice-ninja/connection',
|
||||
primaryColor: '#000000',
|
||||
primaryColor: '000000',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||
auth,
|
||||
|
@@ -9,11 +9,11 @@ export default defineApp({
|
||||
name: 'Jotform',
|
||||
key: 'jotform',
|
||||
iconUrl: '{BASE_URL}/apps/jotform/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/jotform/connection',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/jotform/connection',
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://www.jotform.com',
|
||||
apiBaseUrl: 'https://api.jotform.com',
|
||||
primaryColor: '#FF6100',
|
||||
primaryColor: 'FF6100',
|
||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
|
@@ -12,8 +12,8 @@ export default defineApp({
|
||||
baseUrl: 'https://mailchimp.com',
|
||||
apiBaseUrl: '',
|
||||
iconUrl: '{BASE_URL}/apps/mailchimp/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/mailchimp/connection',
|
||||
primaryColor: '#000000',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/mailchimp/connection',
|
||||
primaryColor: '000000',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||
auth,
|
||||
|
@@ -7,11 +7,11 @@ export default defineApp({
|
||||
name: 'MailerLite',
|
||||
key: 'mailerlite',
|
||||
iconUrl: '{BASE_URL}/apps/mailerlite/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/mailerlite/connection',
|
||||
authDocUrl: 'https://automatisch.io/docs/apps/mailerlite/connection',
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://www.mailerlite.com',
|
||||
apiBaseUrl: 'https://connect.mailerlite.com/api',
|
||||
primaryColor: '#09C269',
|
||||
primaryColor: '09C269',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
||||
authDocUrl: '{DOCS_URL}/apps/mattermost/connection',
|
||||
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
|
||||
primaryColor: '#4a154b',
|
||||
primaryColor: '4a154b',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [setBaseUrl, addXRequestedWithHeader, addAuthHeader],
|
||||
auth,
|
||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
||||
apiBaseUrl: 'https://api.miro.com',
|
||||
iconUrl: '{BASE_URL}/apps/miro/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/miro/connection',
|
||||
primaryColor: '#F2CA02',
|
||||
primaryColor: 'F2CA02',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
||||
apiBaseUrl: 'https://api.notion.com',
|
||||
iconUrl: '{BASE_URL}/apps/notion/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/notion/connection',
|
||||
primaryColor: '#000000',
|
||||
primaryColor: '000000',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader, addNotionVersionHeader],
|
||||
auth,
|
||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://ntfy.sh',
|
||||
apiBaseUrl: 'https://ntfy.sh',
|
||||
primaryColor: '#56bda8',
|
||||
primaryColor: '56bda8',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
actions,
|
||||
|
@@ -10,7 +10,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://odoo.com',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '#9c5789',
|
||||
primaryColor: '9c5789',
|
||||
auth,
|
||||
actions,
|
||||
});
|
||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
||||
apiBaseUrl: 'https://api.openai.com',
|
||||
iconUrl: '{BASE_URL}/apps/openai/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/openai/connection',
|
||||
primaryColor: '#000000',
|
||||
primaryColor: '000000',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
||||
apiBaseUrl: '',
|
||||
iconUrl: '{BASE_URL}/apps/pipedrive/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/pipedrive/connection',
|
||||
primaryColor: '#FFFFFF',
|
||||
primaryColor: 'FFFFFF',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||
auth,
|
||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://placetel.de',
|
||||
apiBaseUrl: 'https://api.placetel.de',
|
||||
primaryColor: '#069dd9',
|
||||
primaryColor: '069dd9',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
|
@@ -10,7 +10,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: '',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '#336791',
|
||||
primaryColor: '336791',
|
||||
auth,
|
||||
actions,
|
||||
});
|
||||
|
@@ -10,7 +10,7 @@ export default defineApp({
|
||||
apiBaseUrl: 'https://api.pushover.net',
|
||||
iconUrl: '{BASE_URL}/apps/pushover/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/pushover/connection',
|
||||
primaryColor: '#249DF1',
|
||||
primaryColor: '249DF1',
|
||||
supportsConnections: true,
|
||||
auth,
|
||||
actions,
|
||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
||||
apiBaseUrl: 'https://oauth.reddit.com',
|
||||
iconUrl: '{BASE_URL}/apps/reddit/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/reddit/connection',
|
||||
primaryColor: '#FF4500',
|
||||
primaryColor: 'FF4500',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://www.remove.bg',
|
||||
apiBaseUrl: 'https://api.remove.bg/v1.0',
|
||||
primaryColor: '#55636c',
|
||||
primaryColor: '55636c',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
actions,
|
||||
|
@@ -9,6 +9,6 @@ export default defineApp({
|
||||
supportsConnections: false,
|
||||
baseUrl: '',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '#ff8800',
|
||||
primaryColor: 'ff8800',
|
||||
triggers,
|
||||
});
|
||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://salesforce.com',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '#00A1E0',
|
||||
primaryColor: '00A1E0',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
|
@@ -9,7 +9,7 @@ export default defineApp({
|
||||
authDocUrl: '{DOCS_URL}/apps/scheduler/connection',
|
||||
baseUrl: '',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '#0059F7',
|
||||
primaryColor: '0059F7',
|
||||
supportsConnections: false,
|
||||
triggers,
|
||||
});
|
||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
||||
apiBaseUrl: '',
|
||||
iconUrl: '{BASE_URL}/apps/self-hosted-llm/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/self-hosted-llm/connection',
|
||||
primaryColor: '#000000',
|
||||
primaryColor: '000000',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||
auth,
|
||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://signalwire.com',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '#044cf6',
|
||||
primaryColor: '044cf6',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://slack.com',
|
||||
apiBaseUrl: 'https://slack.com/api',
|
||||
primaryColor: '#4a154b',
|
||||
primaryColor: '4a154b',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
actions,
|
||||
|
@@ -10,7 +10,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: '',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '#2DAAE1',
|
||||
primaryColor: '2DAAE1',
|
||||
auth,
|
||||
actions,
|
||||
});
|
||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://spotify.com',
|
||||
apiBaseUrl: 'https://api.spotify.com',
|
||||
primaryColor: '#000000',
|
||||
primaryColor: '000000',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
actions,
|
||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://www.strava.com',
|
||||
apiBaseUrl: 'https://www.strava.com/api',
|
||||
primaryColor: '#fc4c01',
|
||||
primaryColor: 'fc4c01',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
actions,
|
||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://stripe.com',
|
||||
apiBaseUrl: 'https://api.stripe.com',
|
||||
primaryColor: '#635bff',
|
||||
primaryColor: '635bff',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://telegram.org',
|
||||
apiBaseUrl: 'https://api.telegram.org',
|
||||
primaryColor: '#2AABEE',
|
||||
primaryColor: '2AABEE',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
actions,
|
||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://todoist.com',
|
||||
apiBaseUrl: 'https://api.todoist.com/rest/v2',
|
||||
primaryColor: '#e44332',
|
||||
primaryColor: 'e44332',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
||||
iconUrl: '{BASE_URL}/apps/trello/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/trello/connection',
|
||||
supportsConnections: true,
|
||||
primaryColor: '#0079bf',
|
||||
primaryColor: '0079bf',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
actions,
|
||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://twilio.com',
|
||||
apiBaseUrl: 'https://api.twilio.com',
|
||||
primaryColor: '#e1000f',
|
||||
primaryColor: 'e1000f',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://twitter.com',
|
||||
apiBaseUrl: 'https://api.twitter.com',
|
||||
primaryColor: '#1da1f2',
|
||||
primaryColor: '1da1f2',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://typeform.com',
|
||||
apiBaseUrl: 'https://api.typeform.com',
|
||||
primaryColor: '#262627',
|
||||
primaryColor: '262627',
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
|
@@ -14,7 +14,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: '',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '#39a86d',
|
||||
primaryColor: '39a86d',
|
||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
|
@@ -10,7 +10,7 @@ export default defineApp({
|
||||
supportsConnections: false,
|
||||
baseUrl: '',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '#0059F7',
|
||||
primaryColor: '0059F7',
|
||||
actions,
|
||||
triggers,
|
||||
});
|
||||
|
@@ -13,7 +13,7 @@ export default defineApp({
|
||||
supportsConnections: true,
|
||||
baseUrl: 'https://wordpress.com',
|
||||
apiBaseUrl: '',
|
||||
primaryColor: '#464342',
|
||||
primaryColor: '464342',
|
||||
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||
auth,
|
||||
triggers,
|
||||
|
@@ -11,7 +11,7 @@ export default defineApp({
|
||||
apiBaseUrl: 'https://api.xero.com',
|
||||
iconUrl: '{BASE_URL}/apps/xero/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/xero/connection',
|
||||
primaryColor: '#13B5EA',
|
||||
primaryColor: '13B5EA',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
|
@@ -10,7 +10,7 @@ export default defineApp({
|
||||
apiBaseUrl: 'https://api.ynab.com/v1',
|
||||
iconUrl: '{BASE_URL}/apps/you-need-a-budget/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/you-need-a-budget/connection',
|
||||
primaryColor: '#19223C',
|
||||
primaryColor: '19223C',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
|
@@ -10,7 +10,7 @@ export default defineApp({
|
||||
apiBaseUrl: 'https://www.googleapis.com/youtube',
|
||||
iconUrl: '{BASE_URL}/apps/youtube/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/youtube/connection',
|
||||
primaryColor: '#FF0000',
|
||||
primaryColor: 'FF0000',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
|
@@ -12,7 +12,7 @@ export default defineApp({
|
||||
apiBaseUrl: '',
|
||||
iconUrl: '{BASE_URL}/apps/zendesk/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/zendesk/connection',
|
||||
primaryColor: '#17494d',
|
||||
primaryColor: '17494d',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
|
@@ -1,28 +1,23 @@
|
||||
import pick from 'lodash/pick.js';
|
||||
import { renderObject } from '../../../../../helpers/renderer.js';
|
||||
import Config from '../../../../../models/config.js';
|
||||
|
||||
export default async (request, response) => {
|
||||
const config = await Config.query().updateFirstOrInsert(
|
||||
configParams(request)
|
||||
);
|
||||
const config = configParams(request);
|
||||
|
||||
await Config.batchUpdate(config);
|
||||
|
||||
renderObject(response, config);
|
||||
};
|
||||
|
||||
const configParams = (request) => {
|
||||
const {
|
||||
logoSvgData,
|
||||
palettePrimaryDark,
|
||||
palettePrimaryLight,
|
||||
palettePrimaryMain,
|
||||
title,
|
||||
} = request.body;
|
||||
const updatableConfigurationKeys = [
|
||||
'logo.svgData',
|
||||
'palette.primary.dark',
|
||||
'palette.primary.light',
|
||||
'palette.primary.main',
|
||||
'title',
|
||||
];
|
||||
|
||||
return {
|
||||
logoSvgData,
|
||||
palettePrimaryDark,
|
||||
palettePrimaryLight,
|
||||
palettePrimaryMain,
|
||||
title,
|
||||
};
|
||||
return pick(request.body, updatableConfigurationKeys);
|
||||
};
|
||||
|
@@ -5,7 +5,7 @@ import app from '../../../../../app.js';
|
||||
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
|
||||
import { createUser } from '../../../../../../test/factories/user.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';
|
||||
|
||||
describe('PATCH /api/v1/admin/config', () => {
|
||||
@@ -30,13 +30,13 @@ describe('PATCH /api/v1/admin/config', () => {
|
||||
|
||||
const appConfig = {
|
||||
title,
|
||||
palettePrimaryMain: palettePrimaryMain,
|
||||
palettePrimaryDark: palettePrimaryDark,
|
||||
palettePrimaryLight: palettePrimaryLight,
|
||||
logoSvgData: logoSvgData,
|
||||
'palette.primary.main': palettePrimaryMain,
|
||||
'palette.primary.dark': palettePrimaryDark,
|
||||
'palette.primary.light': palettePrimaryLight,
|
||||
'logo.svgData': logoSvgData,
|
||||
};
|
||||
|
||||
await updateConfig(appConfig);
|
||||
await createBulkConfig(appConfig);
|
||||
|
||||
const newTitle = 'Updated title';
|
||||
|
||||
@@ -51,7 +51,7 @@ describe('PATCH /api/v1/admin/config', () => {
|
||||
.expect(200);
|
||||
|
||||
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 () => {
|
||||
@@ -68,7 +68,7 @@ describe('PATCH /api/v1/admin/config', () => {
|
||||
.expect(200);
|
||||
|
||||
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 () => {
|
||||
@@ -83,6 +83,6 @@ describe('PATCH /api/v1/admin/config', () => {
|
||||
.expect(200);
|
||||
|
||||
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 { renderObject } from '../../../../helpers/renderer.js';
|
||||
|
||||
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);
|
||||
};
|
||||
|
@@ -1,47 +1,66 @@
|
||||
import { vi, expect, describe, it } from 'vitest';
|
||||
import request from 'supertest';
|
||||
import { updateConfig } from '../../../../../test/factories/config.js';
|
||||
import { createConfig } from '../../../../../test/factories/config.js';
|
||||
import app from '../../../../app.js';
|
||||
import configMock from '../../../../../test/mocks/rest/api/v1/automatisch/config.js';
|
||||
import * as license from '../../../../helpers/license.ee.js';
|
||||
import appConfig from '../../../../config/app.js';
|
||||
|
||||
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(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({
|
||||
logoSvgData: '<svg>Sample</svg>',
|
||||
palettePrimaryDark: '#001f52',
|
||||
palettePrimaryLight: '#4286FF',
|
||||
palettePrimaryMain: '#0059F7',
|
||||
title: 'Sample Title',
|
||||
const logoConfig = await createConfig({
|
||||
key: 'logo.svgData',
|
||||
value: { data: '<svg>Sample</svg>' },
|
||||
});
|
||||
|
||||
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)
|
||||
.get('/api/v1/automatisch/config')
|
||||
.expect(200);
|
||||
|
||||
const expectedPayload = configMock({
|
||||
...config,
|
||||
disableNotificationsPage: true,
|
||||
disableFavicon: true,
|
||||
additionalDrawerLink: 'link',
|
||||
additionalDrawerLinkIcon: 'icon',
|
||||
additionalDrawerLinkText: 'text',
|
||||
});
|
||||
const expectedPayload = configMock(
|
||||
logoConfig,
|
||||
primaryDarkConfig,
|
||||
primaryLightConfig,
|
||||
primaryMainConfig,
|
||||
titleConfig
|
||||
);
|
||||
|
||||
expect(response.body).toStrictEqual(expectedPayload);
|
||||
expect(response.body).toEqual(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 { createRole } from '../../../../../../test/factories/role';
|
||||
import { createUser } from '../../../../../../test/factories/user';
|
||||
import { markInstallationCompleted } from '../../../../../../test/factories/config';
|
||||
import { createInstallationCompletedConfig } from '../../../../../../test/factories/config';
|
||||
|
||||
describe('POST /api/v1/installation/users', () => {
|
||||
let adminRole;
|
||||
@@ -59,7 +59,7 @@ describe('POST /api/v1/installation/users', () => {
|
||||
|
||||
describe('for completed installations', () => {
|
||||
beforeEach(async () => {
|
||||
await markInstallationCompleted();
|
||||
await createInstallationCompletedConfig();
|
||||
});
|
||||
|
||||
it('should respond with HTTP 403 when installation completed', async () => {
|
||||
|
@@ -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;
|
||||
}
|
@@ -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",
|
||||
]
|
||||
`;
|
@@ -1,52 +0,0 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`Config model > jsonSchema should have correct validations 1`] = `
|
||||
{
|
||||
"properties": {
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
},
|
||||
"id": {
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
"installationCompleted": {
|
||||
"type": "boolean",
|
||||
},
|
||||
"logoSvgData": {
|
||||
"type": [
|
||||
"string",
|
||||
"null",
|
||||
],
|
||||
},
|
||||
"palettePrimaryDark": {
|
||||
"type": [
|
||||
"string",
|
||||
"null",
|
||||
],
|
||||
},
|
||||
"palettePrimaryLight": {
|
||||
"type": [
|
||||
"string",
|
||||
"null",
|
||||
],
|
||||
},
|
||||
"palettePrimaryMain": {
|
||||
"type": [
|
||||
"string",
|
||||
"null",
|
||||
],
|
||||
},
|
||||
"title": {
|
||||
"type": [
|
||||
"string",
|
||||
"null",
|
||||
],
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"type": "object",
|
||||
}
|
||||
`;
|
@@ -1,51 +0,0 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`Connection model > jsonSchema should have correct validations 1`] = `
|
||||
{
|
||||
"properties": {
|
||||
"appAuthClientId": {
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
},
|
||||
"data": {
|
||||
"type": "string",
|
||||
},
|
||||
"deletedAt": {
|
||||
"type": "string",
|
||||
},
|
||||
"draft": {
|
||||
"type": "boolean",
|
||||
},
|
||||
"formattedData": {
|
||||
"type": "object",
|
||||
},
|
||||
"id": {
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
"key": {
|
||||
"maxLength": 255,
|
||||
"minLength": 1,
|
||||
"type": "string",
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string",
|
||||
},
|
||||
"userId": {
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
"verified": {
|
||||
"default": false,
|
||||
"type": "boolean",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"key",
|
||||
],
|
||||
"type": "object",
|
||||
}
|
||||
`;
|
@@ -1,36 +0,0 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`Datastore model > jsonSchema should have correct validations 1`] = `
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
"key": {
|
||||
"minLength": 1,
|
||||
"type": "string",
|
||||
},
|
||||
"scope": {
|
||||
"default": "flow",
|
||||
"enum": [
|
||||
"flow",
|
||||
],
|
||||
"type": "string",
|
||||
},
|
||||
"scopeId": {
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"key",
|
||||
"value",
|
||||
"scopeId",
|
||||
],
|
||||
"type": "object",
|
||||
}
|
||||
`;
|
@@ -1,54 +0,0 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`ExecutionStep model > jsonSchema should have correct validations 1`] = `
|
||||
{
|
||||
"properties": {
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
},
|
||||
"dataIn": {
|
||||
"type": [
|
||||
"object",
|
||||
"null",
|
||||
],
|
||||
},
|
||||
"dataOut": {
|
||||
"type": [
|
||||
"object",
|
||||
"null",
|
||||
],
|
||||
},
|
||||
"deletedAt": {
|
||||
"type": "string",
|
||||
},
|
||||
"errorDetails": {
|
||||
"type": [
|
||||
"object",
|
||||
"null",
|
||||
],
|
||||
},
|
||||
"executionId": {
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
"id": {
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
"status": {
|
||||
"enum": [
|
||||
"success",
|
||||
"failure",
|
||||
],
|
||||
"type": "string",
|
||||
},
|
||||
"stepId": {
|
||||
"type": "string",
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"type": "object",
|
||||
}
|
||||
`;
|
@@ -1,33 +0,0 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`Execution model > jsonSchema should have correct validations 1`] = `
|
||||
{
|
||||
"properties": {
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
},
|
||||
"deletedAt": {
|
||||
"type": "string",
|
||||
},
|
||||
"flowId": {
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
"id": {
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
"internalId": {
|
||||
"type": "string",
|
||||
},
|
||||
"testRun": {
|
||||
"default": false,
|
||||
"type": "boolean",
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"type": "object",
|
||||
}
|
||||
`;
|
@@ -1,37 +0,0 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`Identity model > jsonSchema should have correct validations 1`] = `
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
"providerId": {
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
"providerType": {
|
||||
"enum": [
|
||||
"saml",
|
||||
],
|
||||
"type": "string",
|
||||
},
|
||||
"remoteId": {
|
||||
"minLength": 1,
|
||||
"type": "string",
|
||||
},
|
||||
"userId": {
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"providerId",
|
||||
"remoteId",
|
||||
"userId",
|
||||
"providerType",
|
||||
],
|
||||
"type": "object",
|
||||
}
|
||||
`;
|
@@ -1,41 +0,0 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`SamlAuthProvidersRoleMapping model > jsonSchema should have the correct schema 1`] = `
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
"remoteRoleName": {
|
||||
"minLength": 1,
|
||||
"type": "string",
|
||||
},
|
||||
"roleId": {
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
"samlAuthProviderId": {
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"samlAuthProviderId",
|
||||
"roleId",
|
||||
"remoteRoleName",
|
||||
],
|
||||
"type": "object",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`SamlAuthProvidersRoleMapping model > relationMappings should have samlAuthProvider relation 1`] = `
|
||||
{
|
||||
"join": {
|
||||
"from": "saml_auth_providers_role_mappings.saml_auth_provider_id",
|
||||
"to": "saml_auth_providers.id",
|
||||
},
|
||||
"modelClass": [Function],
|
||||
"relation": [Function],
|
||||
}
|
||||
`;
|
@@ -1,63 +0,0 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`Subscription model > jsonSchema should have correct validations 1`] = `
|
||||
{
|
||||
"properties": {
|
||||
"cancelUrl": {
|
||||
"type": "string",
|
||||
},
|
||||
"cancellationEffectiveDate": {
|
||||
"type": "string",
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
},
|
||||
"deletedAt": {
|
||||
"type": "string",
|
||||
},
|
||||
"id": {
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
"lastBillDate": {
|
||||
"type": "string",
|
||||
},
|
||||
"nextBillAmount": {
|
||||
"type": "string",
|
||||
},
|
||||
"nextBillDate": {
|
||||
"type": "string",
|
||||
},
|
||||
"paddlePlanId": {
|
||||
"type": "string",
|
||||
},
|
||||
"paddleSubscriptionId": {
|
||||
"type": "string",
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
},
|
||||
"updateUrl": {
|
||||
"type": "string",
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string",
|
||||
},
|
||||
"userId": {
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"userId",
|
||||
"paddleSubscriptionId",
|
||||
"paddlePlanId",
|
||||
"updateUrl",
|
||||
"cancelUrl",
|
||||
"status",
|
||||
"nextBillAmount",
|
||||
"nextBillDate",
|
||||
],
|
||||
"type": "object",
|
||||
}
|
||||
`;
|
@@ -1,41 +0,0 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`UsageData model > jsonSchema should have correct validations 1`] = `
|
||||
{
|
||||
"properties": {
|
||||
"consumedTaskCount": {
|
||||
"type": "integer",
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
},
|
||||
"deletedAt": {
|
||||
"type": "string",
|
||||
},
|
||||
"id": {
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
"nextResetAt": {
|
||||
"type": "string",
|
||||
},
|
||||
"subscriptionId": {
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string",
|
||||
},
|
||||
"userId": {
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"userId",
|
||||
"consumedTaskCount",
|
||||
"nextResetAt",
|
||||
],
|
||||
"type": "object",
|
||||
}
|
||||
`;
|
@@ -34,25 +34,24 @@ class AccessToken extends Base {
|
||||
return;
|
||||
}
|
||||
|
||||
const user = await this.$relatedQuery('user');
|
||||
const user = await this
|
||||
.$relatedQuery('user');
|
||||
|
||||
const firstIdentity = await user.$relatedQuery('identities').first();
|
||||
const firstIdentity = await user
|
||||
.$relatedQuery('identities')
|
||||
.first();
|
||||
|
||||
const samlAuthProvider = await firstIdentity
|
||||
.$relatedQuery('samlAuthProvider')
|
||||
.throwIfNotFound();
|
||||
|
||||
const response = await samlAuthProvider.terminateRemoteSession(
|
||||
this.samlSessionId
|
||||
);
|
||||
const response = await samlAuthProvider.terminateRemoteSession(this.samlSessionId);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async revoke() {
|
||||
const response = await this.$query().patch({
|
||||
revokedAt: new Date().toISOString(),
|
||||
});
|
||||
const response = await this.$query().patch({ revokedAt: new Date().toISOString() });
|
||||
|
||||
try {
|
||||
await this.terminateRemoteSamlSession();
|
||||
|
@@ -1,84 +0,0 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import AccessToken from './access-token.js';
|
||||
import User from './user.js';
|
||||
import Base from './base.js';
|
||||
import SamlAuthProvider from './saml-auth-provider.ee.js';
|
||||
import { createAccessToken } from '../../test/factories/access-token.js';
|
||||
import { createUser } from '../../test/factories/user.js';
|
||||
import { createIdentity } from '../../test/factories/identity.js';
|
||||
|
||||
describe('AccessToken model', () => {
|
||||
it('tableName should return correct name', () => {
|
||||
expect(AccessToken.tableName).toBe('access_tokens');
|
||||
});
|
||||
|
||||
it('jsonSchema should have correct validations', () => {
|
||||
expect(AccessToken.jsonSchema).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('relationMappings should return correct associations', () => {
|
||||
const relationMappings = AccessToken.relationMappings();
|
||||
|
||||
const expectedRelations = {
|
||||
user: {
|
||||
relation: Base.BelongsToOneRelation,
|
||||
modelClass: User,
|
||||
join: {
|
||||
from: 'access_tokens.user_id',
|
||||
to: 'users.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(relationMappings).toStrictEqual(expectedRelations);
|
||||
});
|
||||
|
||||
it('revoke should set revokedAt and terminate remote SAML session', async () => {
|
||||
const accessToken = await createAccessToken();
|
||||
|
||||
const terminateRemoteSamlSessionSpy = vi
|
||||
.spyOn(accessToken, 'terminateRemoteSamlSession')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
await accessToken.revoke();
|
||||
|
||||
expect(terminateRemoteSamlSessionSpy).toHaveBeenCalledOnce();
|
||||
expect(accessToken.revokedAt).not.toBeUndefined();
|
||||
});
|
||||
|
||||
describe('terminateRemoteSamlSession', () => {
|
||||
it('should terminate remote SAML session when exists', async () => {
|
||||
const user = await createUser();
|
||||
const accessToken = await createAccessToken({
|
||||
userId: user.id,
|
||||
samlSessionId: 'random-remote-session-id',
|
||||
});
|
||||
await createIdentity({ userId: user.id });
|
||||
|
||||
const terminateRemoteSamlSessionSpy = vi
|
||||
.spyOn(SamlAuthProvider.prototype, 'terminateRemoteSession')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
await accessToken.terminateRemoteSamlSession();
|
||||
|
||||
expect(terminateRemoteSamlSessionSpy).toHaveBeenCalledWith(
|
||||
accessToken.samlSessionId
|
||||
);
|
||||
});
|
||||
|
||||
it(`should return undefined when remote SALM session doesn't exist`, async () => {
|
||||
const user = await createUser();
|
||||
const accessToken = await createAccessToken({ userId: user.id });
|
||||
await createIdentity({ userId: user.id });
|
||||
|
||||
const terminateRemoteSamlSessionSpy = vi
|
||||
.spyOn(SamlAuthProvider.prototype, 'terminateRemoteSession')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
const expected = await accessToken.terminateRemoteSamlSession();
|
||||
|
||||
expect(terminateRemoteSamlSessionSpy).not.toHaveBeenCalledOnce();
|
||||
expect(expected).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
@@ -31,7 +31,6 @@ class AppAuthClient extends Base {
|
||||
|
||||
delete this.formattedAuthDefaults;
|
||||
}
|
||||
|
||||
decryptData() {
|
||||
if (!this.eligibleForDecryption()) return;
|
||||
|
||||
|
@@ -1,179 +0,0 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import AES from 'crypto-js/aes.js';
|
||||
import enc from 'crypto-js/enc-utf8.js';
|
||||
|
||||
import AppAuthClient from './app-auth-client.js';
|
||||
import appConfig from '../config/app.js';
|
||||
import { createAppAuthClient } from '../../test/factories/app-auth-client.js';
|
||||
|
||||
describe('AppAuthClient model', () => {
|
||||
it('tableName should return correct name', () => {
|
||||
expect(AppAuthClient.tableName).toBe('app_auth_clients');
|
||||
});
|
||||
|
||||
it('jsonSchema should have correct validations', () => {
|
||||
expect(AppAuthClient.jsonSchema).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('encryptData', () => {
|
||||
it('should return undefined if eligibleForEncryption is not true', async () => {
|
||||
vi.spyOn(
|
||||
AppAuthClient.prototype,
|
||||
'eligibleForEncryption'
|
||||
).mockReturnValue(false);
|
||||
|
||||
const appAuthClient = new AppAuthClient();
|
||||
|
||||
expect(appAuthClient.encryptData()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should encrypt formattedAuthDefaults and set it to authDefaults', async () => {
|
||||
vi.spyOn(
|
||||
AppAuthClient.prototype,
|
||||
'eligibleForEncryption'
|
||||
).mockReturnValue(true);
|
||||
|
||||
const formattedAuthDefaults = {
|
||||
key: 'value',
|
||||
};
|
||||
|
||||
const appAuthClient = new AppAuthClient();
|
||||
appAuthClient.formattedAuthDefaults = formattedAuthDefaults;
|
||||
appAuthClient.encryptData();
|
||||
|
||||
const expectedDecryptedValue = JSON.parse(
|
||||
AES.decrypt(
|
||||
appAuthClient.authDefaults,
|
||||
appConfig.encryptionKey
|
||||
).toString(enc)
|
||||
);
|
||||
|
||||
expect(formattedAuthDefaults).toStrictEqual(expectedDecryptedValue);
|
||||
expect(appAuthClient.authDefaults).not.toEqual(formattedAuthDefaults);
|
||||
});
|
||||
|
||||
it('should encrypt formattedAuthDefaults and remove formattedAuthDefaults', async () => {
|
||||
vi.spyOn(
|
||||
AppAuthClient.prototype,
|
||||
'eligibleForEncryption'
|
||||
).mockReturnValue(true);
|
||||
|
||||
const formattedAuthDefaults = {
|
||||
key: 'value',
|
||||
};
|
||||
|
||||
const appAuthClient = new AppAuthClient();
|
||||
appAuthClient.formattedAuthDefaults = formattedAuthDefaults;
|
||||
appAuthClient.encryptData();
|
||||
|
||||
expect(appAuthClient.formattedAuthDefaults).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('decryptData', () => {
|
||||
it('should return undefined if eligibleForDecryption is not true', () => {
|
||||
vi.spyOn(
|
||||
AppAuthClient.prototype,
|
||||
'eligibleForDecryption'
|
||||
).mockReturnValue(false);
|
||||
|
||||
const appAuthClient = new AppAuthClient();
|
||||
|
||||
expect(appAuthClient.decryptData()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should decrypt authDefaults and set it to formattedAuthDefaults', async () => {
|
||||
vi.spyOn(
|
||||
AppAuthClient.prototype,
|
||||
'eligibleForDecryption'
|
||||
).mockReturnValue(true);
|
||||
|
||||
const formattedAuthDefaults = {
|
||||
key: 'value',
|
||||
};
|
||||
|
||||
const authDefaults = AES.encrypt(
|
||||
JSON.stringify(formattedAuthDefaults),
|
||||
appConfig.encryptionKey
|
||||
).toString();
|
||||
|
||||
const appAuthClient = new AppAuthClient();
|
||||
appAuthClient.authDefaults = authDefaults;
|
||||
appAuthClient.decryptData();
|
||||
|
||||
expect(appAuthClient.formattedAuthDefaults).toStrictEqual(
|
||||
formattedAuthDefaults
|
||||
);
|
||||
expect(appAuthClient.authDefaults).not.toEqual(formattedAuthDefaults);
|
||||
});
|
||||
});
|
||||
|
||||
describe('eligibleForEncryption', () => {
|
||||
it('should return true when formattedAuthDefaults property exists', async () => {
|
||||
const appAuthClient = await createAppAuthClient();
|
||||
|
||||
expect(appAuthClient.eligibleForEncryption()).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when formattedAuthDefaults property doesn't exist", async () => {
|
||||
const appAuthClient = await createAppAuthClient();
|
||||
|
||||
delete appAuthClient.formattedAuthDefaults;
|
||||
|
||||
expect(appAuthClient.eligibleForEncryption()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('eligibleForDecryption', () => {
|
||||
it('should return true when authDefaults property exists', async () => {
|
||||
const appAuthClient = await createAppAuthClient();
|
||||
|
||||
expect(appAuthClient.eligibleForDecryption()).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when authDefaults property doesn't exist", async () => {
|
||||
const appAuthClient = await createAppAuthClient();
|
||||
|
||||
delete appAuthClient.authDefaults;
|
||||
|
||||
expect(appAuthClient.eligibleForDecryption()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('$beforeInsert should call AppAuthClient.encryptData', async () => {
|
||||
const appAuthClientBeforeInsertSpy = vi.spyOn(
|
||||
AppAuthClient.prototype,
|
||||
'encryptData'
|
||||
);
|
||||
|
||||
await createAppAuthClient();
|
||||
|
||||
expect(appAuthClientBeforeInsertSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('$beforeUpdate should call AppAuthClient.encryptData', async () => {
|
||||
const appAuthClient = await createAppAuthClient();
|
||||
|
||||
const appAuthClientBeforeUpdateSpy = vi.spyOn(
|
||||
AppAuthClient.prototype,
|
||||
'encryptData'
|
||||
);
|
||||
|
||||
await appAuthClient.$query().patchAndFetch({ name: 'sample' });
|
||||
|
||||
expect(appAuthClientBeforeUpdateSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('$afterFind should call AppAuthClient.decryptData', async () => {
|
||||
const appAuthClient = await createAppAuthClient();
|
||||
|
||||
const appAuthClientAfterFindSpy = vi.spyOn(
|
||||
AppAuthClient.prototype,
|
||||
'decryptData'
|
||||
);
|
||||
|
||||
await appAuthClient.$query();
|
||||
|
||||
expect(appAuthClientAfterFindSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
@@ -8,7 +8,6 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
class App {
|
||||
static folderPath = join(__dirname, '../apps');
|
||||
|
||||
static list = fs
|
||||
.readdirSync(this.folderPath)
|
||||
.filter((file) => fs.statSync(join(this.folderPath, file)).isDirectory());
|
||||
|
@@ -1,418 +0,0 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
|
||||
import App from './app.js';
|
||||
import * as getAppModule from '../helpers/get-app.js';
|
||||
import * as appInfoConverterModule from '../helpers/app-info-converter.js';
|
||||
|
||||
describe('App model', () => {
|
||||
it('folderPath should return correct path', () => {
|
||||
expect(App.folderPath.endsWith('/packages/backend/src/apps')).toBe(true);
|
||||
});
|
||||
|
||||
it('list should have list of applications keys', () => {
|
||||
expect(App.list).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should return all applications', async () => {
|
||||
const apps = await App.findAll();
|
||||
|
||||
expect(apps.length).toBe(App.list.length);
|
||||
});
|
||||
|
||||
it('should return matching applications when name argument is given', async () => {
|
||||
const apps = await App.findAll('deepl');
|
||||
|
||||
expect(apps.length).toBe(1);
|
||||
expect(apps[0].key).toBe('deepl');
|
||||
});
|
||||
|
||||
it('should return matching applications in plain JSON when stripFunc argument is true', async () => {
|
||||
const appFindOneByNameSpy = vi.spyOn(App, 'findOneByName');
|
||||
|
||||
await App.findAll('deepl', true);
|
||||
|
||||
expect(appFindOneByNameSpy).toHaveBeenCalledWith('deepl', true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findOneByName', () => {
|
||||
it('should return app info for given app name', async () => {
|
||||
const getAppSpy = vi
|
||||
.spyOn(getAppModule, 'default')
|
||||
.mockImplementation(() => 'mock-app');
|
||||
|
||||
const appInfoConverterSpy = vi
|
||||
.spyOn(appInfoConverterModule, 'default')
|
||||
.mockImplementation(() => 'app-info');
|
||||
|
||||
const app = await App.findOneByName('DeepL');
|
||||
|
||||
expect(getAppSpy).toHaveBeenCalledWith('deepl', false);
|
||||
expect(appInfoConverterSpy).toHaveBeenCalledWith('mock-app');
|
||||
expect(app).toStrictEqual('app-info');
|
||||
});
|
||||
|
||||
it('should return app info for given app name in plain JSON when stripFunc argument is true', async () => {
|
||||
const getAppSpy = vi
|
||||
.spyOn(getAppModule, 'default')
|
||||
.mockImplementation(() => 'mock-app');
|
||||
|
||||
const appInfoConverterSpy = vi
|
||||
.spyOn(appInfoConverterModule, 'default')
|
||||
.mockImplementation(() => 'app-info');
|
||||
|
||||
const app = await App.findOneByName('DeepL', true);
|
||||
|
||||
expect(getAppSpy).toHaveBeenCalledWith('deepl', true);
|
||||
expect(appInfoConverterSpy).toHaveBeenCalledWith('mock-app');
|
||||
expect(app).toStrictEqual('app-info');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findOneByKey', () => {
|
||||
it('should return app info for given app key', async () => {
|
||||
const getAppSpy = vi
|
||||
.spyOn(getAppModule, 'default')
|
||||
.mockImplementation(() => 'mock-app');
|
||||
|
||||
const appInfoConverterSpy = vi
|
||||
.spyOn(appInfoConverterModule, 'default')
|
||||
.mockImplementation(() => 'app-info');
|
||||
|
||||
const app = await App.findOneByKey('deepl');
|
||||
|
||||
expect(getAppSpy).toHaveBeenCalledWith('deepl', false);
|
||||
expect(appInfoConverterSpy).toHaveBeenCalledWith('mock-app');
|
||||
expect(app).toStrictEqual('app-info');
|
||||
});
|
||||
|
||||
it('should return app info for given app key in plain JSON when stripFunc argument is true', async () => {
|
||||
const getAppSpy = vi
|
||||
.spyOn(getAppModule, 'default')
|
||||
.mockImplementation(() => 'mock-app');
|
||||
|
||||
const appInfoConverterSpy = vi
|
||||
.spyOn(appInfoConverterModule, 'default')
|
||||
.mockImplementation(() => 'app-info');
|
||||
|
||||
const app = await App.findOneByKey('deepl', true);
|
||||
|
||||
expect(getAppSpy).toHaveBeenCalledWith('deepl', true);
|
||||
expect(appInfoConverterSpy).toHaveBeenCalledWith('mock-app');
|
||||
expect(app).toStrictEqual('app-info');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAuthByKey', () => {
|
||||
it('should return app auth for given app key', async () => {
|
||||
const getAppSpy = vi
|
||||
.spyOn(getAppModule, 'default')
|
||||
.mockImplementation(() => ({ auth: 'mock-auth' }));
|
||||
|
||||
const appInfoConverterSpy = vi
|
||||
.spyOn(appInfoConverterModule, 'default')
|
||||
.mockImplementation((input) => input);
|
||||
|
||||
const appAuth = await App.findAuthByKey('deepl');
|
||||
|
||||
expect(getAppSpy).toHaveBeenCalledWith('deepl', false);
|
||||
expect(appInfoConverterSpy).toHaveBeenCalledWith({ auth: 'mock-auth' });
|
||||
expect(appAuth).toStrictEqual('mock-auth');
|
||||
});
|
||||
|
||||
it('should return app auth for given app key in plain JSON when stripFunc argument is true', async () => {
|
||||
const getAppSpy = vi
|
||||
.spyOn(getAppModule, 'default')
|
||||
.mockImplementation(() => ({ auth: 'mock-auth' }));
|
||||
|
||||
const appInfoConverterSpy = vi
|
||||
.spyOn(appInfoConverterModule, 'default')
|
||||
.mockImplementation((input) => input);
|
||||
|
||||
const appAuth = await App.findAuthByKey('deepl', true);
|
||||
|
||||
expect(getAppSpy).toHaveBeenCalledWith('deepl', true);
|
||||
expect(appInfoConverterSpy).toHaveBeenCalledWith({ auth: 'mock-auth' });
|
||||
expect(appAuth).toStrictEqual('mock-auth');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findTriggersByKey', () => {
|
||||
it('should return app triggers for given app key', async () => {
|
||||
const getAppSpy = vi
|
||||
.spyOn(getAppModule, 'default')
|
||||
.mockImplementation(() => ({ triggers: 'mock-triggers' }));
|
||||
|
||||
const appInfoConverterSpy = vi
|
||||
.spyOn(appInfoConverterModule, 'default')
|
||||
.mockImplementation((input) => input);
|
||||
|
||||
const appTriggers = await App.findTriggersByKey('deepl');
|
||||
|
||||
expect(getAppSpy).toHaveBeenCalledWith('deepl', false);
|
||||
expect(appInfoConverterSpy).toHaveBeenCalledWith({
|
||||
triggers: 'mock-triggers',
|
||||
});
|
||||
expect(appTriggers).toStrictEqual('mock-triggers');
|
||||
});
|
||||
|
||||
it('should return app triggers for given app key in plain JSON when stripFunc argument is true', async () => {
|
||||
const getAppSpy = vi
|
||||
.spyOn(getAppModule, 'default')
|
||||
.mockImplementation(() => ({ triggers: 'mock-triggers' }));
|
||||
|
||||
const appInfoConverterSpy = vi
|
||||
.spyOn(appInfoConverterModule, 'default')
|
||||
.mockImplementation((input) => input);
|
||||
|
||||
const appTriggers = await App.findTriggersByKey('deepl', true);
|
||||
|
||||
expect(getAppSpy).toHaveBeenCalledWith('deepl', true);
|
||||
expect(appInfoConverterSpy).toHaveBeenCalledWith({
|
||||
triggers: 'mock-triggers',
|
||||
});
|
||||
expect(appTriggers).toStrictEqual('mock-triggers');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findTriggerSubsteps', () => {
|
||||
it('should return app trigger substeps for given app key', async () => {
|
||||
const getAppSpy = vi
|
||||
.spyOn(getAppModule, 'default')
|
||||
.mockImplementation(() => ({
|
||||
triggers: [{ key: 'mock-trigger', substeps: 'mock-substeps' }],
|
||||
}));
|
||||
|
||||
const appInfoConverterSpy = vi
|
||||
.spyOn(appInfoConverterModule, 'default')
|
||||
.mockImplementation((input) => input);
|
||||
|
||||
const appTriggerSubsteps = await App.findTriggerSubsteps(
|
||||
'deepl',
|
||||
'mock-trigger'
|
||||
);
|
||||
|
||||
expect(getAppSpy).toHaveBeenCalledWith('deepl', false);
|
||||
expect(appInfoConverterSpy).toHaveBeenCalledWith({
|
||||
triggers: [{ key: 'mock-trigger', substeps: 'mock-substeps' }],
|
||||
});
|
||||
expect(appTriggerSubsteps).toStrictEqual('mock-substeps');
|
||||
});
|
||||
|
||||
it('should return app trigger substeps for given app key in plain JSON when stripFunc argument is true', async () => {
|
||||
const getAppSpy = vi
|
||||
.spyOn(getAppModule, 'default')
|
||||
.mockImplementation(() => ({
|
||||
triggers: [{ key: 'mock-trigger', substeps: 'mock-substeps' }],
|
||||
}));
|
||||
|
||||
const appInfoConverterSpy = vi
|
||||
.spyOn(appInfoConverterModule, 'default')
|
||||
.mockImplementation((input) => input);
|
||||
|
||||
const appTriggerSubsteps = await App.findTriggerSubsteps(
|
||||
'deepl',
|
||||
'mock-trigger',
|
||||
true
|
||||
);
|
||||
|
||||
expect(getAppSpy).toHaveBeenCalledWith('deepl', true);
|
||||
expect(appInfoConverterSpy).toHaveBeenCalledWith({
|
||||
triggers: [{ key: 'mock-trigger', substeps: 'mock-substeps' }],
|
||||
});
|
||||
expect(appTriggerSubsteps).toStrictEqual('mock-substeps');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findActionsByKey', () => {
|
||||
it('should return app actions for given app key', async () => {
|
||||
const getAppSpy = vi
|
||||
.spyOn(getAppModule, 'default')
|
||||
.mockImplementation(() => ({ actions: 'mock-actions' }));
|
||||
|
||||
const appInfoConverterSpy = vi
|
||||
.spyOn(appInfoConverterModule, 'default')
|
||||
.mockImplementation((input) => input);
|
||||
|
||||
const appActions = await App.findActionsByKey('deepl');
|
||||
|
||||
expect(getAppSpy).toHaveBeenCalledWith('deepl', false);
|
||||
expect(appInfoConverterSpy).toHaveBeenCalledWith({
|
||||
actions: 'mock-actions',
|
||||
});
|
||||
expect(appActions).toStrictEqual('mock-actions');
|
||||
});
|
||||
|
||||
it('should return app actions for given app key in plain JSON when stripFunc argument is true', async () => {
|
||||
const getAppSpy = vi
|
||||
.spyOn(getAppModule, 'default')
|
||||
.mockImplementation(() => ({ actions: 'mock-actions' }));
|
||||
|
||||
const appInfoConverterSpy = vi
|
||||
.spyOn(appInfoConverterModule, 'default')
|
||||
.mockImplementation((input) => input);
|
||||
|
||||
const appActions = await App.findActionsByKey('deepl', true);
|
||||
|
||||
expect(getAppSpy).toHaveBeenCalledWith('deepl', true);
|
||||
expect(appInfoConverterSpy).toHaveBeenCalledWith({
|
||||
actions: 'mock-actions',
|
||||
});
|
||||
expect(appActions).toStrictEqual('mock-actions');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findActionSubsteps', () => {
|
||||
it('should return app action substeps for given app key', async () => {
|
||||
const getAppSpy = vi
|
||||
.spyOn(getAppModule, 'default')
|
||||
.mockImplementation(() => ({
|
||||
actions: [{ key: 'mock-action', substeps: 'mock-substeps' }],
|
||||
}));
|
||||
|
||||
const appInfoConverterSpy = vi
|
||||
.spyOn(appInfoConverterModule, 'default')
|
||||
.mockImplementation((input) => input);
|
||||
|
||||
const appActionSubsteps = await App.findActionSubsteps(
|
||||
'deepl',
|
||||
'mock-action'
|
||||
);
|
||||
|
||||
expect(getAppSpy).toHaveBeenCalledWith('deepl', false);
|
||||
expect(appInfoConverterSpy).toHaveBeenCalledWith({
|
||||
actions: [{ key: 'mock-action', substeps: 'mock-substeps' }],
|
||||
});
|
||||
expect(appActionSubsteps).toStrictEqual('mock-substeps');
|
||||
});
|
||||
|
||||
it('should return app action substeps for given app key in plain JSON when stripFunc argument is true', async () => {
|
||||
const getAppSpy = vi
|
||||
.spyOn(getAppModule, 'default')
|
||||
.mockImplementation(() => ({
|
||||
actions: [{ key: 'mock-action', substeps: 'mock-substeps' }],
|
||||
}));
|
||||
|
||||
const appInfoConverterSpy = vi
|
||||
.spyOn(appInfoConverterModule, 'default')
|
||||
.mockImplementation((input) => input);
|
||||
|
||||
const appActionSubsteps = await App.findActionSubsteps(
|
||||
'deepl',
|
||||
'mock-action',
|
||||
true
|
||||
);
|
||||
|
||||
expect(getAppSpy).toHaveBeenCalledWith('deepl', true);
|
||||
expect(appInfoConverterSpy).toHaveBeenCalledWith({
|
||||
actions: [{ key: 'mock-action', substeps: 'mock-substeps' }],
|
||||
});
|
||||
expect(appActionSubsteps).toStrictEqual('mock-substeps');
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkAppAndAction', () => {
|
||||
it('should return undefined when app and action exist', async () => {
|
||||
const findOneByKeySpy = vi
|
||||
.spyOn(App, 'findOneByKey')
|
||||
.mockImplementation(() => ({
|
||||
actions: [
|
||||
{
|
||||
key: 'translate-text',
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
const appAndActionExist = await App.checkAppAndAction(
|
||||
'deepl',
|
||||
'translate-text'
|
||||
);
|
||||
|
||||
expect(findOneByKeySpy).toHaveBeenCalledWith('deepl');
|
||||
expect(appAndActionExist).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined when app exists without action argument provided', async () => {
|
||||
const actionFindSpy = vi.fn();
|
||||
const findOneByKeySpy = vi
|
||||
.spyOn(App, 'findOneByKey')
|
||||
.mockImplementation(() => ({
|
||||
actions: {
|
||||
find: actionFindSpy,
|
||||
},
|
||||
}));
|
||||
|
||||
const appAndActionExist = await App.checkAppAndAction('deepl');
|
||||
|
||||
expect(findOneByKeySpy).toHaveBeenCalledWith('deepl');
|
||||
expect(actionFindSpy).not.toHaveBeenCalled();
|
||||
expect(appAndActionExist).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should throw an error when app exists, but action does not', async () => {
|
||||
const findOneByKeySpy = vi
|
||||
.spyOn(App, 'findOneByKey')
|
||||
.mockImplementation(() => ({ name: 'deepl' }));
|
||||
|
||||
await expect(() =>
|
||||
App.checkAppAndAction('deepl', 'non-existing-action')
|
||||
).rejects.toThrowError(
|
||||
'deepl does not have an action with the "non-existing-action" key!'
|
||||
);
|
||||
expect(findOneByKeySpy).toHaveBeenCalledWith('deepl');
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkAppAndTrigger', () => {
|
||||
it('should return undefined when app and trigger exist', async () => {
|
||||
const findOneByKeySpy = vi
|
||||
.spyOn(App, 'findOneByKey')
|
||||
.mockImplementation(() => ({
|
||||
triggers: [
|
||||
{
|
||||
key: 'catch-raw-webhook',
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
const appAndTriggerExist = await App.checkAppAndTrigger(
|
||||
'webhook',
|
||||
'catch-raw-webhook'
|
||||
);
|
||||
|
||||
expect(findOneByKeySpy).toHaveBeenCalledWith('webhook');
|
||||
expect(appAndTriggerExist).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined when app exists without trigger argument provided', async () => {
|
||||
const triggerFindSpy = vi.fn();
|
||||
const findOneByKeySpy = vi
|
||||
.spyOn(App, 'findOneByKey')
|
||||
.mockImplementation(() => ({
|
||||
actions: {
|
||||
find: triggerFindSpy,
|
||||
},
|
||||
}));
|
||||
|
||||
const appAndTriggerExist = await App.checkAppAndTrigger('webhook');
|
||||
|
||||
expect(findOneByKeySpy).toHaveBeenCalledWith('webhook');
|
||||
expect(triggerFindSpy).not.toHaveBeenCalled();
|
||||
expect(appAndTriggerExist).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should throw an error when app exists, but trigger does not', async () => {
|
||||
const findOneByKeySpy = vi
|
||||
.spyOn(App, 'findOneByKey')
|
||||
.mockImplementation(() => ({ name: 'webhook' }));
|
||||
|
||||
await expect(() =>
|
||||
App.checkAppAndTrigger('webhook', 'non-existing-trigger')
|
||||
).rejects.toThrowError(
|
||||
'webhook does not have a trigger with the "non-existing-trigger" key!'
|
||||
);
|
||||
expect(findOneByKeySpy).toHaveBeenCalledWith('webhook');
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,4 +1,3 @@
|
||||
import appConfig from '../config/app.js';
|
||||
import Base from './base.js';
|
||||
|
||||
class Config extends Base {
|
||||
@@ -6,79 +5,68 @@ class Config extends Base {
|
||||
|
||||
static jsonSchema = {
|
||||
type: 'object',
|
||||
required: ['key', 'value'],
|
||||
|
||||
properties: {
|
||||
id: { type: 'string', format: 'uuid' },
|
||||
installationCompleted: { type: 'boolean' },
|
||||
logoSvgData: { type: ['string', 'null'] },
|
||||
palettePrimaryDark: { type: ['string', 'null'] },
|
||||
palettePrimaryLight: { type: ['string', 'null'] },
|
||||
palettePrimaryMain: { type: ['string', 'null'] },
|
||||
title: { type: ['string', 'null'] },
|
||||
createdAt: { type: 'string' },
|
||||
updatedAt: { type: 'string' },
|
||||
key: { type: 'string', minLength: 1 },
|
||||
value: { type: 'object' },
|
||||
},
|
||||
};
|
||||
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'disableNotificationsPage',
|
||||
'disableFavicon',
|
||||
'additionalDrawerLink',
|
||||
'additionalDrawerLinkIcon',
|
||||
'additionalDrawerLinkText',
|
||||
];
|
||||
}
|
||||
|
||||
get disableNotificationsPage() {
|
||||
return appConfig.disableNotificationsPage;
|
||||
}
|
||||
|
||||
get disableFavicon() {
|
||||
return appConfig.disableFavicon;
|
||||
}
|
||||
|
||||
get additionalDrawerLink() {
|
||||
return appConfig.additionalDrawerLink;
|
||||
}
|
||||
|
||||
get additionalDrawerLinkIcon() {
|
||||
return appConfig.additionalDrawerLinkIcon;
|
||||
}
|
||||
|
||||
get additionalDrawerLinkText() {
|
||||
return appConfig.additionalDrawerLinkText;
|
||||
}
|
||||
|
||||
static async get() {
|
||||
const existingConfig = await this.query().limit(1).first();
|
||||
|
||||
if (!existingConfig) {
|
||||
return await this.query().insertAndFetch({});
|
||||
}
|
||||
|
||||
return existingConfig;
|
||||
}
|
||||
|
||||
static async update(config) {
|
||||
const configEntry = await this.get();
|
||||
|
||||
return await configEntry.$query().patchAndFetch(config);
|
||||
}
|
||||
|
||||
static async isInstallationCompleted() {
|
||||
const config = await this.get();
|
||||
const installationCompletedEntry = await this.query()
|
||||
.where({
|
||||
key: 'installation.completed',
|
||||
})
|
||||
.first();
|
||||
|
||||
return config.installationCompleted;
|
||||
const installationCompleted =
|
||||
installationCompletedEntry?.value?.data === true;
|
||||
|
||||
return installationCompleted;
|
||||
}
|
||||
|
||||
static async markInstallationCompleted() {
|
||||
const config = await this.get();
|
||||
|
||||
return await config.$query().patchAndFetch({
|
||||
installationCompleted: true,
|
||||
return await this.query().insert({
|
||||
key: 'installation.completed',
|
||||
value: {
|
||||
data: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static async batchUpdate(config) {
|
||||
const configKeys = Object.keys(config);
|
||||
const updates = [];
|
||||
|
||||
for (const key of configKeys) {
|
||||
const newValue = config[key];
|
||||
|
||||
if (newValue) {
|
||||
const entryUpdate = Config.query()
|
||||
.insert({
|
||||
key,
|
||||
value: {
|
||||
data: newValue,
|
||||
},
|
||||
})
|
||||
.onConflict('key')
|
||||
.merge({
|
||||
value: {
|
||||
data: newValue,
|
||||
},
|
||||
});
|
||||
|
||||
updates.push(entryUpdate);
|
||||
} else {
|
||||
const entryUpdate = Config.query().findOne({ key }).delete();
|
||||
updates.push(entryUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
return await Promise.all(updates);
|
||||
}
|
||||
}
|
||||
|
||||
export default Config;
|
||||
|
@@ -1,137 +0,0 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import appConfig from '../config/app.js';
|
||||
import Config from './config';
|
||||
import { createConfig } from '../../test/factories/config.js';
|
||||
|
||||
describe('Config model', () => {
|
||||
it('tableName should return correct name', () => {
|
||||
expect(Config.tableName).toBe('config');
|
||||
});
|
||||
|
||||
it('jsonSchema should have correct validations', () => {
|
||||
expect(Config.jsonSchema).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('virtualAttributes should return correct attributes', () => {
|
||||
const virtualAttributes = Config.virtualAttributes;
|
||||
|
||||
const expectedAttributes = [
|
||||
'disableNotificationsPage',
|
||||
'disableFavicon',
|
||||
'additionalDrawerLink',
|
||||
'additionalDrawerLinkIcon',
|
||||
'additionalDrawerLinkText',
|
||||
];
|
||||
|
||||
expect(virtualAttributes).toStrictEqual(expectedAttributes);
|
||||
});
|
||||
|
||||
it('disableNotificationsPage should return its value in appConfig', async () => {
|
||||
const disableNotificationsPageSpy = vi.spyOn(
|
||||
appConfig,
|
||||
'disableNotificationsPage',
|
||||
'get'
|
||||
);
|
||||
|
||||
new Config().disableNotificationsPage;
|
||||
|
||||
expect(disableNotificationsPageSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('disableFavicon should return its value in appConfig', async () => {
|
||||
const disableFaviconSpy = vi
|
||||
.spyOn(appConfig, 'disableFavicon', 'get')
|
||||
.mockReturnValue(true);
|
||||
|
||||
new Config().disableFavicon;
|
||||
|
||||
expect(disableFaviconSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('additionalDrawerLink should return its value in appConfig', async () => {
|
||||
const additionalDrawerLinkSpy = vi
|
||||
.spyOn(appConfig, 'additionalDrawerLink', 'get')
|
||||
.mockReturnValue('https://automatisch.io');
|
||||
|
||||
new Config().additionalDrawerLink;
|
||||
|
||||
expect(additionalDrawerLinkSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('additionalDrawerLinkIcon should return its value in appConfig', async () => {
|
||||
const additionalDrawerLinkIconSpy = vi
|
||||
.spyOn(appConfig, 'additionalDrawerLinkIcon', 'get')
|
||||
.mockReturnValue('SampleIcon');
|
||||
|
||||
new Config().additionalDrawerLinkIcon;
|
||||
|
||||
expect(additionalDrawerLinkIconSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('additionalDrawerLinkText should return its value in appConfig', async () => {
|
||||
const additionalDrawerLinkTextSpy = vi
|
||||
.spyOn(appConfig, 'additionalDrawerLinkText', 'get')
|
||||
.mockReturnValue('Go back to Automatisch');
|
||||
|
||||
new Config().additionalDrawerLinkText;
|
||||
|
||||
expect(additionalDrawerLinkTextSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
describe('get', () => {
|
||||
it('should return single config record when it exists', async () => {
|
||||
const createdConfig = await createConfig({
|
||||
title: 'Automatisch',
|
||||
});
|
||||
|
||||
const config = await Config.get();
|
||||
|
||||
expect(config).toStrictEqual(createdConfig);
|
||||
});
|
||||
|
||||
it('should create config record and return when it does not exist', async () => {
|
||||
const configBefore = await Config.query().first();
|
||||
|
||||
expect(configBefore).toBeUndefined();
|
||||
|
||||
const config = await Config.get();
|
||||
|
||||
expect(config).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('update should update existing single record', async () => {
|
||||
const patchAndFetchSpy = vi
|
||||
.fn()
|
||||
.mockImplementation((newConfig) => newConfig);
|
||||
|
||||
vi.spyOn(Config, 'get').mockImplementation(() => ({
|
||||
$query: () => ({
|
||||
patchAndFetch: patchAndFetchSpy,
|
||||
}),
|
||||
}));
|
||||
|
||||
const config = await Config.update({ title: 'Automatisch' });
|
||||
|
||||
expect(patchAndFetchSpy).toHaveBeenCalledWith({ title: 'Automatisch' });
|
||||
expect(config).toStrictEqual({ title: 'Automatisch' });
|
||||
});
|
||||
|
||||
it('isInstallationCompleted should return installationCompleted value', async () => {
|
||||
const configGetSpy = vi.spyOn(Config, 'get').mockImplementation(() => ({
|
||||
installationCompleted: true,
|
||||
}));
|
||||
|
||||
await Config.isInstallationCompleted();
|
||||
|
||||
expect(configGetSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('markInstallationCompleted should update installationCompleted as true', async () => {
|
||||
await Config.update({ installationCompleted: false });
|
||||
|
||||
const config = await Config.markInstallationCompleted();
|
||||
|
||||
expect(config.installationCompleted).toBe(true);
|
||||
});
|
||||
});
|
@@ -160,6 +160,35 @@ class Connection extends Base {
|
||||
return this;
|
||||
}
|
||||
|
||||
// TODO: Make another abstraction like beforeSave instead of using
|
||||
// beforeInsert and beforeUpdate separately for the same operation.
|
||||
async $beforeInsert(queryContext) {
|
||||
await super.$beforeInsert(queryContext);
|
||||
|
||||
await this.checkEligibilityForCreation();
|
||||
|
||||
this.encryptData();
|
||||
}
|
||||
|
||||
async $beforeUpdate(opt, queryContext) {
|
||||
await super.$beforeUpdate(opt, queryContext);
|
||||
this.encryptData();
|
||||
}
|
||||
|
||||
async $afterFind() {
|
||||
this.decryptData();
|
||||
}
|
||||
|
||||
async $afterInsert(queryContext) {
|
||||
await super.$afterInsert(queryContext);
|
||||
Telemetry.connectionCreated(this);
|
||||
}
|
||||
|
||||
async $afterUpdate(opt, queryContext) {
|
||||
await super.$afterUpdate(opt, queryContext);
|
||||
Telemetry.connectionUpdated(this);
|
||||
}
|
||||
|
||||
async getApp() {
|
||||
if (!this.key) return null;
|
||||
|
||||
@@ -249,35 +278,6 @@ class Connection extends Base {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Make another abstraction like beforeSave instead of using
|
||||
// beforeInsert and beforeUpdate separately for the same operation.
|
||||
async $beforeInsert(queryContext) {
|
||||
await super.$beforeInsert(queryContext);
|
||||
|
||||
await this.checkEligibilityForCreation();
|
||||
|
||||
this.encryptData();
|
||||
}
|
||||
|
||||
async $beforeUpdate(opt, queryContext) {
|
||||
await super.$beforeUpdate(opt, queryContext);
|
||||
this.encryptData();
|
||||
}
|
||||
|
||||
async $afterFind() {
|
||||
this.decryptData();
|
||||
}
|
||||
|
||||
async $afterInsert(queryContext) {
|
||||
await super.$afterInsert(queryContext);
|
||||
Telemetry.connectionCreated(this);
|
||||
}
|
||||
|
||||
async $afterUpdate(opt, queryContext) {
|
||||
await super.$afterUpdate(opt, queryContext);
|
||||
Telemetry.connectionUpdated(this);
|
||||
}
|
||||
}
|
||||
|
||||
export default Connection;
|
||||
|
@@ -1,163 +0,0 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import AES from 'crypto-js/aes.js';
|
||||
import enc from 'crypto-js/enc-utf8.js';
|
||||
import appConfig from '../config/app.js';
|
||||
import AppAuthClient from './app-auth-client.js';
|
||||
import AppConfig from './app-config.js';
|
||||
import Base from './base.js';
|
||||
import Connection from './connection';
|
||||
import Step from './step.js';
|
||||
import User from './user.js';
|
||||
|
||||
describe('Connection model', () => {
|
||||
it('tableName should return correct name', () => {
|
||||
expect(Connection.tableName).toBe('connections');
|
||||
});
|
||||
|
||||
it('jsonSchema should have correct validations', () => {
|
||||
expect(Connection.jsonSchema).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('virtualAttributes should return correct attributes', () => {
|
||||
const virtualAttributes = Connection.virtualAttributes;
|
||||
|
||||
const expectedAttributes = ['reconnectable'];
|
||||
|
||||
expect(virtualAttributes).toStrictEqual(expectedAttributes);
|
||||
});
|
||||
|
||||
it('relationMappings should return correct associations', () => {
|
||||
const relationMappings = Connection.relationMappings();
|
||||
|
||||
const expectedRelations = {
|
||||
user: {
|
||||
relation: Base.BelongsToOneRelation,
|
||||
modelClass: User,
|
||||
join: {
|
||||
from: 'connections.user_id',
|
||||
to: 'users.id',
|
||||
},
|
||||
},
|
||||
steps: {
|
||||
relation: Base.HasManyRelation,
|
||||
modelClass: Step,
|
||||
join: {
|
||||
from: 'connections.id',
|
||||
to: 'steps.connection_id',
|
||||
},
|
||||
},
|
||||
triggerSteps: {
|
||||
relation: Base.HasManyRelation,
|
||||
modelClass: Step,
|
||||
join: {
|
||||
from: 'connections.id',
|
||||
to: 'steps.connection_id',
|
||||
},
|
||||
filter: expect.any(Function),
|
||||
},
|
||||
appConfig: {
|
||||
relation: Base.BelongsToOneRelation,
|
||||
modelClass: AppConfig,
|
||||
join: {
|
||||
from: 'connections.key',
|
||||
to: 'app_configs.key',
|
||||
},
|
||||
},
|
||||
appAuthClient: {
|
||||
relation: Base.BelongsToOneRelation,
|
||||
modelClass: AppAuthClient,
|
||||
join: {
|
||||
from: 'connections.app_auth_client_id',
|
||||
to: 'app_auth_clients.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(relationMappings).toStrictEqual(expectedRelations);
|
||||
});
|
||||
|
||||
describe.todo('reconnectable');
|
||||
|
||||
describe('encryptData', () => {
|
||||
it('should return undefined if eligibleForEncryption is not true', async () => {
|
||||
vi.spyOn(Connection.prototype, 'eligibleForEncryption').mockReturnValue(
|
||||
false
|
||||
);
|
||||
|
||||
const connection = new Connection();
|
||||
|
||||
expect(connection.encryptData()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should encrypt formattedData and set it to data', async () => {
|
||||
vi.spyOn(Connection.prototype, 'eligibleForEncryption').mockReturnValue(
|
||||
true
|
||||
);
|
||||
|
||||
const formattedData = {
|
||||
key: 'value',
|
||||
};
|
||||
|
||||
const connection = new Connection();
|
||||
connection.formattedData = formattedData;
|
||||
connection.encryptData();
|
||||
|
||||
const expectedDecryptedValue = JSON.parse(
|
||||
AES.decrypt(connection.data, appConfig.encryptionKey).toString(enc)
|
||||
);
|
||||
|
||||
expect(formattedData).toStrictEqual(expectedDecryptedValue);
|
||||
expect(connection.data).not.toEqual(formattedData);
|
||||
});
|
||||
|
||||
it('should encrypt formattedData and remove formattedData', async () => {
|
||||
vi.spyOn(Connection.prototype, 'eligibleForEncryption').mockReturnValue(
|
||||
true
|
||||
);
|
||||
|
||||
const formattedData = {
|
||||
key: 'value',
|
||||
};
|
||||
|
||||
const connection = new Connection();
|
||||
connection.formattedData = formattedData;
|
||||
connection.encryptData();
|
||||
|
||||
expect(connection.formattedData).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('decryptData', () => {
|
||||
it('should return undefined if eligibleForDecryption is not true', () => {
|
||||
vi.spyOn(Connection.prototype, 'eligibleForDecryption').mockReturnValue(
|
||||
false
|
||||
);
|
||||
|
||||
const connection = new Connection();
|
||||
|
||||
expect(connection.decryptData()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should decrypt data and set it to formattedData', async () => {
|
||||
vi.spyOn(Connection.prototype, 'eligibleForDecryption').mockReturnValue(
|
||||
true
|
||||
);
|
||||
|
||||
const formattedData = {
|
||||
key: 'value',
|
||||
};
|
||||
|
||||
const data = AES.encrypt(
|
||||
JSON.stringify(formattedData),
|
||||
appConfig.encryptionKey
|
||||
).toString();
|
||||
|
||||
const connection = new Connection();
|
||||
connection.data = data;
|
||||
connection.decryptData();
|
||||
|
||||
expect(connection.formattedData).toStrictEqual(formattedData);
|
||||
expect(connection.data).not.toEqual(formattedData);
|
||||
});
|
||||
});
|
||||
});
|
@@ -5,7 +5,7 @@ class Datastore extends Base {
|
||||
|
||||
static jsonSchema = {
|
||||
type: 'object',
|
||||
required: ['key', 'value', 'scopeId'],
|
||||
required: ['key', 'value', 'scope', 'scopeId'],
|
||||
|
||||
properties: {
|
||||
id: { type: 'string', format: 'uuid' },
|
||||
|
@@ -1,12 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import Datastore from './datastore';
|
||||
|
||||
describe('Datastore model', () => {
|
||||
it('tableName should return correct name', () => {
|
||||
expect(Datastore.tableName).toBe('datastore');
|
||||
});
|
||||
|
||||
it('jsonSchema should have correct validations', () => {
|
||||
expect(Datastore.jsonSchema).toMatchSnapshot();
|
||||
});
|
||||
});
|
@@ -47,31 +47,21 @@ class ExecutionStep extends Base {
|
||||
return this.status === 'failure';
|
||||
}
|
||||
|
||||
async isSucceededNonTestRun() {
|
||||
const execution = await this.$relatedQuery('execution');
|
||||
return !execution.testRun && !this.isFailed;
|
||||
}
|
||||
|
||||
async updateUsageData() {
|
||||
const execution = await this.$relatedQuery('execution');
|
||||
|
||||
const flow = await execution.$relatedQuery('flow');
|
||||
const user = await flow.$relatedQuery('user');
|
||||
const usageData = await user.$relatedQuery('currentUsageData');
|
||||
|
||||
await usageData.increaseConsumedTaskCountByOne();
|
||||
}
|
||||
|
||||
async increaseUsageCount() {
|
||||
if (appConfig.isCloud && this.isSucceededNonTestRun()) {
|
||||
await this.updateUsageData();
|
||||
}
|
||||
}
|
||||
|
||||
async $afterInsert(queryContext) {
|
||||
await super.$afterInsert(queryContext);
|
||||
Telemetry.executionStepCreated(this);
|
||||
await this.increaseUsageCount();
|
||||
|
||||
if (appConfig.isCloud) {
|
||||
const execution = await this.$relatedQuery('execution');
|
||||
|
||||
if (!execution.testRun && !this.isFailed) {
|
||||
const flow = await execution.$relatedQuery('flow');
|
||||
const user = await flow.$relatedQuery('user');
|
||||
const usageData = await user.$relatedQuery('currentUsageData');
|
||||
|
||||
await usageData.increaseConsumedTaskCountByOne();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user