Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
1f93f3b262 chore(deps): bump express from 4.18.2 to 4.20.0
Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.20.0.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.2...4.20.0)

---
updated-dependencies:
- dependency-name: express
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 12:15:32 +00:00
138 changed files with 526 additions and 2926 deletions

View File

@@ -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:

View File

@@ -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",

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,
});

View File

@@ -9,6 +9,6 @@ export default defineApp({
supportsConnections: false,
baseUrl: '',
apiBaseUrl: '',
primaryColor: '#001F52',
primaryColor: '001F52',
actions,
});

View File

@@ -9,6 +9,6 @@ export default defineApp({
supportsConnections: false,
baseUrl: '',
apiBaseUrl: '',
primaryColor: '#001F52',
primaryColor: '001F52',
actions,
});

View File

@@ -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,

View File

@@ -9,6 +9,6 @@ export default defineApp({
supportsConnections: false,
baseUrl: '',
apiBaseUrl: '',
primaryColor: '#001F52',
primaryColor: '001F52',
actions,
});

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -9,6 +9,6 @@ export default defineApp({
supportsConnections: false,
baseUrl: '',
apiBaseUrl: '',
primaryColor: '#001F52',
primaryColor: '001F52',
actions,
});

View File

@@ -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',

View File

@@ -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,

View File

@@ -10,7 +10,7 @@ export default defineApp({
supportsConnections: false,
baseUrl: '',
apiBaseUrl: '',
primaryColor: '#001F52',
primaryColor: '001F52',
actions,
dynamicFields,
});

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -9,6 +9,6 @@ export default defineApp({
supportsConnections: false,
baseUrl: '',
apiBaseUrl: '',
primaryColor: '#000000',
primaryColor: '000000',
actions,
});

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -10,7 +10,7 @@ export default defineApp({
supportsConnections: true,
baseUrl: 'https://odoo.com',
apiBaseUrl: '',
primaryColor: '#9c5789',
primaryColor: '9c5789',
auth,
actions,
});

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -10,7 +10,7 @@ export default defineApp({
supportsConnections: true,
baseUrl: '',
apiBaseUrl: '',
primaryColor: '#336791',
primaryColor: '336791',
auth,
actions,
});

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -9,6 +9,6 @@ export default defineApp({
supportsConnections: false,
baseUrl: '',
apiBaseUrl: '',
primaryColor: '#ff8800',
primaryColor: 'ff8800',
triggers,
});

View File

@@ -13,7 +13,7 @@ export default defineApp({
supportsConnections: true,
baseUrl: 'https://salesforce.com',
apiBaseUrl: '',
primaryColor: '#00A1E0',
primaryColor: '00A1E0',
beforeRequest: [addAuthHeader],
auth,
triggers,

View File

@@ -9,7 +9,7 @@ export default defineApp({
authDocUrl: '{DOCS_URL}/apps/scheduler/connection',
baseUrl: '',
apiBaseUrl: '',
primaryColor: '#0059F7',
primaryColor: '0059F7',
supportsConnections: false,
triggers,
});

View File

@@ -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,

View File

@@ -13,7 +13,7 @@ export default defineApp({
supportsConnections: true,
baseUrl: 'https://signalwire.com',
apiBaseUrl: '',
primaryColor: '#044cf6',
primaryColor: '044cf6',
beforeRequest: [addAuthHeader],
auth,
triggers,

View File

@@ -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,

View File

@@ -10,7 +10,7 @@ export default defineApp({
supportsConnections: true,
baseUrl: '',
apiBaseUrl: '',
primaryColor: '#2DAAE1',
primaryColor: '2DAAE1',
auth,
actions,
});

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -14,7 +14,7 @@ export default defineApp({
supportsConnections: true,
baseUrl: '',
apiBaseUrl: '',
primaryColor: '#39a86d',
primaryColor: '39a86d',
beforeRequest: [setBaseUrl, addAuthHeader],
auth,
triggers,

View File

@@ -10,7 +10,7 @@ export default defineApp({
supportsConnections: false,
baseUrl: '',
apiBaseUrl: '',
primaryColor: '#0059F7',
primaryColor: '0059F7',
actions,
triggers,
});

View File

@@ -13,7 +13,7 @@ export default defineApp({
supportsConnections: true,
baseUrl: 'https://wordpress.com',
apiBaseUrl: '',
primaryColor: '#464342',
primaryColor: '464342',
beforeRequest: [setBaseUrl, addAuthHeader],
auth,
triggers,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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);
};

View File

@@ -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');
});
});

View File

@@ -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);
};

View File

@@ -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');
});
});

View File

@@ -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 () => {

View File

@@ -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;
}

View File

@@ -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",
}
`;

View File

@@ -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",
}
`;

View File

@@ -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",
]
`;

View File

@@ -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",
}
`;

View File

@@ -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",
}
`;

View File

@@ -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",
}
`;

View File

@@ -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",
}
`;

View File

@@ -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",
}
`;

View File

@@ -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",
}
`;

View File

@@ -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],
}
`;

View File

@@ -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",
}
`;

View File

@@ -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",
}
`;

View File

@@ -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();

View File

@@ -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();
});
});
});

View File

@@ -31,7 +31,6 @@ class AppAuthClient extends Base {
delete this.formattedAuthDefaults;
}
decryptData() {
if (!this.eligibleForDecryption()) return;

View File

@@ -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();
});
});

View File

@@ -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());

View File

@@ -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');
});
});
});

View File

@@ -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;

View File

@@ -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);
});
});

View File

@@ -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;

View File

@@ -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);
});
});
});

View File

@@ -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' },

View File

@@ -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();
});
});

View File

@@ -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