Compare commits

..

1 Commits

Author SHA1 Message Date
Rıdvan Akca
8e45a42a6b feat(discord): add change scheduled event action 2024-02-23 18:09:54 +03:00
494 changed files with 5682 additions and 5711 deletions

View File

@@ -28,7 +28,7 @@ cd packages/web
rm -rf .env rm -rf .env
echo " echo "
PORT=$WEB_PORT PORT=$WEB_PORT
REACT_APP_BACKEND_URL=http://localhost:$BACKEND_PORT REACT_APP_GRAPHQL_URL=http://localhost:$BACKEND_PORT/graphql
" >> .env " >> .env
cd $CURRENT_DIR cd $CURRENT_DIR

18
.eslintrc.js Normal file
View File

@@ -0,0 +1,18 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
overrides: [
{
files: ['**/*.test.ts', '**/test/**/*.ts'],
rules: {
'@typescript-eslint/ban-ts-comment': ['off'],
},
},
],
};

View File

@@ -22,7 +22,7 @@ jobs:
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner." - run: echo "🖥️ The workflow is now ready to test your code on the runner."
- run: yarn --frozen-lockfile - run: yarn --frozen-lockfile
- run: cd packages/backend && yarn lint - run: yarn lint
- run: echo "🍏 This job's status is ${{ job.status }}." - run: echo "🍏 This job's status is ${{ job.status }}."
start-backend-server: start-backend-server:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -62,9 +62,8 @@ jobs:
run: yarn && yarn lerna bootstrap run: yarn && yarn lerna bootstrap
- name: Install Playwright Browsers - name: Install Playwright Browsers
run: yarn playwright install --with-deps run: yarn playwright install --with-deps
- name: Build Automatisch web - name: Build Automatisch
working-directory: ./packages/web run: yarn lerna run --scope=@*/{web,cli} build
run: yarn build
env: env:
# Keep this until we clean up warnings in build processes # Keep this until we clean up warnings in build processes
CI: false CI: false

View File

@@ -6,6 +6,7 @@
"start": "lerna run --stream --parallel --scope=@*/{web,backend} dev", "start": "lerna run --stream --parallel --scope=@*/{web,backend} dev",
"start:web": "lerna run --stream --scope=@*/web dev", "start:web": "lerna run --stream --scope=@*/web dev",
"start:backend": "lerna run --stream --scope=@*/backend dev", "start:backend": "lerna run --stream --scope=@*/backend dev",
"lint": "lerna run --no-bail --stream --parallel --scope=@*/{web,backend} lint",
"build:docs": "cd ./packages/docs && yarn install && yarn build" "build:docs": "cd ./packages/docs && yarn install && yarn build"
}, },
"workspaces": { "workspaces": {
@@ -20,6 +21,8 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.9.1",
"@typescript-eslint/parser": "^5.9.1",
"eslint": "^8.13.0", "eslint": "^8.13.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",

View File

@@ -18,8 +18,8 @@ async function fetchAdminRole() {
} }
export async function createUser( export async function createUser(
email = appConfig.seedUserEmail, email = 'user@automatisch.io',
password = appConfig.seedUserPassword password = 'sample'
) { ) {
const UNIQUE_VIOLATION_CODE = '23505'; const UNIQUE_VIOLATION_CODE = '23505';

View File

@@ -11,7 +11,7 @@
"start:worker": "node src/worker.js", "start:worker": "node src/worker.js",
"pretest": "APP_ENV=test node ./test/setup/prepare-test-env.js", "pretest": "APP_ENV=test node ./test/setup/prepare-test-env.js",
"test": "APP_ENV=test vitest run", "test": "APP_ENV=test vitest run",
"lint": "eslint .", "lint": "eslint . --ignore-path ../../.eslintignore",
"db:create": "node ./bin/database/create.js", "db:create": "node ./bin/database/create.js",
"db:seed:user": "node ./bin/database/seed-user.js", "db:seed:user": "node ./bin/database/seed-user.js",
"db:drop": "node ./bin/database/drop.js", "db:drop": "node ./bin/database/drop.js",
@@ -38,7 +38,6 @@
"debug": "~2.6.9", "debug": "~2.6.9",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"express": "~4.18.2", "express": "~4.18.2",
"express-async-handler": "^1.2.0",
"express-basic-auth": "^1.2.1", "express-basic-auth": "^1.2.1",
"express-graphql": "^0.12.0", "express-graphql": "^0.12.0",
"fast-xml-parser": "^4.0.11", "fast-xml-parser": "^4.0.11",

View File

@@ -1,27 +0,0 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Get value',
key: 'getValue',
description: 'Get value from the persistent datastore.',
arguments: [
{
label: 'Key',
key: 'key',
type: 'string',
required: true,
description: 'The key of your value to get.',
variables: true,
},
],
async run($) {
const keyValuePair = await $.datastore.get({
key: $.step.parameters.key,
});
$.setActionItem({
raw: keyValuePair,
});
},
});

View File

@@ -1,4 +0,0 @@
import getValue from './get-value/index.js';
import setValue from './set-value/index.js';
export default [getValue, setValue];

View File

@@ -1,36 +0,0 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Set value',
key: 'setValue',
description: 'Set value to the persistent datastore.',
arguments: [
{
label: 'Key',
key: 'key',
type: 'string',
required: true,
description: 'The key of your value to set.',
variables: true,
},
{
label: 'Value',
key: 'value',
type: 'string',
required: true,
description: 'The value to set.',
variables: true,
},
],
async run($) {
const keyValuePair = await $.datastore.set({
key: $.step.parameters.key,
value: $.step.parameters.value,
});
$.setActionItem({
raw: keyValuePair,
});
},
});

View File

@@ -1,13 +0,0 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" id="icon">
<defs>
<style>.cls-1{fill:none;}</style>
</defs>
<title>datastore</title>
<circle cx="23" cy="23" r="1"/>
<rect x="8" y="22" width="12" height="2"/>
<circle cx="23" cy="9" r="1"/>
<rect x="8" y="8" width="12" height="2"/>
<path d="M26,14a2,2,0,0,0,2-2V6a2,2,0,0,0-2-2H6A2,2,0,0,0,4,6v6a2,2,0,0,0,2,2H8v4H6a2,2,0,0,0-2,2v6a2,2,0,0,0,2,2H26a2,2,0,0,0,2-2V20a2,2,0,0,0-2-2H24V14ZM6,6H26v6H6ZM26,26H6V20H26Zm-4-8H10V14H22Z"/>
<rect id="_Transparent_Rectangle_" data-name="&lt;Transparent Rectangle&gt;" class="cls-1" width="32" height="32"/>
</svg>

Before

Width:  |  Height:  |  Size: 704 B

View File

@@ -1,14 +0,0 @@
import defineApp from '../../helpers/define-app.js';
import actions from './actions/index.js';
export default defineApp({
name: 'Datastore',
key: 'datastore',
iconUrl: '{BASE_URL}/apps/datastore/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/datastore/connection',
supportsConnections: false,
baseUrl: '',
apiBaseUrl: '',
primaryColor: '001F52',
actions,
});

View File

@@ -0,0 +1,120 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Change a scheduled event',
key: 'changeScheduledEvent',
description: 'Changes a scheduled event',
arguments: [
{
label: 'Scheduled Event',
key: 'scheduledEventId',
type: 'dropdown',
required: true,
description: '',
variables: false,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listScheduledEvents',
},
],
},
},
{
label: 'Status',
key: 'status',
type: 'dropdown',
required: false,
description:
'After the status has been changed to COMPLETED or CANCELED, it becomes immutable and cannot be modified further.',
variables: true,
options: [
{ label: 'SCHEDULED', value: 1 },
{ label: 'ACTIVE', value: 2 },
{ label: 'COMPLETED', value: 3 },
{ label: 'CANCELED', value: 4 },
],
},
{
label: 'Type',
key: 'entityType',
type: 'dropdown',
required: true,
variables: true,
options: [
{ label: 'Stage channel', value: 1 },
{ label: 'Voice channel', value: 2 },
{ label: 'External', value: 3 },
],
additionalFields: {
type: 'query',
name: 'getDynamicFields',
arguments: [
{
name: 'key',
value: 'listScheduledEventFieldsForChange',
},
{
name: 'parameters.entityType',
value: '{parameters.entityType}',
},
],
},
},
{
label: 'Name',
key: 'name',
type: 'string',
required: false,
variables: true,
},
{
label: 'Description',
key: 'description',
type: 'string',
required: false,
variables: true,
},
{
label: 'Image',
key: 'image',
type: 'string',
required: false,
description:
'Image as DataURI scheme [data:image/<jpeg/png/gif>;base64,BASE64_ENCODED_<JPEG/PNG/GIF>_IMAGE_DATA]',
variables: true,
},
],
async run($) {
const data = {
channel_id: $.step.parameters.channel_id,
name: $.step.parameters.name,
scheduled_start_time: $.step.parameters.scheduledStartTime,
scheduled_end_time: $.step.parameters.scheduledEndTime,
description: $.step.parameters.description,
entity_type: $.step.parameters.entityType,
image: $.step.parameters.image,
};
const isExternal = $.step.parameters.entityType === 3;
if (isExternal) {
data.entity_metadata = {
location: $.step.parameters.location,
};
data.channel_id = null;
}
const response = await $.http?.patch(
`/guilds/${$.auth.data.guildId}/scheduled-events/${$.step.parameters.scheduledEventId}`,
data
);
$.setActionItem({ raw: response.data });
},
});

View File

@@ -1,4 +1,9 @@
import changeScheduledEvent from './change-scheduled-event/index.js';
import sendMessageToChannel from './send-message-to-channel/index.js'; import sendMessageToChannel from './send-message-to-channel/index.js';
import createScheduledEvent from './create-scheduled-event/index.js'; import createScheduledEvent from './create-scheduled-event/index.js';
export default [sendMessageToChannel, createScheduledEvent]; export default [
changeScheduledEvent,
sendMessageToChannel,
createScheduledEvent,
];

View File

@@ -1,4 +1,5 @@
import listChannels from './list-channels/index.js'; import listChannels from './list-channels/index.js';
import listScheduledEvents from './list-scheduled-events/index.js';
import listVoiceChannels from './list-voice-channels/index.js'; import listVoiceChannels from './list-voice-channels/index.js';
export default [listChannels, listVoiceChannels]; export default [listChannels, listScheduledEvents, listVoiceChannels];

View File

@@ -0,0 +1,24 @@
export default {
name: 'List scheduled events',
key: 'listScheduledEvents',
async run($) {
const scheduledEvents = {
data: [],
error: null,
};
const response = await $.http.get(
`/guilds/${$.auth.data.guildId}/scheduled-events`
);
scheduledEvents.data = response.data.map((scheduledEvent) => {
return {
value: scheduledEvent.id,
name: scheduledEvent.name,
};
});
return scheduledEvents;
},
};

View File

@@ -1,3 +1,7 @@
import listExternalScheduledEventFields from './list-external-scheduled-event-fields/index.js'; import listExternalScheduledEventFields from './list-external-scheduled-event-fields/index.js';
import listScheduledEventFieldsForChange from './list-scheduled-event-fields-for-change/index.js';
export default [listExternalScheduledEventFields]; export default [
listExternalScheduledEventFields,
listScheduledEventFieldsForChange,
];

View File

@@ -0,0 +1,87 @@
export default {
name: 'List scheduled event fields for change',
key: 'listScheduledEventFieldsForChange',
async run($) {
const isExternal = $.step.parameters.entityType === 3;
if (isExternal) {
return [
{
label: 'Location',
key: 'location',
type: 'string',
required: true,
description:
'The location of the event (1-100 characters). This will be omitted if type is NOT EXTERNAL',
variables: true,
},
{
label: 'Start-Time',
key: 'scheduledStartTime',
type: 'string',
required: false,
description: 'The time the event will start [ISO8601]',
variables: true,
},
{
label: 'End-Time',
key: 'scheduledEndTime',
type: 'string',
required: true,
description:
'The time the event will end [ISO8601]. This will be omitted if type is NOT EXTERNAL',
variables: true,
},
];
}
return [
{
label: 'Channel',
key: 'channel_id',
type: 'dropdown',
required: true,
description:
'Pick a voice or stage channel to link the event to. This will be omitted if type is EXTERNAL',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listVoiceChannels',
},
],
},
},
{
label: 'Location',
key: 'location',
type: 'string',
required: false,
description:
'The location of the event (1-100 characters). This will be omitted if type is NOT EXTERNAL',
variables: true,
},
{
label: 'Start-Time',
key: 'scheduledStartTime',
type: 'string',
required: false,
description: 'The time the event will start [ISO8601]',
variables: true,
},
{
label: 'End-Time',
key: 'scheduledEndTime',
type: 'string',
required: false,
description:
'The time the event will end [ISO8601]. This will be omitted if type is NOT EXTERNAL',
variables: true,
},
];
},
};

View File

@@ -1,3 +1,4 @@
import FormData from 'form-data';
import defineAction from '../../../../helpers/define-action.js'; import defineAction from '../../../../helpers/define-action.js';
export default defineAction({ export default defineAction({
@@ -5,51 +6,45 @@ export default defineAction({
key: 'newChat', key: 'newChat',
description: 'Create a new chat session for Helix AI.', description: 'Create a new chat session for Helix AI.',
arguments: [ arguments: [
{
label: 'Session ID',
key: 'sessionId',
type: 'string',
required: false,
description:
'ID of the chat session to continue. Leave empty to start a new chat.',
variables: true,
},
{
label: 'System Prompt',
key: 'systemPrompt',
type: 'string',
required: false,
description:
'Optional system prompt to start the chat with. It will be used only for new chat sessions.',
variables: true,
},
{ {
label: 'Input', label: 'Input',
key: 'input', key: 'input',
type: 'string', type: 'string',
required: true, required: true,
description: 'User input to start the chat with.', description: 'Prompt to start the chat with.',
variables: true, variables: true,
}, },
], ],
async run($) { async run($) {
const response = await $.http.post('/api/v1/sessions/chat', { const formData = new FormData();
session_id: $.step.parameters.sessionId, formData.append('input', $.step.parameters.input);
system: $.step.parameters.systemPrompt, formData.append('mode', 'inference');
messages: [ formData.append('type', 'text');
{
role: 'user', const sessionResponse = await $.http.post('/api/v1/sessions', formData, {
content: { headers: {
content_type: 'text', ...formData.getHeaders(),
parts: [$.step.parameters.input], },
},
},
],
}); });
$.setActionItem({ const sessionId = sessionResponse.data.id;
raw: response.data,
}); let chatGenerated = false;
while (!chatGenerated) {
const response = await $.http.get(`/api/v1/sessions/${sessionId}`);
const message =
response.data.interactions[response.data.interactions.length - 1];
if (message.creator === 'system' && message.state === 'complete') {
$.setActionItem({
raw: message,
});
chatGenerated = true;
}
}
}, },
}); });

View File

@@ -94,8 +94,6 @@ const appConfig = {
disableFavicon: process.env.DISABLE_FAVICON === 'true', disableFavicon: process.env.DISABLE_FAVICON === 'true',
additionalDrawerLink: process.env.ADDITIONAL_DRAWER_LINK, additionalDrawerLink: process.env.ADDITIONAL_DRAWER_LINK,
additionalDrawerLinkText: process.env.ADDITIONAL_DRAWER_LINK_TEXT, additionalDrawerLinkText: process.env.ADDITIONAL_DRAWER_LINK_TEXT,
seedUserEmail: process.env.SEED_USER_EMAIL || 'user@automatisch.io',
seedUserPassword: process.env.SEED_USER_PASSWORD || 'sample',
}; };
if (!appConfig.encryptionKey) { if (!appConfig.encryptionKey) {

View File

@@ -1,10 +0,0 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import AppAuthClient from '../../../../../models/app-auth-client.js';
export default async (request, response) => {
const appAuthClient = await AppAuthClient.query()
.findById(request.params.appAuthClientId)
.throwIfNotFound();
renderObject(response, appAuthClient);
};

View File

@@ -1,52 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../../test/factories/user.js';
import getAdminAppAuthClientMock from '../../../../../../test/mocks/rest/api/v1/admin/get-app-auth-client.js';
import { createAppAuthClient } from '../../../../../../test/factories/app-auth-client.js';
import { createRole } from '../../../../../../test/factories/role.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/app-auth-clients/:appAuthClientId', () => {
let currentUser, currentUserRole, currentAppAuthClient, token;
describe('with valid license key', () => {
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
currentUserRole = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: currentUserRole.id });
currentAppAuthClient = await createAppAuthClient();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return specified app auth client info', async () => {
const response = await request(app)
.get(`/api/v1/admin/app-auth-clients/${currentAppAuthClient.id}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = getAdminAppAuthClientMock(currentAppAuthClient);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing app auth client UUID', async () => {
const notExistingAppAuthClientUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/admin/app-auth-clients/${notExistingAppAuthClientUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await request(app)
.get('/api/v1/admin/app-auth-clients/invalidAppAuthClientUUID')
.set('Authorization', token)
.expect(400);
});
});
});

View File

@@ -1,6 +1,5 @@
import { vi, describe, it, expect, beforeEach } from 'vitest'; import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest'; import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../../app.js'; import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js'; import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createRole } from '../../../../../../test/factories/role.js'; import { createRole } from '../../../../../../test/factories/role.js';
@@ -21,7 +20,7 @@ describe('GET /api/v1/admin/roles/:roleId', () => {
token = createAuthTokenByUserId(currentUser.id); token = createAuthTokenByUserId(currentUser.id);
}); });
it('should return role', async () => { it('should return roles', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const response = await request(app) const response = await request(app)
@@ -36,24 +35,4 @@ describe('GET /api/v1/admin/roles/:roleId', () => {
expect(response.body).toEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for not existing role UUID', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const notExistingRoleUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/admin/roles/${notExistingRoleUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
await request(app)
.get('/api/v1/admin/roles/invalidRoleUUID')
.set('Authorization', token)
.expect(400);
});
}); });

View File

@@ -1,6 +1,5 @@
import { vi, describe, it, expect, beforeEach } from 'vitest'; import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest'; import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../../app.js'; import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js'; import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createRole } from '../../../../../../test/factories/role.js'; import { createRole } from '../../../../../../test/factories/role.js';
@@ -32,26 +31,4 @@ describe('GET /api/v1/admin/saml-auth-provider/:samlAuthProviderId', () => {
expect(response.body).toEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
it('should return not found response for not existing saml auth provider UUID', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const notExistingSamlAuthProviderUUID = Crypto.randomUUID();
await request(app)
.get(
`/api/v1/admin/saml-auth-providers/${notExistingSamlAuthProviderUUID}`
)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
await request(app)
.get('/api/v1/admin/saml-auth-providers/invalidSamlAuthProviderUUID')
.set('Authorization', token)
.expect(400);
});
}); });

View File

@@ -1,55 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../../test/factories/user';
import { createRole } from '../../../../../../test/factories/role';
import getUserMock from '../../../../../../test/mocks/rest/api/v1/admin/users/get-user.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/users/:userId', () => {
let currentUser, currentUserRole, anotherUser, anotherUserRole, token;
beforeEach(async () => {
currentUserRole = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: currentUserRole.id });
anotherUser = await createUser();
anotherUserRole = await anotherUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id);
});
it('should return specified user info', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const response = await request(app)
.get(`/api/v1/admin/users/${anotherUser.id}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = getUserMock(anotherUser, anotherUserRole);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing user UUID', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const notExistingUserUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/admin/users/${notExistingUserUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
await request(app)
.get('/api/v1/admin/users/invalidUserUUID')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,11 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
import AppAuthClient from '../../../../models/app-auth-client.js';
export default async (request, response) => {
const appAuthClient = await AppAuthClient.query()
.findById(request.params.appAuthClientId)
.where({ active: true })
.throwIfNotFound();
renderObject(response, appAuthClient);
};

View File

@@ -1,48 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import getAppAuthClientMock from '../../../../../test/mocks/rest/api/v1/admin/get-app-auth-client.js';
import { createAppAuthClient } from '../../../../../test/factories/app-auth-client.js';
import * as license from '../../../../helpers/license.ee.js';
describe('GET /api/v1/app-auth-clients/:id', () => {
let currentUser, currentAppAuthClient, token;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
currentUser = await createUser();
currentAppAuthClient = await createAppAuthClient();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return specified app auth client info', async () => {
const response = await request(app)
.get(`/api/v1/app-auth-clients/${currentAppAuthClient.id}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = getAppAuthClientMock(currentAppAuthClient);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing app auth client ID', async () => {
const notExistingAppAuthClientUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/app-auth-clients/${notExistingAppAuthClientUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await request(app)
.get('/api/v1/app-auth-clients/invalidAppAuthClientUUID')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,11 +0,0 @@
import App from '../../../../models/app.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const substeps = await App.findActionSubsteps(
request.params.appKey,
request.params.actionKey
);
renderObject(response, substeps);
};

View File

@@ -1,52 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import App from '../../../../models/app';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import getActionSubstepsMock from '../../../../../test/mocks/rest/api/v1/apps/get-action-substeps.js';
describe('GET /api/v1/apps/:appKey/actions/:actionKey/substeps', () => {
let currentUser, exampleApp, token;
beforeEach(async () => {
currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
exampleApp = await App.findOneByKey('github');
});
it('should return the app auth info', async () => {
const actions = await App.findActionsByKey('github');
const exampleAction = actions.find(
(action) => action.key === 'createIssue'
);
const endpointUrl = `/api/v1/apps/${exampleApp.key}/actions/${exampleAction.key}/substeps`;
const response = await request(app)
.get(endpointUrl)
.set('Authorization', token)
.expect(200);
const expectedPayload = getActionSubstepsMock(exampleAction.substeps);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for invalid app key', async () => {
await request(app)
.get('/api/v1/apps/invalid-app-key/actions/invalid-actions-key/substeps')
.set('Authorization', token)
.expect(404);
});
it('should return empty array for invalid action key', async () => {
const endpointUrl = `/api/v1/apps/${exampleApp.key}/actions/invalid-action-key/substeps`;
const response = await request(app)
.get(endpointUrl)
.set('Authorization', token)
.expect(200);
expect(response.body.data).toEqual([]);
});
});

View File

@@ -1,8 +0,0 @@
import App from '../../../../models/app.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const actions = await App.findActionsByKey(request.params.appKey);
renderObject(response, actions, { serializer: 'Action' });
};

View File

@@ -1,35 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import App from '../../../../models/app';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import getActionsMock from '../../../../../test/mocks/rest/api/v1/apps/get-actions.js';
describe('GET /api/v1/apps/:appKey/actions', () => {
let currentUser, token;
beforeEach(async () => {
currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the app actions', async () => {
const exampleApp = await App.findOneByKey('github');
const response = await request(app)
.get(`/api/v1/apps/${exampleApp.key}/actions`)
.set('Authorization', token)
.expect(200);
const expectedPayload = getActionsMock(exampleApp.actions);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for invalid app key', async () => {
await request(app)
.get('/api/v1/apps/invalid-app-key/actions')
.set('Authorization', token)
.expect(404);
});
});

View File

@@ -1,8 +0,0 @@
import App from '../../../../models/app.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const app = await App.findOneByKey(request.params.appKey);
renderObject(response, app, { serializer: 'App' });
};

View File

@@ -1,35 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import App from '../../../../models/app';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import getAppMock from '../../../../../test/mocks/rest/api/v1/apps/get-app.js';
describe('GET /api/v1/apps/:appKey', () => {
let currentUser, token;
beforeEach(async () => {
currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the app info', async () => {
const exampleApp = await App.findOneByKey('github');
const response = await request(app)
.get(`/api/v1/apps/${exampleApp.key}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = getAppMock(exampleApp);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for invalid app key', async () => {
await request(app)
.get('/api/v1/apps/invalid-app-key')
.set('Authorization', token)
.expect(404);
});
});

View File

@@ -1,16 +0,0 @@
import App from '../../../../models/app.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
let apps = await App.findAll(request.query.name);
if (request.query.onlyWithTriggers) {
apps = apps.filter((app) => app.triggers?.length);
}
if (request.query.onlyWithActions) {
apps = apps.filter((app) => app.actions?.length);
}
renderObject(response, apps, { serializer: 'App' });
};

View File

@@ -1,63 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import App from '../../../../models/app';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import getAppsMock from '../../../../../test/mocks/rest/api/v1/apps/get-apps.js';
describe('GET /api/v1/apps', () => {
let currentUser, apps, token;
beforeEach(async () => {
currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
apps = await App.findAll();
});
it('should return all apps', async () => {
const response = await request(app)
.get('/api/v1/apps')
.set('Authorization', token)
.expect(200);
const expectedPayload = getAppsMock(apps);
expect(response.body).toEqual(expectedPayload);
});
it('should return all apps filtered by name', async () => {
const appsWithNameGit = apps.filter((app) => app.name.includes('Git'));
const response = await request(app)
.get('/api/v1/apps?name=Git')
.set('Authorization', token)
.expect(200);
const expectedPayload = getAppsMock(appsWithNameGit);
expect(response.body).toEqual(expectedPayload);
});
it('should return only the apps with triggers', async () => {
const appsWithTriggers = apps.filter((app) => app.triggers?.length > 0);
const response = await request(app)
.get('/api/v1/apps?onlyWithTriggers=true')
.set('Authorization', token)
.expect(200);
const expectedPayload = getAppsMock(appsWithTriggers);
expect(response.body).toEqual(expectedPayload);
});
it('should return only the apps with actions', async () => {
const appsWithActions = apps.filter((app) => app.actions?.length > 0);
const response = await request(app)
.get('/api/v1/apps?onlyWithActions=true')
.set('Authorization', token)
.expect(200);
const expectedPayload = getAppsMock(appsWithActions);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -1,8 +0,0 @@
import App from '../../../../models/app.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const auth = await App.findAuthByKey(request.params.appKey);
renderObject(response, auth, { serializer: 'Auth' });
};

View File

@@ -1,35 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import App from '../../../../models/app';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import getAuthMock from '../../../../../test/mocks/rest/api/v1/apps/get-auth.js';
describe('GET /api/v1/apps/:appKey/auth', () => {
let currentUser, token;
beforeEach(async () => {
currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the app auth info', async () => {
const exampleApp = await App.findOneByKey('github');
const response = await request(app)
.get(`/api/v1/apps/${exampleApp.key}/auth`)
.set('Authorization', token)
.expect(200);
const expectedPayload = getAuthMock(exampleApp.auth);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for invalid app key', async () => {
await request(app)
.get('/api/v1/apps/invalid-app-key/auth')
.set('Authorization', token)
.expect(404);
});
});

View File

@@ -1,11 +0,0 @@
import App from '../../../../models/app.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const substeps = await App.findTriggerSubsteps(
request.params.appKey,
request.params.triggerKey
);
renderObject(response, substeps);
};

View File

@@ -1,52 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import App from '../../../../models/app';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import getTriggerSubstepsMock from '../../../../../test/mocks/rest/api/v1/apps/get-trigger-substeps.js';
describe('GET /api/v1/apps/:appKey/triggers/:triggerKey/substeps', () => {
let currentUser, exampleApp, token;
beforeEach(async () => {
currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
exampleApp = await App.findOneByKey('github');
});
it('should return the app auth info', async () => {
const triggers = await App.findTriggersByKey('github');
const exampleTrigger = triggers.find(
(trigger) => trigger.key === 'newIssues'
);
const endpointUrl = `/api/v1/apps/${exampleApp.key}/triggers/${exampleTrigger.key}/substeps`;
const response = await request(app)
.get(endpointUrl)
.set('Authorization', token)
.expect(200);
const expectedPayload = getTriggerSubstepsMock(exampleTrigger.substeps);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for invalid app key', async () => {
await request(app)
.get('/api/v1/apps/invalid-app-key/triggers/invalid-trigger-key/substeps')
.set('Authorization', token)
.expect(404);
});
it('should return empty array for invalid trigger key', async () => {
const endpointUrl = `/api/v1/apps/${exampleApp.key}/triggers/invalid-trigger-key/substeps`;
const response = await request(app)
.get(endpointUrl)
.set('Authorization', token)
.expect(200);
expect(response.body.data).toEqual([]);
});
});

View File

@@ -1,8 +0,0 @@
import App from '../../../../models/app.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const triggers = await App.findTriggersByKey(request.params.appKey);
renderObject(response, triggers, { serializer: 'Trigger' });
};

View File

@@ -1,35 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import App from '../../../../models/app';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import getTriggersMock from '../../../../../test/mocks/rest/api/v1/apps/get-triggers.js';
describe('GET /api/v1/apps/:appKey/triggers', () => {
let currentUser, token;
beforeEach(async () => {
currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the app triggers', async () => {
const exampleApp = await App.findOneByKey('github');
const response = await request(app)
.get(`/api/v1/apps/${exampleApp.key}/triggers`)
.set('Authorization', token)
.expect(200);
const expectedPayload = getTriggersMock(exampleApp.triggers);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for invalid app key', async () => {
await request(app)
.get('/api/v1/apps/invalid-app-key/triggers')
.set('Authorization', token)
.expect(404);
});
});

View File

@@ -1,13 +0,0 @@
import appConfig from '../../../../config/app.js';
import { hasValidLicense } from '../../../../helpers/license.ee.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const info = {
isCloud: appConfig.isCloud,
isMation: appConfig.isMation,
isEnterprise: await hasValidLicense(),
};
renderObject(response, info);
};

View File

@@ -1,22 +0,0 @@
import { vi, expect, describe, it } from 'vitest';
import request from 'supertest';
import appConfig from '../../../../config/app.js';
import app from '../../../../app.js';
import infoMock from '../../../../../test/mocks/rest/api/v1/automatisch/info.js';
import * as license from '../../../../helpers/license.ee.js';
describe('GET /api/v1/automatisch/info', () => {
it('should return Automatisch info', async () => {
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false);
vi.spyOn(appConfig, 'isMation', 'get').mockReturnValue(false);
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const response = await request(app)
.get('/api/v1/automatisch/info')
.expect(200);
const expectedPayload = infoMock();
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -1,15 +0,0 @@
import { getLicense } from '../../../../helpers/license.ee.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const license = await getLicense();
const computedLicense = {
id: license ? license.id : null,
name: license ? license.name : null,
expireAt: license ? license.expireAt : null,
verified: license ? true : false,
};
renderObject(response, computedLicense);
};

View File

@@ -1,23 +0,0 @@
import { vi, expect, describe, it } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import licenseMock from '../../../../../test/mocks/rest/api/v1/automatisch/license.js';
import * as license from '../../../../helpers/license.ee.js';
describe('GET /api/v1/automatisch/license', () => {
it('should return Automatisch license info', async () => {
vi.spyOn(license, 'getLicense').mockResolvedValue({
id: '123',
name: 'license-name',
expireAt: '2025-12-31T23:59:59Z',
});
const response = await request(app)
.get('/api/v1/automatisch/license')
.expect(200);
const expectedPayload = licenseMock();
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -1,23 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
import paginateRest from '../../../../helpers/pagination-rest.js';
export default async (request, response) => {
const execution = await request.currentUser.authorizedExecutions
.clone()
.withSoftDeleted()
.findById(request.params.executionId)
.throwIfNotFound();
const executionStepsQuery = execution
.$relatedQuery('executionSteps')
.withSoftDeleted()
.withGraphFetched('step')
.orderBy('created_at', 'asc');
const executionSteps = await paginateRest(
executionStepsQuery,
request.query.page
);
renderObject(response, executionSteps);
};

View File

@@ -1,153 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import { createFlow } from '../../../../../test/factories/flow.js';
import { createStep } from '../../../../../test/factories/step.js';
import { createExecution } from '../../../../../test/factories/execution.js';
import { createExecutionStep } from '../../../../../test/factories/execution-step.js';
import { createPermission } from '../../../../../test/factories/permission';
import getExecutionStepsMock from '../../../../../test/mocks/rest/api/v1/executions/get-execution-steps';
describe('GET /api/v1/executions/:executionId/execution-steps', () => {
let currentUser, currentUserRole, anotherUser, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
anotherUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the execution steps of current user execution', async () => {
const currentUserFlow = await createFlow({
userId: currentUser.id,
});
const stepOne = await createStep({
flowId: currentUserFlow.id,
type: 'trigger',
});
const stepTwo = await createStep({
flowId: currentUserFlow.id,
type: 'action',
});
const currentUserExecution = await createExecution({
flowId: currentUserFlow.id,
});
const currentUserExecutionStepOne = await createExecutionStep({
executionId: currentUserExecution.id,
stepId: stepOne.id,
});
const currentUserExecutionStepTwo = await createExecutionStep({
executionId: currentUserExecution.id,
stepId: stepTwo.id,
});
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.get(`/api/v1/executions/${currentUserExecution.id}/execution-steps`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getExecutionStepsMock(
[currentUserExecutionStepOne, currentUserExecutionStepTwo],
[stepOne, stepTwo]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return the execution steps of another user execution', async () => {
const anotherUserFlow = await createFlow({
userId: anotherUser.id,
});
const stepOne = await createStep({
flowId: anotherUserFlow.id,
type: 'trigger',
});
const stepTwo = await createStep({
flowId: anotherUserFlow.id,
type: 'action',
});
const anotherUserExecution = await createExecution({
flowId: anotherUserFlow.id,
});
const anotherUserExecutionStepOne = await createExecutionStep({
executionId: anotherUserExecution.id,
stepId: stepOne.id,
});
const anotherUserExecutionStepTwo = await createExecutionStep({
executionId: anotherUserExecution.id,
stepId: stepTwo.id,
});
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.get(`/api/v1/executions/${anotherUserExecution.id}/execution-steps`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getExecutionStepsMock(
[anotherUserExecutionStepOne, anotherUserExecutionStepTwo],
[stepOne, stepTwo]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing execution step UUID', async () => {
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: [],
});
const notExistingExcecutionUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/executions/${notExistingExcecutionUUID}/execution-steps`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: [],
});
await request(app)
.get('/api/v1/executions/invalidExecutionUUID/execution-steps')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,15 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const execution = await request.currentUser.authorizedExecutions
.withGraphFetched({
flow: {
steps: true,
},
})
.withSoftDeleted()
.findById(request.params.executionId)
.throwIfNotFound();
renderObject(response, execution);
};

View File

@@ -1,134 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import { createFlow } from '../../../../../test/factories/flow.js';
import { createStep } from '../../../../../test/factories/step.js';
import { createExecution } from '../../../../../test/factories/execution.js';
import { createPermission } from '../../../../../test/factories/permission';
import getExecutionMock from '../../../../../test/mocks/rest/api/v1/executions/get-execution';
describe('GET /api/v1/executions/:executionId', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the execution data of current user', async () => {
const currentUserFlow = await createFlow({
userId: currentUser.id,
});
const stepOne = await createStep({
flowId: currentUserFlow.id,
type: 'trigger',
});
const stepTwo = await createStep({
flowId: currentUserFlow.id,
type: 'action',
});
const currentUserExecution = await createExecution({
flowId: currentUserFlow.id,
});
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.get(`/api/v1/executions/${currentUserExecution.id}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getExecutionMock(
currentUserExecution,
currentUserFlow,
[stepOne, stepTwo]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return the execution data of another user', async () => {
const anotherUser = await createUser();
const anotherUserFlow = await createFlow({
userId: anotherUser.id,
});
const stepOne = await createStep({
flowId: anotherUserFlow.id,
type: 'trigger',
});
const stepTwo = await createStep({
flowId: anotherUserFlow.id,
type: 'action',
});
const anotherUserExecution = await createExecution({
flowId: anotherUserFlow.id,
});
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.get(`/api/v1/executions/${anotherUserExecution.id}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getExecutionMock(
anotherUserExecution,
anotherUserFlow,
[stepOne, stepTwo]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing execution UUID', async () => {
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: [],
});
const notExistingExcecutionUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/executions/${notExistingExcecutionUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: [],
});
await request(app)
.get('/api/v1/executions/invalidExecutionUUID')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,26 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
import paginateRest from '../../../../helpers/pagination-rest.js';
export default async (request, response) => {
const executionsQuery = request.currentUser.authorizedExecutions
.withSoftDeleted()
.orderBy('created_at', 'desc')
.withGraphFetched({
flow: {
steps: true,
},
});
const executions = await paginateRest(executionsQuery, request.query.page);
for (const execution of executions.records) {
const executionSteps = await execution.$relatedQuery('executionSteps');
const status = executionSteps.some((step) => step.status === 'failure')
? 'failure'
: 'success';
execution.status = status;
}
renderObject(response, executions);
};

View File

@@ -1,113 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import { createFlow } from '../../../../../test/factories/flow.js';
import { createStep } from '../../../../../test/factories/step.js';
import { createExecution } from '../../../../../test/factories/execution.js';
import { createPermission } from '../../../../../test/factories/permission';
import getExecutionsMock from '../../../../../test/mocks/rest/api/v1/executions/get-executions';
describe('GET /api/v1/executions', () => {
let currentUser, currentUserRole, anotherUser, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
anotherUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the executions of current user', async () => {
const currentUserFlow = await createFlow({
userId: currentUser.id,
});
const stepOne = await createStep({
flowId: currentUserFlow.id,
type: 'trigger',
});
const stepTwo = await createStep({
flowId: currentUserFlow.id,
type: 'action',
});
const currentUserExecutionOne = await createExecution({
flowId: currentUserFlow.id,
});
const currentUserExecutionTwo = await createExecution({
flowId: currentUserFlow.id,
deletedAt: new Date().toISOString(),
});
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.get('/api/v1/executions')
.set('Authorization', token)
.expect(200);
const expectedPayload = await getExecutionsMock(
[currentUserExecutionTwo, currentUserExecutionOne],
currentUserFlow,
[stepOne, stepTwo]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return the executions of another user', async () => {
const anotherUserFlow = await createFlow({
userId: anotherUser.id,
});
const stepOne = await createStep({
flowId: anotherUserFlow.id,
type: 'trigger',
});
const stepTwo = await createStep({
flowId: anotherUserFlow.id,
type: 'action',
});
const anotherUserExecutionOne = await createExecution({
flowId: anotherUserFlow.id,
});
const anotherUserExecutionTwo = await createExecution({
flowId: anotherUserFlow.id,
deletedAt: new Date().toISOString(),
});
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.get('/api/v1/executions')
.set('Authorization', token)
.expect(200);
const expectedPayload = await getExecutionsMock(
[anotherUserExecutionTwo, anotherUserExecutionOne],
anotherUserFlow,
[stepOne, stepTwo]
);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -1,11 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const flow = await request.currentUser.authorizedFlows
.withGraphJoined({ steps: true })
.orderBy('steps.position', 'asc')
.findOne({ 'flows.id': request.params.flowId })
.throwIfNotFound();
renderObject(response, flow);
};

View File

@@ -1,102 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import { createFlow } from '../../../../../test/factories/flow';
import { createStep } from '../../../../../test/factories/step';
import { createPermission } from '../../../../../test/factories/permission';
import getFlowMock from '../../../../../test/mocks/rest/api/v1/flows/get-flow';
describe('GET /api/v1/flows/:flowId', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the flow data of current user', async () => {
const currentUserflow = await createFlow({ userId: currentUser.id });
const triggerStep = await createStep({ flowId: currentUserflow.id });
const actionStep = await createStep({ flowId: currentUserflow.id });
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.get(`/api/v1/flows/${currentUserflow.id}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getFlowMock(currentUserflow, [
triggerStep,
actionStep,
]);
expect(response.body).toEqual(expectedPayload);
});
it('should return the flow data of another user', async () => {
const anotherUser = await createUser();
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
const triggerStep = await createStep({ flowId: anotherUserFlow.id });
const actionStep = await createStep({ flowId: anotherUserFlow.id });
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.get(`/api/v1/flows/${anotherUserFlow.id}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getFlowMock(anotherUserFlow, [
triggerStep,
actionStep,
]);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing flow UUID', async () => {
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const notExistingFlowUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/flows/${notExistingFlowUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await request(app)
.get('/api/v1/flows/invalidFlowUUID')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -2,29 +2,15 @@ import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest'; import request from 'supertest';
import app from '../../../../app.js'; import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id'; import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createPermission } from '../../../../../test/factories/permission';
import { createRole } from '../../../../../test/factories/role';
import { createUser } from '../../../../../test/factories/user'; import { createUser } from '../../../../../test/factories/user';
import getCurrentUserMock from '../../../../../test/mocks/rest/api/v1/users/get-current-user'; import getCurrentUserMock from '../../../../../test/mocks/rest/api/v1/users/get-current-user';
describe('GET /api/v1/users/me', () => { describe('GET /api/v1/users/me', () => {
let role, permissionOne, permissionTwo, currentUser, token; let role, currentUser, token;
beforeEach(async () => { beforeEach(async () => {
role = await createRole(); currentUser = await createUser();
role = await currentUser.$relatedQuery('role');
permissionOne = await createPermission({
roleId: role.id,
});
permissionTwo = await createPermission({
roleId: role.id,
});
currentUser = await createUser({
roleId: role.id,
});
token = createAuthTokenByUserId(currentUser.id); token = createAuthTokenByUserId(currentUser.id);
}); });
@@ -34,11 +20,7 @@ describe('GET /api/v1/users/me', () => {
.set('Authorization', token) .set('Authorization', token)
.expect(200); .expect(200);
const expectedPayload = getCurrentUserMock(currentUser, role, [ const expectedPayload = getCurrentUserMock(currentUser, role);
permissionOne,
permissionTwo,
]);
expect(response.body).toEqual(expectedPayload); expect(response.body).toEqual(expectedPayload);
}); });
}); });

View File

@@ -1,7 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const invoices = await request.currentUser.getInvoices();
renderObject(response, invoices);
};

View File

@@ -1,34 +0,0 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import User from '../../../../models/user';
import getInvoicesMock from '../../../../../test/mocks/rest/api/v1/users/get-invoices.ee';
describe('GET /api/v1/user/invoices', () => {
let currentUser, token;
beforeEach(async () => {
currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return current user invoices', async () => {
const invoices = [
{ id: 1, amount: 100, description: 'Invoice 1' },
{ id: 2, amount: 200, description: 'Invoice 2' },
];
vi.spyOn(User.prototype, 'getInvoices').mockResolvedValue(invoices);
const response = await request(app)
.get('/api/v1/users/invoices')
.set('Authorization', token)
.expect(200);
const expectedPayload = await getInvoicesMock(invoices);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -1,8 +1,11 @@
import { renderObject } from '../../../../../helpers/renderer.js'; import { renderObject } from '../../../../helpers/renderer.js';
import User from '../../../../../models/user.js'; import User from '../../../../models/user.js';
export default async (request, response) => { export default async (request, response) => {
const user = await User.query() const user = await User.query()
.leftJoinRelated({
role: true,
})
.withGraphFetched({ .withGraphFetched({
role: true, role: true,
}) })

View File

@@ -0,0 +1,36 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import { createPermission } from '../../../../../test/factories/permission';
import getUserMock from '../../../../../test/mocks/rest/api/v1/users/get-user';
describe('GET /api/v1/users/:userId', () => {
let currentUser, currentUserRole, anotherUser, anotherUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
anotherUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
anotherUserRole = await anotherUser.$relatedQuery('role');
await createPermission({
roleId: currentUserRole.id,
action: 'read',
subject: 'User',
});
token = createAuthTokenByUserId(currentUser.id);
});
it('should return specified user info', async () => {
const response = await request(app)
.get(`/api/v1/users/${anotherUser.id}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = getUserMock(anotherUser, anotherUserRole);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -1,9 +1,12 @@
import { renderObject } from '../../../../../helpers/renderer.js'; import { renderObject } from '../../../../helpers/renderer.js';
import User from '../../../../../models/user.js'; import User from '../../../../models/user.js';
import paginateRest from '../../../../../helpers/pagination-rest.js'; import paginateRest from '../../../../helpers/pagination-rest.js';
export default async (request, response) => { export default async (request, response) => {
const usersQuery = User.query() const usersQuery = User.query()
.leftJoinRelated({
role: true,
})
.withGraphFetched({ .withGraphFetched({
role: true, role: true,
}) })

View File

@@ -1,17 +1,26 @@
import { vi, describe, it, expect, beforeEach } from 'vitest'; import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest'; import request from 'supertest';
import app from '../../../../../app'; import app from '../../../../app';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id'; import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createRole } from '../../../../../../test/factories/role'; import { createRole } from '../../../../../test/factories/role';
import { createUser } from '../../../../../../test/factories/user'; import { createPermission } from '../../../../../test/factories/permission';
import getUsersMock from '../../../../../../test/mocks/rest/api/v1/admin/users/get-users.js'; import { createUser } from '../../../../../test/factories/user';
import * as license from '../../../../../helpers/license.ee.js'; import getUsersMock from '../../../../../test/mocks/rest/api/v1/users/get-users';
describe('GET /api/v1/admin/users', () => { describe('GET /api/v1/users', () => {
let currentUser, currentUserRole, anotherUser, anotherUserRole, token; let currentUser, currentUserRole, anotherUser, anotherUserRole, token;
beforeEach(async () => { beforeEach(async () => {
currentUserRole = await createRole({ key: 'admin' }); currentUserRole = await createRole({
key: 'currentUser',
name: 'Current user role',
});
await createPermission({
action: 'read',
subject: 'User',
roleId: currentUserRole.id,
});
currentUser = await createUser({ currentUser = await createUser({
roleId: currentUserRole.id, roleId: currentUserRole.id,
@@ -32,10 +41,8 @@ describe('GET /api/v1/admin/users', () => {
}); });
it('should return users data', async () => { it('should return users data', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const response = await request(app) const response = await request(app)
.get('/api/v1/admin/users') .get('/api/v1/users')
.set('Authorization', token) .set('Authorization', token)
.expect(200); .expect(200);

View File

@@ -1,16 +0,0 @@
export async function up(knex) {
return knex.schema.createTable('datastore', (table) => {
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
table.string('key').notNullable();
table.string('value');
table.string('scope').notNullable();
table.uuid('scope_id').notNullable();
table.index(['key', 'scope', 'scope_id']);
table.timestamps(true, true);
});
}
export async function down(knex) {
return knex.schema.dropTable('datastore');
}

View File

@@ -1,13 +1,8 @@
import Step from '../../models/flow.js';
const deleteStep = async (_parent, params, context) => { const deleteStep = async (_parent, params, context) => {
const conditions = context.currentUser.can('update', 'Flow'); context.currentUser.can('update', 'Flow');
const isCreator = conditions.isCreator;
const allSteps = Step.query();
const userSteps = context.currentUser.$relatedQuery('steps');
const baseQuery = isCreator ? userSteps : allSteps;
const step = await baseQuery const step = await context.currentUser
.$relatedQuery('steps')
.withGraphFetched('flow') .withGraphFetched('flow')
.findOne({ .findOne({
'steps.id': params.input.id, 'steps.id': params.input.id,

View File

@@ -0,0 +1,21 @@
import appConfig from '../../config/app.js';
import { getLicense } from '../../helpers/license.ee.js';
const getAutomatischInfo = async () => {
const license = await getLicense();
const computedLicense = {
id: license ? license.id : null,
name: license ? license.name : null,
expireAt: license ? license.expireAt : null,
verified: license ? true : false,
};
return {
isCloud: appConfig.isCloud,
isMation: appConfig.isMation,
license: computedLicense,
};
};
export default getAutomatischInfo;

View File

@@ -0,0 +1,190 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../app';
import * as license from '../../helpers/license.ee';
import appConfig from '../../config/app';
describe('graphQL getAutomatischInfo query', () => {
const query = `
query {
getAutomatischInfo {
isCloud
isMation
license {
id
name
expireAt
verified
}
}
}
`;
describe('and without valid license', () => {
beforeEach(async () => {
vi.spyOn(license, 'getLicense').mockResolvedValue(false);
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false);
vi.spyOn(appConfig, 'isMation', 'get').mockReturnValue(false);
});
it('should return empty license data', async () => {
const response = await request(app)
.post('/graphql')
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getAutomatischInfo: {
isCloud: false,
isMation: false,
license: {
id: null,
name: null,
expireAt: null,
verified: false,
},
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
describe('and with valid license', () => {
beforeEach(async () => {
const mockedLicense = {
id: '123123',
name: 'Test License',
expireAt: '2025-08-09T10:56:54.144Z',
verified: true,
};
vi.spyOn(license, 'getLicense').mockResolvedValue(mockedLicense);
});
describe('and with cloud flag enabled', () => {
beforeEach(async () => {
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
});
it('should return all license data', async () => {
const response = await request(app)
.post('/graphql')
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getAutomatischInfo: {
isCloud: true,
isMation: false,
license: {
expireAt: '2025-08-09T10:56:54.144Z',
id: '123123',
name: 'Test License',
verified: true,
},
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
describe('and with cloud flag disabled', () => {
beforeEach(async () => {
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false);
});
it('should return all license data', async () => {
const response = await request(app)
.post('/graphql')
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getAutomatischInfo: {
isCloud: false,
isMation: false,
license: {
expireAt: '2025-08-09T10:56:54.144Z',
id: '123123',
name: 'Test License',
verified: true,
},
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
describe('and with mation flag enabled', () => {
beforeEach(async () => {
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false);
vi.spyOn(appConfig, 'isMation', 'get').mockReturnValue(true);
});
it('should return all license data', async () => {
const response = await request(app)
.post('/graphql')
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getAutomatischInfo: {
isCloud: false,
isMation: true,
license: {
expireAt: '2025-08-09T10:56:54.144Z',
id: '123123',
name: 'Test License',
verified: true,
},
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
describe('and with mation flag disabled', () => {
beforeEach(async () => {
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false);
vi.spyOn(appConfig, 'isMation', 'get').mockReturnValue(false);
});
it('should return all license data', async () => {
const response = await request(app)
.post('/graphql')
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getAutomatischInfo: {
isMation: false,
isCloud: false,
license: {
expireAt: '2025-08-09T10:56:54.144Z',
id: '123123',
name: 'Test License',
verified: true,
},
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
});
});

View File

@@ -3,6 +3,7 @@ import getAppAuthClient from './queries/get-app-auth-client.ee.js';
import getAppAuthClients from './queries/get-app-auth-clients.ee.js'; import getAppAuthClients from './queries/get-app-auth-clients.ee.js';
import getAppConfig from './queries/get-app-config.ee.js'; import getAppConfig from './queries/get-app-config.ee.js';
import getApps from './queries/get-apps.js'; import getApps from './queries/get-apps.js';
import getAutomatischInfo from './queries/get-automatisch-info.js';
import getBillingAndUsage from './queries/get-billing-and-usage.ee.js'; import getBillingAndUsage from './queries/get-billing-and-usage.ee.js';
import getConfig from './queries/get-config.ee.js'; import getConfig from './queries/get-config.ee.js';
import getConnectedApps from './queries/get-connected-apps.js'; import getConnectedApps from './queries/get-connected-apps.js';
@@ -38,6 +39,7 @@ const queryResolvers = {
getAppAuthClients, getAppAuthClients,
getAppConfig, getAppConfig,
getApps, getApps,
getAutomatischInfo,
getBillingAndUsage, getBillingAndUsage,
getConfig, getConfig,
getConnectedApps, getConnectedApps,

View File

@@ -40,6 +40,7 @@ type Query {
key: String! key: String!
parameters: JSONObject parameters: JSONObject
): [SubstepArgument] ): [SubstepArgument]
getAutomatischInfo: GetAutomatischInfo
getBillingAndUsage: GetBillingAndUsage getBillingAndUsage: GetBillingAndUsage
getCurrentUser: User getCurrentUser: User
getConfig(keys: [String]): JSONObject getConfig(keys: [String]): JSONObject
@@ -643,6 +644,12 @@ type AppHealth {
version: String version: String
} }
type GetAutomatischInfo {
isCloud: Boolean
isMation: Boolean
license: License
}
type License { type License {
id: String id: String
name: String name: String

View File

@@ -42,6 +42,7 @@ const isAuthenticatedRule = rule()(isAuthenticated);
export const authenticationRules = { export const authenticationRules = {
Query: { Query: {
'*': isAuthenticatedRule, '*': isAuthenticatedRule,
getAutomatischInfo: allow,
getConfig: allow, getConfig: allow,
getNotifications: allow, getNotifications: allow,
healthcheck: allow, healthcheck: allow,

View File

@@ -7,22 +7,6 @@ const authorizationList = {
action: 'read', action: 'read',
subject: 'User', subject: 'User',
}, },
'GET /api/v1/flows/:flowId': {
action: 'read',
subject: 'Flow',
},
'GET /api/v1/executions/:executionId': {
action: 'read',
subject: 'Execution',
},
'GET /api/v1/executions/': {
action: 'read',
subject: 'Execution',
},
'GET /api/v1/executions/:executionId/execution-steps': {
action: 'read',
subject: 'Execution',
},
}; };
export const authorizeUser = async (request, response, next) => { export const authorizeUser = async (request, response, next) => {

View File

@@ -1,45 +1,14 @@
import logger from './logger.js'; import logger from './logger.js';
import objection from 'objection';
import * as Sentry from './sentry.ee.js';
const { NotFoundError, DataError } = objection;
// Do not remove `next` argument as the function signature will not fit for an error handler middleware // Do not remove `next` argument as the function signature will not fit for an error handler middleware
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const errorHandler = (error, request, response, next) => { const errorHandler = (err, req, res, next) => {
if (error.message === 'Not Found' || error instanceof NotFoundError) { if (err.message === 'Not Found') {
response.status(404).end(); res.status(404).end();
} else {
logger.error(err.message + '\n' + err.stack);
res.status(err.statusCode || 500).send(err.message);
} }
if (notFoundAppError(error)) {
response.status(404).end();
}
if (error instanceof DataError) {
response.status(400).end();
}
const statusCode = error.statusCode || 500;
logger.error(request.method + ' ' + request.url + ' ' + statusCode);
logger.error(error.stack);
Sentry.captureException(error, {
tags: { rest: true },
extra: {
url: request?.url,
method: request?.method,
params: request?.params,
},
});
response.status(statusCode).end();
};
const notFoundAppError = (error) => {
return (
error.message.includes('An application with the') ||
error.message.includes("key couldn't be found.")
);
}; };
export default errorHandler; export default errorHandler;

View File

@@ -1,7 +1,6 @@
import createHttpClient from './http-client/index.js'; import createHttpClient from './http-client/index.js';
import EarlyExitError from '../errors/early-exit.js'; import EarlyExitError from '../errors/early-exit.js';
import AlreadyProcessedError from '../errors/already-processed.js'; import AlreadyProcessedError from '../errors/already-processed.js';
import Datastore from '../models/datastore.js';
const globalVariable = async (options) => { const globalVariable = async (options) => {
const { const {
@@ -89,43 +88,6 @@ const globalVariable = async (options) => {
setActionItem: (actionItem) => { setActionItem: (actionItem) => {
$.actionOutput.data = actionItem; $.actionOutput.data = actionItem;
}, },
datastore: {
get: async ({ key }) => {
const datastore = await Datastore.query().findOne({
key,
scope: 'flow',
scope_id: $.flow.id,
});
return {
key: datastore.key,
value: datastore.value,
[datastore.key]: datastore.value,
};
},
set: async ({ key, value }) => {
let datastore = await Datastore.query()
.where({ key, scope: 'flow', scope_id: $.flow.id })
.first();
if (datastore) {
await datastore.$query().patchAndFetch({ value: value });
} else {
datastore = await Datastore.query().insert({
key,
value,
scope: 'flow',
scopeId: $.flow.id,
});
}
return {
key: datastore.key,
value: datastore.value,
[datastore.key]: datastore.value,
};
},
},
}; };
if (request) { if (request) {

View File

@@ -11,18 +11,15 @@ const isArray = (object) =>
const totalCount = (object) => const totalCount = (object) =>
isPaginated(object) ? object.totalCount : isArray(object) ? object.length : 1; isPaginated(object) ? object.totalCount : isArray(object) ? object.length : 1;
const renderObject = (response, object, options) => { const renderObject = (response, object) => {
let data = isPaginated(object) ? object.records : object; let data = isPaginated(object) ? object.records : object;
const type = isPaginated(object) const type = isPaginated(object)
? object.records[0].constructor.name ? object.records[0].constructor.name
: Array.isArray(object) : Array.isArray(object)
? object?.[0]?.constructor?.name || 'Object' ? object[0].constructor.name
: object.constructor.name; : object.constructor.name;
const serializer = options?.serializer const serializer = serializers[type];
? serializers[options.serializer]
: serializers[type];
if (serializer) { if (serializer) {
data = Array.isArray(data) data = Array.isArray(data)

View File

@@ -3,13 +3,10 @@ import * as Tracing from '@sentry/tracing';
import appConfig from '../config/app.js'; import appConfig from '../config/app.js';
const isSentryEnabled = () => { const isSentryEnabled = !!appConfig.sentryDsn;
if (appConfig.isDev || appConfig.isTest) return false;
return !!appConfig.sentryDsn;
};
export function init(app) { export function init(app) {
if (!isSentryEnabled()) return; if (!isSentryEnabled) return;
return Sentry.init({ return Sentry.init({
enabled: !!appConfig.sentryDsn, enabled: !!appConfig.sentryDsn,
@@ -24,19 +21,19 @@ export function init(app) {
} }
export function attachRequestHandler(app) { export function attachRequestHandler(app) {
if (!isSentryEnabled()) return; if (!isSentryEnabled) return;
app.use(Sentry.Handlers.requestHandler()); app.use(Sentry.Handlers.requestHandler());
} }
export function attachTracingHandler(app) { export function attachTracingHandler(app) {
if (!isSentryEnabled()) return; if (!isSentryEnabled) return;
app.use(Sentry.Handlers.tracingHandler()); app.use(Sentry.Handlers.tracingHandler());
} }
export function attachErrorHandler(app) { export function attachErrorHandler(app) {
if (!isSentryEnabled()) return; if (!isSentryEnabled) return;
app.use( app.use(
Sentry.Handlers.errorHandler({ Sentry.Handlers.errorHandler({
@@ -49,7 +46,7 @@ export function attachErrorHandler(app) {
} }
export function captureException(exception, captureContext) { export function captureException(exception, captureContext) {
if (!isSentryEnabled()) return; if (!isSentryEnabled) return;
return Sentry.captureException(exception, captureContext); return Sentry.captureException(exception, captureContext);
} }

View File

@@ -39,47 +39,6 @@ class App {
return appInfoConverter(rawAppData); return appInfoConverter(rawAppData);
} }
static async findAuthByKey(key, stripFuncs = false) {
const rawAppData = await getApp(key, stripFuncs);
const appData = appInfoConverter(rawAppData);
return appData?.auth || {};
}
static async findTriggersByKey(key, stripFuncs = false) {
const rawAppData = await getApp(key, stripFuncs);
const appData = appInfoConverter(rawAppData);
return appData?.triggers || [];
}
static async findTriggerSubsteps(appKey, triggerKey, stripFuncs = false) {
const rawAppData = await getApp(appKey, stripFuncs);
const appData = appInfoConverter(rawAppData);
const trigger = appData?.triggers?.find(
(trigger) => trigger.key === triggerKey
);
return trigger?.substeps || [];
}
static async findActionsByKey(key, stripFuncs = false) {
const rawAppData = await getApp(key, stripFuncs);
const appData = appInfoConverter(rawAppData);
return appData?.actions || [];
}
static async findActionSubsteps(appKey, actionKey, stripFuncs = false) {
const rawAppData = await getApp(appKey, stripFuncs);
const appData = appInfoConverter(rawAppData);
const action = appData?.actions?.find((action) => action.key === actionKey);
return action?.substeps || [];
}
static async checkAppAndAction(appKey, actionKey) { static async checkAppAndAction(appKey, actionKey) {
const app = await this.findOneByKey(appKey); const app = await this.findOneByKey(appKey);

View File

@@ -1,24 +0,0 @@
import Base from './base.js';
class Datastore extends Base {
static tableName = 'datastore';
static jsonSchema = {
type: 'object',
required: ['key', 'value', 'scope', 'scopeId'],
properties: {
id: { type: 'string', format: 'uuid' },
key: { type: 'string', minLength: 1 },
value: { type: 'string' },
scope: {
type: 'string',
enum: ['flow'],
default: 'flow',
},
scopeId: { type: 'string', format: 'uuid' },
},
};
}
export default Datastore;

View File

@@ -15,7 +15,6 @@ import Role from './role.js';
import Step from './step.js'; import Step from './step.js';
import Subscription from './subscription.ee.js'; import Subscription from './subscription.ee.js';
import UsageData from './usage-data.ee.js'; import UsageData from './usage-data.ee.js';
import Billing from '../helpers/billing/index.ee.js';
class User extends Base { class User extends Base {
static tableName = 'users'; static tableName = 'users';
@@ -144,18 +143,6 @@ class User extends Base {
}, },
}); });
get authorizedFlows() {
const conditions = this.can('read', 'Flow');
return conditions.isCreator ? this.$relatedQuery('flows') : Flow.query();
}
get authorizedExecutions() {
const conditions = this.can('read', 'Execution');
return conditions.isCreator
? this.$relatedQuery('executions')
: Execution.query();
}
login(password) { login(password) {
return bcrypt.compare(password, this.password); return bcrypt.compare(password, this.password);
} }
@@ -250,20 +237,6 @@ class User extends Base {
return currentUsageData.consumedTaskCount < plan.quota; return currentUsageData.consumedTaskCount < plan.quota;
} }
async getInvoices() {
const subscription = await this.$relatedQuery('currentSubscription');
if (!subscription) {
return [];
}
const invoices = await Billing.paddleClient.getInvoices(
Number(subscription.paddleSubscriptionId)
);
return invoices;
}
async $beforeInsert(queryContext) { async $beforeInsert(queryContext) {
await super.$beforeInsert(queryContext); await super.$beforeInsert(queryContext);

View File

@@ -15,17 +15,11 @@ process.on('SIGTERM', async () => {
await actionQueue.close(); await actionQueue.close();
}); });
actionQueue.on('error', (error) => { actionQueue.on('error', (err) => {
if (error.code === CONNECTION_REFUSED) { if (err.code === CONNECTION_REFUSED) {
logger.error( logger.error('Make sure you have installed Redis and it is running.', err);
'Make sure you have installed Redis and it is running.',
error
);
process.exit(); process.exit();
} }
logger.error('Error happened in action queue!', error);
}); });
export default actionQueue; export default actionQueue;

View File

@@ -15,17 +15,11 @@ process.on('SIGTERM', async () => {
await deleteUserQueue.close(); await deleteUserQueue.close();
}); });
deleteUserQueue.on('error', (error) => { deleteUserQueue.on('error', (err) => {
if (error.code === CONNECTION_REFUSED) { if (err.code === CONNECTION_REFUSED) {
logger.error( logger.error('Make sure you have installed Redis and it is running.', err);
'Make sure you have installed Redis and it is running.',
error
);
process.exit(); process.exit();
} }
logger.error('Error happened in delete user queue!', error);
}); });
export default deleteUserQueue; export default deleteUserQueue;

View File

@@ -15,17 +15,11 @@ process.on('SIGTERM', async () => {
await emailQueue.close(); await emailQueue.close();
}); });
emailQueue.on('error', (error) => { emailQueue.on('error', (err) => {
if (error.code === CONNECTION_REFUSED) { if (err.code === CONNECTION_REFUSED) {
logger.error( logger.error('Make sure you have installed Redis and it is running.', err);
'Make sure you have installed Redis and it is running.',
error
);
process.exit(); process.exit();
} }
logger.error('Error happened in email queue!', error);
}); });
export default emailQueue; export default emailQueue;

View File

@@ -15,17 +15,11 @@ process.on('SIGTERM', async () => {
await flowQueue.close(); await flowQueue.close();
}); });
flowQueue.on('error', (error) => { flowQueue.on('error', (err) => {
if (error.code === CONNECTION_REFUSED) { if (err.code === CONNECTION_REFUSED) {
logger.error( logger.error('Make sure you have installed Redis and it is running.', err);
'Make sure you have installed Redis and it is running.',
error
);
process.exit(); process.exit();
} }
logger.error('Error happened in flow queue!', error);
}); });
export default flowQueue; export default flowQueue;

View File

@@ -18,20 +18,11 @@ process.on('SIGTERM', async () => {
await removeCancelledSubscriptionsQueue.close(); await removeCancelledSubscriptionsQueue.close();
}); });
removeCancelledSubscriptionsQueue.on('error', (error) => { removeCancelledSubscriptionsQueue.on('error', (err) => {
if (error.code === CONNECTION_REFUSED) { if (err.code === CONNECTION_REFUSED) {
logger.error( logger.error('Make sure you have installed Redis and it is running.', err);
'Make sure you have installed Redis and it is running.',
error
);
process.exit(); process.exit();
} }
logger.error(
'Error happened in remove cancelled subscriptions queue!',
error
);
}); });
removeCancelledSubscriptionsQueue.add('remove-cancelled-subscriptions', null, { removeCancelledSubscriptionsQueue.add('remove-cancelled-subscriptions', null, {

View File

@@ -15,17 +15,11 @@ process.on('SIGTERM', async () => {
await triggerQueue.close(); await triggerQueue.close();
}); });
triggerQueue.on('error', (error) => { triggerQueue.on('error', (err) => {
if (error.code === CONNECTION_REFUSED) { if (err.code === CONNECTION_REFUSED) {
logger.error( logger.error('Make sure you have installed Redis and it is running.', err);
'Make sure you have installed Redis and it is running.',
error
);
process.exit(); process.exit();
} }
logger.error('Error happened in trigger queue!', error);
}); });
export default triggerQueue; export default triggerQueue;

View File

@@ -1,18 +0,0 @@
import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../../helpers/authentication.js';
import { authorizeAdmin } from '../../../../helpers/authorization.js';
import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js';
import getAdminAppAuthClientsAction from '../../../../controllers/api/v1/admin/app-auth-clients/get-app-auth-client.js';
const router = Router();
router.get(
'/:appAuthClientId',
authenticateUser,
authorizeAdmin,
checkIsEnterprise,
asyncHandler(getAdminAppAuthClientsAction)
);
export default router;

View File

@@ -1,5 +1,4 @@
import { Router } from 'express'; import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../../helpers/authentication.js'; import { authenticateUser } from '../../../../helpers/authentication.js';
import { authorizeAdmin } from '../../../../helpers/authorization.js'; import { authorizeAdmin } from '../../../../helpers/authorization.js';
import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js'; import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js';
@@ -12,7 +11,7 @@ router.get(
authenticateUser, authenticateUser,
authorizeAdmin, authorizeAdmin,
checkIsEnterprise, checkIsEnterprise,
asyncHandler(getPermissionsCatalogAction) getPermissionsCatalogAction
); );
export default router; export default router;

View File

@@ -1,5 +1,4 @@
import { Router } from 'express'; import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../../helpers/authentication.js'; import { authenticateUser } from '../../../../helpers/authentication.js';
import { authorizeAdmin } from '../../../../helpers/authorization.js'; import { authorizeAdmin } from '../../../../helpers/authorization.js';
import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js'; import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js';
@@ -13,7 +12,7 @@ router.get(
authenticateUser, authenticateUser,
authorizeAdmin, authorizeAdmin,
checkIsEnterprise, checkIsEnterprise,
asyncHandler(getRolesAction) getRolesAction
); );
router.get( router.get(
@@ -21,7 +20,7 @@ router.get(
authenticateUser, authenticateUser,
authorizeAdmin, authorizeAdmin,
checkIsEnterprise, checkIsEnterprise,
asyncHandler(getRoleAction) getRoleAction
); );
export default router; export default router;

View File

@@ -1,5 +1,4 @@
import { Router } from 'express'; import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../../helpers/authentication.js'; import { authenticateUser } from '../../../../helpers/authentication.js';
import { authorizeAdmin } from '../../../../helpers/authorization.js'; import { authorizeAdmin } from '../../../../helpers/authorization.js';
import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js'; import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js';
@@ -13,7 +12,7 @@ router.get(
authenticateUser, authenticateUser,
authorizeAdmin, authorizeAdmin,
checkIsEnterprise, checkIsEnterprise,
asyncHandler(getSamlAuthProvidersAction) getSamlAuthProvidersAction
); );
router.get( router.get(
@@ -21,7 +20,7 @@ router.get(
authenticateUser, authenticateUser,
authorizeAdmin, authorizeAdmin,
checkIsEnterprise, checkIsEnterprise,
asyncHandler(getSamlAuthProviderAction) getSamlAuthProviderAction
); );
export default router; export default router;

View File

@@ -1,27 +0,0 @@
import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../../helpers/authentication.js';
import { authorizeAdmin } from '../../../../helpers/authorization.js';
import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js';
import getUsersAction from '../../../../controllers/api/v1/admin/users/get-users.ee.js';
import getUserAction from '../../../../controllers/api/v1/admin/users/get-user.ee.js';
const router = Router();
router.get(
'/',
authenticateUser,
authorizeAdmin,
checkIsEnterprise,
asyncHandler(getUsersAction)
);
router.get(
'/:userId',
authenticateUser,
authorizeAdmin,
checkIsEnterprise,
asyncHandler(getUserAction)
);
export default router;

View File

@@ -1,16 +0,0 @@
import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../helpers/authentication.js';
import { checkIsEnterprise } from '../../../helpers/check-is-enterprise.js';
import getAppAuthClientAction from '../../../controllers/api/v1/app-auth-clients/get-app-auth-client.js';
const router = Router();
router.get(
'/:appAuthClientId',
authenticateUser,
checkIsEnterprise,
asyncHandler(getAppAuthClientAction)
);
export default router;

View File

@@ -1,42 +0,0 @@
import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../helpers/authentication.js';
import getAppAction from '../../../controllers/api/v1/apps/get-app.js';
import getAppsAction from '../../../controllers/api/v1/apps/get-apps.js';
import getAuthAction from '../../../controllers/api/v1/apps/get-auth.js';
import getTriggersAction from '../../../controllers/api/v1/apps/get-triggers.js';
import getTriggerSubstepsAction from '../../../controllers/api/v1/apps/get-trigger-substeps.js';
import getActionsAction from '../../../controllers/api/v1/apps/get-actions.js';
import getActionSubstepsAction from '../../../controllers/api/v1/apps/get-action-substeps.js';
const router = Router();
router.get('/', authenticateUser, asyncHandler(getAppsAction));
router.get('/:appKey', authenticateUser, asyncHandler(getAppAction));
router.get('/:appKey/auth', authenticateUser, asyncHandler(getAuthAction));
router.get(
'/:appKey/triggers',
authenticateUser,
asyncHandler(getTriggersAction)
);
router.get(
'/:appKey/triggers/:triggerKey/substeps',
authenticateUser,
asyncHandler(getTriggerSubstepsAction)
);
router.get(
'/:appKey/actions',
authenticateUser,
asyncHandler(getActionsAction)
);
router.get(
'/:appKey/actions/:actionKey/substeps',
authenticateUser,
asyncHandler(getActionSubstepsAction)
);
export default router;

View File

@@ -1,15 +1,10 @@
import { Router } from 'express'; import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import versionAction from '../../../controllers/api/v1/automatisch/version.js'; import versionAction from '../../../controllers/api/v1/automatisch/version.js';
import notificationsAction from '../../../controllers/api/v1/automatisch/notifications.js'; import notificationsAction from '../../../controllers/api/v1/automatisch/notifications.js';
import infoAction from '../../../controllers/api/v1/automatisch/info.js';
import licenseAction from '../../../controllers/api/v1/automatisch/license.js';
const router = Router(); const router = Router();
router.get('/version', asyncHandler(versionAction)); router.get('/version', versionAction);
router.get('/notifications', asyncHandler(notificationsAction)); router.get('/notifications', notificationsAction);
router.get('/info', asyncHandler(infoAction));
router.get('/license', asyncHandler(licenseAction));
export default router; export default router;

View File

@@ -1,32 +0,0 @@
import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../helpers/authentication.js';
import { authorizeUser } from '../../../helpers/authorization.js';
import getExecutionsAction from '../../../controllers/api/v1/executions/get-executions.js';
import getExecutionAction from '../../../controllers/api/v1/executions/get-execution.js';
import getExecutionStepsAction from '../../../controllers/api/v1/executions/get-execution-steps.js';
const router = Router();
router.get(
'/',
authenticateUser,
authorizeUser,
asyncHandler(getExecutionsAction)
);
router.get(
'/:executionId',
authenticateUser,
authorizeUser,
asyncHandler(getExecutionAction)
);
router.get(
'/:executionId/execution-steps',
authenticateUser,
authorizeUser,
asyncHandler(getExecutionStepsAction)
);
export default router;

View File

@@ -1,16 +0,0 @@
import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../helpers/authentication.js';
import { authorizeUser } from '../../../helpers/authorization.js';
import getFlowAction from '../../../controllers/api/v1/flows/get-flow.js';
const router = Router();
router.get(
'/:flowId',
authenticateUser,
authorizeUser,
asyncHandler(getFlowAction)
);
export default router;

View File

@@ -1,5 +1,4 @@
import { Router } from 'express'; import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../helpers/authentication.js'; import { authenticateUser } from '../../../helpers/authentication.js';
import checkIsCloud from '../../../helpers/check-is-cloud.js'; import checkIsCloud from '../../../helpers/check-is-cloud.js';
import getPlansAction from '../../../controllers/api/v1/payment/get-plans.ee.js'; import getPlansAction from '../../../controllers/api/v1/payment/get-plans.ee.js';
@@ -7,18 +6,7 @@ import getPaddleInfoAction from '../../../controllers/api/v1/payment/get-paddle-
const router = Router(); const router = Router();
router.get( router.get('/plans', authenticateUser, checkIsCloud, getPlansAction);
'/plans', router.get('/paddle-info', authenticateUser, checkIsCloud, getPaddleInfoAction);
authenticateUser,
checkIsCloud,
asyncHandler(getPlansAction)
);
router.get(
'/paddle-info',
authenticateUser,
checkIsCloud,
asyncHandler(getPaddleInfoAction)
);
export default router; export default router;

View File

@@ -1,26 +1,22 @@
import { Router } from 'express'; import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../helpers/authentication.js'; import { authenticateUser } from '../../../helpers/authentication.js';
import { authorizeUser } from '../../../helpers/authorization.js';
import checkIsCloud from '../../../helpers/check-is-cloud.js'; import checkIsCloud from '../../../helpers/check-is-cloud.js';
import getCurrentUserAction from '../../../controllers/api/v1/users/get-current-user.js'; import getCurrentUserAction from '../../../controllers/api/v1/users/get-current-user.js';
import getUserAction from '../../../controllers/api/v1/users/get-user.js';
import getUsersAction from '../../../controllers/api/v1/users/get-users.js';
import getUserTrialAction from '../../../controllers/api/v1/users/get-user-trial.ee.js'; import getUserTrialAction from '../../../controllers/api/v1/users/get-user-trial.ee.js';
import getInvoicesAction from '../../../controllers/api/v1/users/get-invoices.ee.js';
const router = Router(); const router = Router();
router.get('/me', authenticateUser, asyncHandler(getCurrentUserAction)); router.get('/', authenticateUser, authorizeUser, getUsersAction);
router.get( router.get('/me', authenticateUser, getCurrentUserAction);
'/invoices', router.get('/:userId', authenticateUser, authorizeUser, getUserAction);
authenticateUser,
checkIsCloud,
asyncHandler(getInvoicesAction)
);
router.get( router.get(
'/:userId/trial', '/:userId/trial',
authenticateUser, authenticateUser,
checkIsCloud, checkIsCloud,
asyncHandler(getUserTrialAction) getUserTrialAction
); );
export default router; export default router;

View File

@@ -1,9 +1,8 @@
import { Router } from 'express'; import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import indexAction from '../controllers/healthcheck/index.js'; import indexAction from '../controllers/healthcheck/index.js';
const router = Router(); const router = Router();
router.get('/', asyncHandler(indexAction)); router.get('/', indexAction);
export default router; export default router;

View File

@@ -6,15 +6,9 @@ import healthcheckRouter from './healthcheck.js';
import automatischRouter from './api/v1/automatisch.js'; import automatischRouter from './api/v1/automatisch.js';
import usersRouter from './api/v1/users.js'; import usersRouter from './api/v1/users.js';
import paymentRouter from './api/v1/payment.ee.js'; import paymentRouter from './api/v1/payment.ee.js';
import appAuthClientsRouter from './api/v1/app-auth-clients.js';
import flowsRouter from './api/v1/flows.js';
import appsRouter from './api/v1/apps.js';
import executionsRouter from './api/v1/executions.js';
import samlAuthProvidersRouter from './api/v1/admin/saml-auth-providers.ee.js'; import samlAuthProvidersRouter from './api/v1/admin/saml-auth-providers.ee.js';
import rolesRouter from './api/v1/admin/roles.ee.js'; import rolesRouter from './api/v1/admin/roles.ee.js';
import permissionsRouter from './api/v1/admin/permissions.ee.js'; import permissionsRouter from './api/v1/admin/permissions.ee.js';
import adminUsersRouter from './api/v1/admin/users.ee.js';
import adminAppAuthClientsRouter from './api/v1/admin/app-auth-clients.js';
const router = Router(); const router = Router();
@@ -25,14 +19,8 @@ router.use('/healthcheck', healthcheckRouter);
router.use('/api/v1/automatisch', automatischRouter); router.use('/api/v1/automatisch', automatischRouter);
router.use('/api/v1/users', usersRouter); router.use('/api/v1/users', usersRouter);
router.use('/api/v1/payment', paymentRouter); router.use('/api/v1/payment', paymentRouter);
router.use('/api/v1/app-auth-clients', appAuthClientsRouter);
router.use('/api/v1/flows', flowsRouter);
router.use('/api/v1/apps', appsRouter);
router.use('/api/v1/executions', executionsRouter);
router.use('/api/v1/admin/saml-auth-providers', samlAuthProvidersRouter); router.use('/api/v1/admin/saml-auth-providers', samlAuthProvidersRouter);
router.use('/api/v1/admin/roles', rolesRouter); router.use('/api/v1/admin/roles', rolesRouter);
router.use('/api/v1/admin/permissions', permissionsRouter); router.use('/api/v1/admin/permissions', permissionsRouter);
router.use('/api/v1/admin/users', adminUsersRouter);
router.use('/api/v1/admin/app-auth-clients', adminAppAuthClientsRouter);
export default router; export default router;

View File

@@ -1,9 +1,16 @@
import { Router } from 'express'; import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import webhooksHandler from '../controllers/paddle/webhooks.ee.js'; import webhooksHandler from '../controllers/paddle/webhooks.ee.js';
const router = Router(); const router = Router();
router.post('/webhooks', asyncHandler(webhooksHandler)); const exposeError = (handler) => async (req, res, next) => {
try {
await handler(req, res, next);
} catch (err) {
next(err);
}
};
router.post('/webhooks', exposeError(webhooksHandler));
export default router; export default router;

View File

@@ -1,9 +0,0 @@
const actionSerializer = (action) => {
return {
name: action.name,
key: action.key,
description: action.description,
};
};
export default actionSerializer;

View File

@@ -1,21 +0,0 @@
import { describe, it, expect } from 'vitest';
import App from '../models/app';
import actionSerializer from './action';
describe('actionSerializer', () => {
it('should return the action data', async () => {
const actions = await App.findActionsByKey('github');
const action = actions[0];
const expectedPayload = {
description: action.description,
key: action.key,
name: action.name,
pollInterval: action.pollInterval,
showWebhookUrl: action.showWebhookUrl,
type: action.type,
};
expect(actionSerializer(action)).toEqual(expectedPayload);
});
});

View File

@@ -1,10 +0,0 @@
const appAuthClientSerializer = (appAuthClient) => {
return {
id: appAuthClient.id,
appConfigId: appAuthClient.appConfigId,
name: appAuthClient.name,
active: appAuthClient.active,
};
};
export default appAuthClientSerializer;

Some files were not shown because too many files have changed in this diff Show More