Compare commits

..

54 Commits

Author SHA1 Message Date
Rıdvan Akca
64755695a1 feat(dropbox): add create text file action 2024-02-26 18:47:35 +03:00
Rıdvan Akca
51e96b832d feat(dropbox): add new folders trigger 2024-02-26 17:43:01 +03:00
Ömer Faruk Aydın
60b20c4d01 Merge pull request #1655 from automatisch/add-async-handler
feat: Implement async handler for routes
2024-02-26 01:25:35 +01:00
Faruk AYDIN
8b4aee1afa feat: Implement async handler for routes 2024-02-26 01:03:15 +01:00
Ömer Faruk Aydın
51abd74304 Merge pull request #1654 from automatisch/get-flow
feat: Implement get flow API endpoint
2024-02-26 01:02:38 +01:00
Faruk AYDIN
b93b465f09 feat: Implement get flow API endpoint 2024-02-26 00:52:02 +01:00
Faruk AYDIN
5aad68ec62 test: Use nested serializers explicitly for serializer tests 2024-02-25 23:34:41 +01:00
Faruk AYDIN
74fbc937a1 feat: Introduce flow serializer 2024-02-25 23:31:22 +01:00
Faruk AYDIN
7e35f544eb feat: Introduce step serializer 2024-02-25 23:01:55 +01:00
Ömer Faruk Aydın
ed1c3cffc1 Merge pull request #1653 from automatisch/rest-automatisch-license
faet: Implement automatisch license API endpoint
2024-02-25 18:36:42 +01:00
Ömer Faruk Aydın
c4983a9f9b Merge pull request #1652 from automatisch/rest-automatisch-info
feat: Implement automatisch info API endpoint
2024-02-25 18:36:27 +01:00
Ömer Faruk Aydın
5b43262e7a Merge pull request #1651 from automatisch/remove-role-join
chore: No need to join role since we don't expose roleId anymore
2024-02-25 18:36:10 +01:00
Ömer Faruk Aydın
dad4408679 Merge pull request #1650 from automatisch/rest-get-invoices
feat: Implement get invoices API endpoint
2024-02-25 18:35:52 +01:00
Ömer Faruk Aydın
a78c4d12b4 Merge pull request #1607 from automatisch/AUT-681
feat: implement app-auth-client endpoint
2024-02-25 18:35:33 +01:00
Ömer Faruk Aydın
74664a9df8 Merge pull request #1649 from automatisch/move-get-user
feat: Move get user API endpoint to admin namespace
2024-02-25 18:34:48 +01:00
Ömer Faruk Aydın
fce5281a03 Merge pull request #1648 from automatisch/move-get-users
feat: Move get users API endpoint to admin namespace
2024-02-25 18:32:24 +01:00
Faruk AYDIN
de0bd2f486 faet: Implement automatisch license API endpoint 2024-02-25 03:28:20 +01:00
Faruk AYDIN
079fb5d108 feat: Implement automatisch info API endpoint 2024-02-25 03:27:17 +01:00
Faruk AYDIN
1c7435a32b chore: No need to join role since we don't expose roleId anymore 2024-02-25 02:02:02 +01:00
Faruk AYDIN
1afd374cf6 feat: Implement get invoices API endpoint 2024-02-25 01:31:36 +01:00
Faruk AYDIN
3adf549915 feat: Extract get invoices logic to user model 2024-02-25 01:30:29 +01:00
Faruk AYDIN
e94d669eca fix: Cover empty array case for renderer helper 2024-02-25 01:29:59 +01:00
Faruk AYDIN
5fac0b4689 test: Add tests for app auth client serializer 2024-02-24 02:51:34 +01:00
Faruk AYDIN
832d323a6e refactor: Remove redundant query string from get app auth client tests 2024-02-24 01:25:46 +01:00
Faruk AYDIN
03f1dbd5b2 feat: Add check enterprise middleware to get app auth clients 2024-02-24 01:24:41 +01:00
Faruk AYDIN
c0a216f109 refactor: Remove license check for admin, since it is middleware responsibility 2024-02-24 01:22:27 +01:00
Faruk AYDIN
ad67b13270 fix: Add missing middleware imports for admin app auth clients 2024-02-24 01:18:30 +01:00
Faruk AYDIN
5d420c08c6 fix: Remove forgotten character in the routes 2024-02-24 01:14:56 +01:00
Faruk AYDIN
3d8235c670 refactor: Use kebab-case for app auth client serializer filename 2024-02-24 01:10:59 +01:00
Faruk AYDIN
5a209f81d1 feat: Add missing middleware checks to admin app auth clients 2024-02-24 01:08:08 +01:00
Rıdvan Akca
d17d8e2805 feat: implement app-auth-client endpoint 2024-02-24 01:02:28 +01:00
Faruk AYDIN
ca7636e7bc feat: Move get user API endpoint to admin namespace 2024-02-24 00:40:54 +01:00
Faruk AYDIN
532cfc10d0 feat: Move get users API endpoint to admin namespace 2024-02-24 00:31:15 +01:00
Ömer Faruk Aydın
72d68c4377 Merge pull request #1647 from automatisch/error-logger-for-queues
feat: Add logger for errors happened in queues
2024-02-23 23:57:00 +01:00
Faruk AYDIN
00f5964aa4 feat: Add logger for errors happened in queues 2024-02-23 23:50:50 +01:00
Ömer Faruk Aydın
fcf345abab Merge pull request #1642 from automatisch/delete-step
fix: Allow permitted users to delete others steps
2024-02-23 14:15:29 +01:00
Faruk AYDIN
24ad43d3e4 fix: Allow permitted users to delete others steps 2024-02-23 13:45:50 +01:00
Ömer Faruk Aydın
9a7cdf42e1 Merge pull request #1641 from automatisch/remove-role-id-from-user-serializer
chore: Remove redundant roleId from user serializer
2024-02-23 11:28:20 +01:00
Ömer Faruk Aydın
c36b652d5b Merge pull request #1640 from automatisch/rest-get-notifications
feat: Implement get notifications API endpoint
2024-02-23 11:28:11 +01:00
Ömer Faruk Aydın
553070fc23 Merge pull request #1638 from automatisch/rest-get-payment-paddle-info
feat: Implement get paddle info API endpoint
2024-02-23 11:27:50 +01:00
Ömer Faruk Aydın
5d69f7e24f Merge pull request #1637 from automatisch/rest-get-payment-plans
feat: Implement get payment plans API endpoint
2024-02-23 11:27:36 +01:00
Ömer Faruk Aydın
bc0e2bada0 Merge pull request #1635 from automatisch/rest-get-role
feat: Implement get role API endpoint for admin
2024-02-23 11:27:22 +01:00
Ömer Faruk Aydın
80b6cc1d94 Merge pull request #1636 from automatisch/rest-get-permissions-catalog
feat: Implement permission catalog API endpoint
2024-02-23 11:20:34 +01:00
Ömer Faruk Aydın
bce3273e64 Merge pull request #1634 from automatisch/rest-get-roles
feat: Implement admin get roles API endpoint
2024-02-23 11:20:25 +01:00
Faruk AYDIN
3abf61152a chore: Remove redundant roleId from user serializer 2024-02-23 01:30:57 +01:00
Faruk AYDIN
14923d4cd6 feat: Implement get notifications API endpoint 2024-02-23 01:24:56 +01:00
Faruk AYDIN
6fdc4bf900 feat: Implement get paddle info API endpoint 2024-02-22 20:22:05 +01:00
Faruk AYDIN
d21e1f75b5 feat: Implement get payment plans API endpoint 2024-02-21 18:15:32 +01:00
Faruk AYDIN
84a0b37fcc feat: Implement permission catalog API endpoint 2024-02-21 17:52:51 +01:00
Faruk AYDIN
f135a0f09e feat: Implement get role API endpoint for admin 2024-02-21 17:39:05 +01:00
Faruk AYDIN
0f24c99456 feat: Add permissions to role serializer 2024-02-21 16:01:45 +01:00
Faruk AYDIN
9eae0ab947 fix: Move get saml auth provider mocks to correct namespace 2024-02-21 15:37:39 +01:00
Faruk AYDIN
3bf1f79c79 feat: Implement admin get roles API endpoint 2024-02-21 15:35:30 +01:00
Faruk AYDIN
b21074c871 fix: Move saml auth provider router to correct folder 2024-02-21 14:59:42 +01:00
104 changed files with 1655 additions and 3220 deletions

View File

@@ -38,6 +38,7 @@
"debug": "~2.6.9",
"dotenv": "^10.0.0",
"express": "~4.18.2",
"express-async-handler": "^1.2.0",
"express-basic-auth": "^1.2.1",
"express-graphql": "^0.12.0",
"fast-xml-parser": "^4.0.11",
@@ -94,6 +95,7 @@
"url": "https://github.com/automatisch/automatisch/issues"
},
"devDependencies": {
"@typescript-eslint/utils": "^7.0.2",
"nodemon": "^2.0.13",
"supertest": "^6.3.3",
"vitest": "^1.1.3"

View File

@@ -0,0 +1,82 @@
import path from 'node:path';
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Create a text file',
key: 'createTextFile',
description: 'Create a new text file from plain text content you specify.',
arguments: [
{
label: 'Folder',
key: 'parentFolder',
type: 'string',
required: true,
description:
'Enter the folder path that file will be saved, like /TextFiles/ or /Documents/Taxes/',
variables: true,
},
{
label: 'Folder Name',
key: 'folderName',
type: 'string',
required: true,
description:
"Enter the name for the new file. The file extension will be '.txt'.",
variables: true,
},
{
label: 'File Content',
key: 'fileContent',
type: 'string',
required: true,
description: 'Plain text content to insert into the new text file.',
variables: true,
},
{
label: 'Overwrite',
key: 'overwrite',
type: 'dropdown',
required: true,
description:
'Overwrite this file (if one of the same name exists) or not.',
variables: true,
options: [
{ label: 'False', value: false },
{ label: 'True', value: true },
],
},
],
async run($) {
const fileContent = $.step.parameters.fileContent;
const overwrite = $.step.parameters.overwrite;
const parentFolder = $.step.parameters.parentFolder;
const folderName = $.step.parameters.folderName;
const folderPath = path.join(parentFolder, folderName);
const headers = {
Authorization: `Bearer ${$.auth.data.accessToken}`,
'Content-Type': 'application/octet-stream',
'Dropbox-API-Arg': JSON.stringify({
autorename: false,
mode: overwrite ? 'overwrite' : 'add',
mute: false,
path: `${folderPath}.txt`,
strict_conflict: false,
}),
};
const response = await $.http.post(
'https://content.dropboxapi.com/2/files/upload',
fileContent,
{
headers,
additionalProperties: {
skipAddingAuthHeader: true,
},
}
);
$.setActionItem({ raw: response.data });
},
});

View File

@@ -1,4 +1,5 @@
import createFolder from './create-folder/index.js';
import createTextFile from './create-text-file/index.js';
import renameFile from './rename-file/index.js';
export default [createFolder, renameFile];
export default [createFolder, createTextFile, renameFile];

View File

@@ -1,10 +1,10 @@
const addAuthHeader = ($, requestConfig) => {
requestConfig.headers['Content-Type'] = 'application/json';
if (
!requestConfig.additionalProperties?.skipAddingAuthHeader &&
$.auth.data?.accessToken
) {
requestConfig.headers['Content-Type'] = 'application/json';
requestConfig.headers.Authorization = `Bearer ${$.auth.data.accessToken}`;
}

View File

@@ -2,6 +2,7 @@ import defineApp from '../../helpers/define-app.js';
import addAuthHeader from './common/add-auth-header.js';
import auth from './auth/index.js';
import actions from './actions/index.js';
import triggers from './triggers/index.js';
export default defineApp({
name: 'Dropbox',
@@ -15,4 +16,5 @@ export default defineApp({
beforeRequest: [addAuthHeader],
auth,
actions,
triggers,
});

View File

@@ -0,0 +1,3 @@
import newFolders from './new-folders/index.js';
export default [newFolders];

View File

@@ -0,0 +1,61 @@
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'New folders',
key: 'newFolders',
pollInterval: 15,
description:
'Triggers when any new folder is added. Ensure that the number of files/folders within the monitored directory remains below 4000.',
arguments: [
{
label: 'Folder',
key: 'folderPath',
type: 'string',
required: true,
description:
'Enter the folder path that you want to follow, like /TextFiles or /Documents/Taxes.',
variables: true,
},
],
async run($) {
const folderPath = $.step.parameters.folderPath;
let endpoint = '/2/files/list_folder';
let next = false;
const params = {
path: folderPath,
recursive: false,
include_deleted: false,
include_has_explicit_shared_members: false,
include_mounted_folders: true,
limit: 2000,
include_non_downloadable_files: true,
};
do {
const { data } = await $.http.post(endpoint, params);
if (data.has_more) {
endpoint += '/continue';
params.cursor = data.cursor;
next = data.has_more;
} else {
next = false;
}
if (data.entries?.length) {
for (const entry of data.entries.reverse()) {
if (entry['.tag'] === 'folder') {
$.pushTriggerItem({
raw: entry,
meta: {
internalId: entry.id,
},
});
}
}
}
} while (next);
},
});

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 112 KiB

View File

@@ -1,21 +0,0 @@
import verifyCredentials from './verify-credentials.js';
import isStillVerified from './is-still-verified.js';
export default {
fields: [
{
key: 'apiKey',
label: 'API Key',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: 'PDFMonkey API secret key of your account.',
clickToCopy: false,
},
],
verifyCredentials,
isStillVerified,
};

View File

@@ -1,8 +0,0 @@
import getCurrentUser from '../common/get-current-user.js';
const isStillVerified = async ($) => {
const currentUser = await getCurrentUser($);
return !!currentUser.id;
};
export default isStillVerified;

View File

@@ -1,15 +0,0 @@
import getCurrentUser from '../common/get-current-user.js';
const verifyCredentials = async ($) => {
const currentUser = await getCurrentUser($);
const screenName = [currentUser.desired_name, currentUser.email]
.filter(Boolean)
.join(' @ ');
await $.auth.set({
screenName,
apiKey: $.auth.data.apiKey,
});
};
export default verifyCredentials;

View File

@@ -1,9 +0,0 @@
const addAuthHeader = ($, requestConfig) => {
if ($.auth.data?.apiKey) {
requestConfig.headers.Authorization = `Bearer ${$.auth.data.apiKey}`;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -1,8 +0,0 @@
const getCurrentUser = async ($) => {
const response = await $.http.get('/v1/current_user');
const currentUser = response.data.current_user;
return currentUser;
};
export default getCurrentUser;

View File

@@ -1,16 +0,0 @@
import defineApp from '../../helpers/define-app.js';
import addAuthHeader from './common/add-auth-header.js';
import auth from './auth/index.js';
export default defineApp({
name: 'PDFMonkey',
key: 'pdf-monkey',
iconUrl: '{BASE_URL}/apps/pdf-monkey/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/pdf-monkey/connection',
supportsConnections: true,
baseUrl: 'https://pdfmonkey.io',
apiBaseUrl: 'https://api.pdfmonkey.io/api',
primaryColor: 'db2777',
beforeRequest: [addAuthHeader],
auth,
});

View File

@@ -0,0 +1,10 @@
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

@@ -0,0 +1,35 @@
import { vi, 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.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);
});
});
});

View File

@@ -0,0 +1,6 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import permissionCatalog from '../../../../../helpers/permission-catalog.ee.js';
export default async (request, response) => {
renderObject(response, permissionCatalog);
};

View File

@@ -0,0 +1,32 @@
import { vi, 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.js';
import { createRole } from '../../../../../../test/factories/role.js';
import { createUser } from '../../../../../../test/factories/user.js';
import getPermissionsCatalogMock from '../../../../../../test/mocks/rest/api/v1/admin/permissions/get-permissions-catalog.ee.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/permissions/catalog', () => {
let role, currentUser, token;
beforeEach(async () => {
role = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: role.id });
token = createAuthTokenByUserId(currentUser.id);
});
it('should return roles', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const response = await request(app)
.get('/api/v1/admin/permissions/catalog')
.set('Authorization', token)
.expect(200);
const expectedPayload = await getPermissionsCatalogMock();
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -0,0 +1,16 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import Role from '../../../../../models/role.js';
export default async (request, response) => {
const role = await Role.query()
.leftJoinRelated({
permissions: true,
})
.withGraphFetched({
permissions: true,
})
.findById(request.params.roleId)
.throwIfNotFound();
renderObject(response, role);
};

View File

@@ -0,0 +1,38 @@
import { vi, 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.js';
import { createRole } from '../../../../../../test/factories/role.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createPermission } from '../../../../../../test/factories/permission.js';
import getRoleMock from '../../../../../../test/mocks/rest/api/v1/admin/roles/get-role.ee.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/roles/:roleId', () => {
let role, currentUser, token, permissionOne, permissionTwo;
beforeEach(async () => {
role = await createRole({ key: 'admin' });
permissionOne = await createPermission({ roleId: role.id });
permissionTwo = await createPermission({ roleId: role.id });
currentUser = await createUser({ roleId: role.id });
token = createAuthTokenByUserId(currentUser.id);
});
it('should return roles', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const response = await request(app)
.get(`/api/v1/admin/roles/${role.id}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getRoleMock(role, [
permissionOne,
permissionTwo,
]);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -0,0 +1,8 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import Role from '../../../../../models/role.js';
export default async (request, response) => {
const roles = await Role.query().orderBy('name');
renderObject(response, roles);
};

View File

@@ -0,0 +1,33 @@
import { vi, 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.js';
import { createRole } from '../../../../../../test/factories/role.js';
import { createUser } from '../../../../../../test/factories/user.js';
import getRolesMock from '../../../../../../test/mocks/rest/api/v1/admin/roles/get-roles.ee.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/roles', () => {
let roleOne, roleTwo, currentUser, token;
beforeEach(async () => {
roleOne = await createRole({ key: 'admin' });
roleTwo = await createRole({ key: 'user' });
currentUser = await createUser({ roleId: roleOne.id });
token = createAuthTokenByUserId(currentUser.id);
});
it('should return roles', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const response = await request(app)
.get('/api/v1/admin/roles')
.set('Authorization', token)
.expect(200);
const expectedPayload = await getRolesMock([roleOne, roleTwo]);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -5,7 +5,7 @@ import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by
import { createRole } from '../../../../../../test/factories/role.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createSamlAuthProvider } from '../../../../../../test/factories/saml-auth-provider.ee.js';
import getSamlAuthProviderMock from '../../../../../../test/mocks/rest/api/v1/saml-auth-providers/get-saml-auth-provider.ee.js';
import getSamlAuthProviderMock from '../../../../../../test/mocks/rest/api/v1/admin/saml-auth-providers/get-saml-auth-provider.ee.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/saml-auth-provider/:samlAuthProviderId', () => {

View File

@@ -5,7 +5,7 @@ import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by
import { createRole } from '../../../../../../test/factories/role.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createSamlAuthProvider } from '../../../../../../test/factories/saml-auth-provider.ee.js';
import getSamlAuthProvidersMock from '../../../../../../test/mocks/rest/api/v1/saml-auth-providers/get-saml-auth-providers.ee.js';
import getSamlAuthProvidersMock from '../../../../../../test/mocks/rest/api/v1/admin/saml-auth-providers/get-saml-auth-providers.ee.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/saml-auth-providers', () => {

View File

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

View File

@@ -0,0 +1,34 @@
import { vi, 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 { 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);
});
});

View File

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

View File

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

View File

@@ -0,0 +1,11 @@
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

@@ -0,0 +1,31 @@
import { vi, 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.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);
});
});

View File

@@ -0,0 +1,13 @@
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

@@ -0,0 +1,22 @@
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

@@ -0,0 +1,15 @@
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

@@ -0,0 +1,23 @@
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

@@ -0,0 +1,19 @@
import { renderObject } from '../../../../helpers/renderer.js';
import axios from '../../../../helpers/axios-with-proxy.js';
import logger from '../../../../helpers/logger.js';
const NOTIFICATIONS_URL =
'https://notifications.automatisch.io/notifications.json';
export default async (request, response) => {
let notifications = [];
try {
const response = await axios.get(NOTIFICATIONS_URL);
notifications = response.data;
} catch (error) {
logger.error('Error fetching notifications API endpoint!', error);
}
renderObject(response, notifications);
};

View File

@@ -0,0 +1,9 @@
import { describe, it } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
describe('GET /api/v1/automatisch/notifications', () => {
it('should return Automatisch notifications', async () => {
await request(app).get('/api/v1/automatisch/notifications').expect(200);
});
});

View File

@@ -0,0 +1,11 @@
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

@@ -0,0 +1,71 @@
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';
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);
});
});

View File

@@ -0,0 +1,8 @@
import { renderObject } from '../../../../helpers/renderer.js';
import Billing from '../../../../helpers/billing/index.ee.js';
export default async (request, response) => {
const paddleInfo = Billing.paddleInfo;
renderObject(response, paddleInfo);
};

View File

@@ -0,0 +1,33 @@
import { vi, 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.js';
import { createUser } from '../../../../../test/factories/user.js';
import getPaddleInfoMock from '../../../../../test/mocks/rest/api/v1/payment/get-paddle-info.js';
import appConfig from '../../../../config/app.js';
import billing from '../../../../helpers/billing/index.ee.js';
describe('GET /api/v1/payment/paddle-info', () => {
let user, token;
beforeEach(async () => {
user = await createUser();
token = createAuthTokenByUserId(user.id);
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
vi.spyOn(billing.paddleInfo, 'vendorId', 'get').mockReturnValue(
'sampleVendorId'
);
});
it('should return payment plans', async () => {
const response = await request(app)
.get('/api/v1/payment/paddle-info')
.set('Authorization', token)
.expect(200);
const expectedResponsePayload = await getPaddleInfoMock();
expect(response.body).toEqual(expectedResponsePayload);
});
});

View File

@@ -0,0 +1,8 @@
import { renderObject } from '../../../../helpers/renderer.js';
import Billing from '../../../../helpers/billing/index.ee.js';
export default async (request, response) => {
const paymentPlans = Billing.paddlePlans;
renderObject(response, paymentPlans);
};

View File

@@ -0,0 +1,29 @@
import { vi, 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.js';
import { createUser } from '../../../../../test/factories/user.js';
import getPaymentPlansMock from '../../../../../test/mocks/rest/api/v1/payment/get-plans.js';
import appConfig from '../../../../config/app.js';
describe('GET /api/v1/payment/plans', () => {
let user, token;
beforeEach(async () => {
user = await createUser();
token = createAuthTokenByUserId(user.id);
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
});
it('should return payment plans', async () => {
const response = await request(app)
.get('/api/v1/payment/plans')
.set('Authorization', token)
.expect(200);
const expectedResponsePayload = await getPaymentPlansMock();
expect(response.body).toEqual(expectedResponsePayload);
});
});

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,6 +15,7 @@ import Role from './role.js';
import Step from './step.js';
import Subscription from './subscription.ee.js';
import UsageData from './usage-data.ee.js';
import Billing from '../helpers/billing/index.ee.js';
class User extends Base {
static tableName = 'users';
@@ -143,6 +144,11 @@ class User extends Base {
},
});
get authorizedFlows() {
const conditions = this.can('read', 'Flow');
return conditions.isCreator ? this.$relatedQuery('flows') : Flow.query();
}
login(password) {
return bcrypt.compare(password, this.password);
}
@@ -237,6 +243,20 @@ class User extends Base {
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) {
await super.$beforeInsert(queryContext);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,18 @@
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

@@ -0,0 +1,18 @@
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 getPermissionsCatalogAction from '../../../../controllers/api/v1/admin/permissions/get-permissions-catalog.ee.js';
const router = Router();
router.get(
'/catalog',
authenticateUser,
authorizeAdmin,
checkIsEnterprise,
asyncHandler(getPermissionsCatalogAction)
);
export default router;

View File

@@ -0,0 +1,27 @@
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 getRolesAction from '../../../../controllers/api/v1/admin/roles/get-roles.ee.js';
import getRoleAction from '../../../../controllers/api/v1/admin/roles/get-role.ee.js';
const router = Router();
router.get(
'/',
authenticateUser,
authorizeAdmin,
checkIsEnterprise,
asyncHandler(getRolesAction)
);
router.get(
'/:roleId',
authenticateUser,
authorizeAdmin,
checkIsEnterprise,
asyncHandler(getRoleAction)
);
export default router;

View File

@@ -0,0 +1,27 @@
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 getSamlAuthProvidersAction from '../../../../controllers/api/v1/admin/saml-auth-providers/get-saml-auth-providers.ee.js';
import getSamlAuthProviderAction from '../../../../controllers/api/v1/admin/saml-auth-providers/get-saml-auth-provider.ee.js';
const router = Router();
router.get(
'/',
authenticateUser,
authorizeAdmin,
checkIsEnterprise,
asyncHandler(getSamlAuthProvidersAction)
);
router.get(
'/:samlAuthProviderId',
authenticateUser,
authorizeAdmin,
checkIsEnterprise,
asyncHandler(getSamlAuthProviderAction)
);
export default router;

View File

@@ -0,0 +1,27 @@
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

@@ -0,0 +1,16 @@
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,8 +1,15 @@
import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import versionAction from '../../../controllers/api/v1/automatisch/version.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();
router.get('/version', versionAction);
router.get('/version', asyncHandler(versionAction));
router.get('/notifications', asyncHandler(notificationsAction));
router.get('/info', asyncHandler(infoAction));
router.get('/license', asyncHandler(licenseAction));
export default router;

View File

@@ -0,0 +1,16 @@
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

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

View File

@@ -1,26 +0,0 @@
import { Router } from 'express';
import { authenticateUser } from '../../../helpers/authentication.js';
import { authorizeAdmin } from '../../../helpers/authorization.js';
import { checkIsEnterprise } from '../../../helpers/check-is-enterprise.js';
import getSamlAuthProvidersAction from '../../../controllers/api/v1/admin/saml-auth-providers/get-saml-auth-providers.ee.js';
import getSamlAuthProviderAction from '../../../controllers/api/v1/admin/saml-auth-providers/get-saml-auth-provider.ee.js';
const router = Router();
router.get(
'/',
authenticateUser,
authorizeAdmin,
checkIsEnterprise,
getSamlAuthProvidersAction
);
router.get(
'/:samlAuthProviderId',
authenticateUser,
authorizeAdmin,
checkIsEnterprise,
getSamlAuthProviderAction
);
export default router;

View File

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

View File

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

View File

@@ -5,7 +5,14 @@ import paddleRouter from './paddle.ee.js';
import healthcheckRouter from './healthcheck.js';
import automatischRouter from './api/v1/automatisch.js';
import usersRouter from './api/v1/users.js';
import samlAuthProvidersRouter from './api/v1/saml-auth-providers.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 samlAuthProvidersRouter from './api/v1/admin/saml-auth-providers.ee.js';
import rolesRouter from './api/v1/admin/roles.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();
@@ -15,6 +22,13 @@ router.use('/paddle', paddleRouter);
router.use('/healthcheck', healthcheckRouter);
router.use('/api/v1/automatisch', automatischRouter);
router.use('/api/v1/users', usersRouter);
router.use('/api/v1/payment', paymentRouter);
router.use('/api/v1/app-auth-clients', appAuthClientsRouter);
router.use('/api/v1/flows', flowsRouter);
router.use('/api/v1/admin/saml-auth-providers', samlAuthProvidersRouter);
router.use('/api/v1/admin/roles', rolesRouter);
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;

View File

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

View File

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

View File

@@ -0,0 +1,22 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { createAppAuthClient } from '../../test/factories/app-auth-client';
import appAuthClientSerializer from './app-auth-client';
describe('appAuthClient serializer', () => {
let appAuthClient;
beforeEach(async () => {
appAuthClient = await createAppAuthClient();
});
it('should return app auth client data', async () => {
const expectedPayload = {
id: appAuthClient.id,
appConfigId: appAuthClient.appConfigId,
name: appAuthClient.name,
active: appAuthClient.active,
};
expect(appAuthClientSerializer(appAuthClient)).toEqual(expectedPayload);
});
});

View File

@@ -0,0 +1,18 @@
import stepSerializer from './step.js';
const flowSerializer = (flow) => {
let flowData = {
id: flow.id,
name: flow.name,
active: flow.active,
status: flow.status,
};
if (flow.steps) {
flowData.steps = flow.steps.map((step) => stepSerializer(step));
}
return flowData;
};
export default flowSerializer;

View File

@@ -0,0 +1,44 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { createFlow } from '../../test/factories/flow';
import flowSerializer from './flow';
import stepSerializer from './step';
import { createStep } from '../../test/factories/step';
describe('flowSerializer', () => {
let flow, stepOne, stepTwo;
beforeEach(async () => {
flow = await createFlow();
stepOne = await createStep({
flowId: flow.id,
type: 'trigger',
});
stepTwo = await createStep({
flowId: flow.id,
type: 'action',
});
});
it('should return flow data', async () => {
const expectedPayload = {
id: flow.id,
name: flow.name,
active: flow.active,
status: flow.status,
};
expect(flowSerializer(flow)).toEqual(expectedPayload);
});
it('should return flow data with the steps', async () => {
flow.steps = [stepOne, stepTwo];
const expectedPayload = {
steps: [stepSerializer(stepOne), stepSerializer(stepTwo)],
};
expect(flowSerializer(flow)).toMatchObject(expectedPayload);
});
});

View File

@@ -2,12 +2,18 @@ import userSerializer from './user.js';
import roleSerializer from './role.js';
import permissionSerializer from './permission.js';
import samlAuthProviderSerializer from './saml-auth-provider.ee.js';
import appAuthClientSerializer from './app-auth-client.js';
import flowSerializer from './flow.js';
import stepSerializer from './step.js';
const serializers = {
User: userSerializer,
Role: roleSerializer,
Permission: permissionSerializer,
SamlAuthProvider: samlAuthProviderSerializer,
AppAuthClient: appAuthClientSerializer,
Flow: flowSerializer,
Step: stepSerializer,
};
export default serializers;

View File

@@ -1,5 +1,7 @@
import permissionSerializer from './permission.js';
const roleSerializer = (role) => {
return {
let roleData = {
id: role.id,
name: role.name,
key: role.key,
@@ -8,6 +10,14 @@ const roleSerializer = (role) => {
updatedAt: role.updatedAt,
isAdmin: role.isAdmin,
};
if (role.permissions) {
roleData.permissions = role.permissions.map((permission) =>
permissionSerializer(permission)
);
}
return roleData;
};
export default roleSerializer;

View File

@@ -1,12 +1,26 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { createRole } from '../../test/factories/role';
import roleSerializer from './role';
import permissionSerializer from './permission';
import { createPermission } from '../../test/factories/permission';
describe('roleSerializer', () => {
let role;
let role, permissionOne, permissionTwo;
beforeEach(async () => {
role = await createRole();
permissionOne = await createPermission({
roleId: role.id,
action: 'read',
subject: 'User',
});
permissionTwo = await createPermission({
roleId: role.id,
action: 'read',
subject: 'Role',
});
});
it('should return role data', async () => {
@@ -22,4 +36,17 @@ describe('roleSerializer', () => {
expect(roleSerializer(role)).toEqual(expectedPayload);
});
it('should return role data with the permissions', async () => {
role.permissions = [permissionOne, permissionTwo];
const expectedPayload = {
permissions: [
permissionSerializer(permissionOne),
permissionSerializer(permissionTwo),
],
};
expect(roleSerializer(role)).toMatchObject(expectedPayload);
});
});

View File

@@ -0,0 +1,15 @@
const stepSerializer = (step) => {
return {
id: step.id,
type: step.type,
key: step.key,
appKey: step.appKey,
iconUrl: step.iconUrl,
webhookUrl: step.webhookUrl,
status: step.status,
position: step.position,
parameters: step.parameters,
};
};
export default stepSerializer;

View File

@@ -0,0 +1,27 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { createStep } from '../../test/factories/step';
import stepSerializer from './step';
describe('stepSerializer', () => {
let step;
beforeEach(async () => {
step = await createStep();
});
it('should return step data', async () => {
const expectedPayload = {
id: step.id,
type: step.type,
key: step.key,
appKey: step.appKey,
iconUrl: step.iconUrl,
webhookUrl: step.webhookUrl,
status: step.status,
position: step.position,
parameters: step.parameters,
};
expect(stepSerializer(step)).toEqual(expectedPayload);
});
});

View File

@@ -9,7 +9,6 @@ const userSerializer = (user) => {
createdAt: user.createdAt,
updatedAt: user.updatedAt,
fullName: user.fullName,
roleId: user.roleId,
};
if (user.role) {

View File

@@ -4,6 +4,8 @@ import appConfig from '../config/app';
import { createUser } from '../../test/factories/user';
import { createPermission } from '../../test/factories/permission';
import userSerializer from './user';
import roleSerializer from './role';
import permissionSerializer from './permission';
describe('userSerializer', () => {
let user, role, permissionOne, permissionTwo;
@@ -33,7 +35,6 @@ describe('userSerializer', () => {
email: user.email,
fullName: user.fullName,
id: user.id,
roleId: user.roleId,
updatedAt: user.updatedAt,
};
@@ -44,7 +45,7 @@ describe('userSerializer', () => {
user.role = role;
const expectedPayload = {
role,
role: roleSerializer(role),
};
expect(userSerializer(user)).toMatchObject(expectedPayload);
@@ -54,7 +55,10 @@ describe('userSerializer', () => {
user.permissions = [permissionOne, permissionTwo];
const expectedPayload = {
permissions: [permissionOne, permissionTwo],
permissions: [
permissionSerializer(permissionOne),
permissionSerializer(permissionTwo),
],
};
expect(userSerializer(user)).toMatchObject(expectedPayload);

View File

@@ -0,0 +1,25 @@
import { faker } from '@faker-js/faker';
import { createAppConfig } from './app-config.js';
import AppAuthClient from '../../src/models/app-auth-client';
const formattedAuthDefaults = {
oAuthRedirectUrl: faker.internet.url(),
instanceUrl: faker.internet.url(),
clientId: faker.string.uuid(),
clientSecret: faker.string.uuid(),
};
export const createAppAuthClient = async (params = {}) => {
params.name = params?.name || faker.person.fullName();
params.id = params?.id || faker.string.uuid();
params.appConfigId = params?.appConfigId || (await createAppConfig()).id;
params.active = params?.active ?? true;
params.formattedAuthDefaults =
params?.formattedAuthDefaults || formattedAuthDefaults;
const appAuthClient = await AppAuthClient.query()
.insert(params)
.returning('*');
return appAuthClient;
};

View File

@@ -0,0 +1,13 @@
import AppConfig from '../../src/models/app-config.js';
export const createAppConfig = async (params = {}) => {
const appConfigData = {
key: params?.key || 'gitlab',
};
const appConfig = await AppConfig.query()
.insert(appConfigData)
.returning('*');
return appConfig;
};

View File

@@ -0,0 +1,19 @@
const getAdminAppAuthClientMock = (appAuthClient) => {
return {
data: {
appConfigId: appAuthClient.appConfigId,
name: appAuthClient.name,
id: appAuthClient.id,
active: appAuthClient.active,
},
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'AppAuthClient',
},
};
};
export default getAdminAppAuthClientMock;

View File

@@ -0,0 +1,64 @@
const getPermissionsCatalogMock = async () => {
const data = {
actions: [
{
key: 'create',
label: 'Create',
subjects: ['Connection', 'Flow'],
},
{
key: 'read',
label: 'Read',
subjects: ['Connection', 'Execution', 'Flow'],
},
{
key: 'update',
label: 'Update',
subjects: ['Connection', 'Flow'],
},
{
key: 'delete',
label: 'Delete',
subjects: ['Connection', 'Flow'],
},
{
key: 'publish',
label: 'Publish',
subjects: ['Flow'],
},
],
conditions: [
{
key: 'isCreator',
label: 'Is creator',
},
],
subjects: [
{
key: 'Connection',
label: 'Connection',
},
{
key: 'Flow',
label: 'Flow',
},
{
key: 'Execution',
label: 'Execution',
},
],
};
return {
data: data,
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'Object',
},
};
};
export default getPermissionsCatalogMock;

View File

@@ -0,0 +1,33 @@
const getRoleMock = async (role, permissions) => {
const data = {
id: role.id,
key: role.key,
name: role.name,
isAdmin: role.isAdmin,
description: role.description,
createdAt: role.createdAt.toISOString(),
updatedAt: role.updatedAt.toISOString(),
permissions: permissions.map((permission) => ({
id: permission.id,
action: permission.action,
conditions: permission.conditions,
roleId: permission.roleId,
subject: permission.subject,
createdAt: permission.createdAt.toISOString(),
updatedAt: permission.updatedAt.toISOString(),
})),
};
return {
data: data,
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'Role',
},
};
};
export default getRoleMock;

View File

@@ -0,0 +1,26 @@
const getRolesMock = async (roles) => {
const data = roles.map((role) => {
return {
id: role.id,
key: role.key,
name: role.name,
isAdmin: role.isAdmin,
description: role.description,
createdAt: role.createdAt.toISOString(),
updatedAt: role.updatedAt.toISOString(),
};
});
return {
data: data,
meta: {
count: data.length,
currentPage: null,
isArray: true,
totalPages: null,
type: 'Role',
},
};
};
export default getRolesMock;

View File

@@ -1,4 +1,4 @@
const getSamlAuthProvidersMock = async (samlAuthProvider) => {
const getSamlAuthProviderMock = async (samlAuthProvider) => {
const data = {
active: samlAuthProvider.active,
certificate: samlAuthProvider.certificate,
@@ -26,4 +26,4 @@ const getSamlAuthProvidersMock = async (samlAuthProvider) => {
};
};
export default getSamlAuthProvidersMock;
export default getSamlAuthProviderMock;

View File

@@ -14,7 +14,6 @@ const getUserMock = (currentUser, role) => {
name: role.name,
updatedAt: role.updatedAt.toISOString(),
},
roleId: role.id,
trialExpiryDate: currentUser.trialExpiryDate.toISOString(),
updatedAt: currentUser.updatedAt.toISOString(),
},

View File

@@ -1,6 +1,7 @@
const getUsersMock = async (users, roles) => {
const data = users.map((user) => {
const role = roles.find((r) => r.id === user.roleId);
return {
createdAt: user.createdAt.toISOString(),
email: user.email,
@@ -16,8 +17,7 @@ const getUsersMock = async (users, roles) => {
name: role.name,
updatedAt: role.updatedAt.toISOString(),
}
: null, // Fallback to null if role not found
roleId: user.roleId,
: null,
trialExpiryDate: user.trialExpiryDate.toISOString(),
updatedAt: user.updatedAt.toISOString(),
};

View File

@@ -0,0 +1,18 @@
const infoMock = () => {
return {
data: {
isCloud: false,
isMation: false,
isEnterprise: true,
},
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'Object',
},
};
};
export default infoMock;

View File

@@ -0,0 +1,19 @@
const licenseMock = () => {
return {
data: {
expireAt: '2025-12-31T23:59:59Z',
id: '123',
name: 'license-name',
verified: true,
},
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'Object',
},
};
};
export default licenseMock;

View File

@@ -0,0 +1,32 @@
const getFlowMock = async (flow, steps) => {
const data = {
active: flow.active,
id: flow.id,
name: flow.name,
status: flow.active ? 'published' : 'draft',
steps: steps.map((step) => ({
appKey: step.appKey,
iconUrl: step.iconUrl,
id: step.id,
key: step.key,
parameters: step.parameters,
position: step.position,
status: step.status,
type: step.type,
webhookUrl: step.webhookUrl,
})),
};
return {
data: data,
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'Flow',
},
};
};
export default getFlowMock;

View File

@@ -0,0 +1,19 @@
const getAppAuthClientMock = (appAuthClient) => {
return {
data: {
name: appAuthClient.name,
id: appAuthClient.id,
appConfigId: appAuthClient.appConfigId,
active: appAuthClient.active,
},
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'AppAuthClient',
},
};
};
export default getAppAuthClientMock;

View File

@@ -0,0 +1,17 @@
const getPaddleInfoMock = async () => {
return {
data: {
sandbox: true,
vendorId: 'sampleVendorId',
},
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'Object',
},
};
};
export default getPaddleInfoMock;

View File

@@ -0,0 +1,22 @@
const getPaymentPlansMock = async () => {
return {
data: [
{
limit: '10,000',
name: '10k - monthly',
price: '€20',
productId: '47384',
quota: 10000,
},
],
meta: {
count: 1,
currentPage: null,
isArray: true,
totalPages: null,
type: 'Object',
},
};
};
export default getPaymentPlansMock;

View File

@@ -15,7 +15,6 @@ const getCurrentUserMock = (currentUser, role) => {
name: role.name,
updatedAt: role.updatedAt.toISOString(),
},
roleId: role.id,
trialExpiryDate: currentUser.trialExpiryDate.toISOString(),
updatedAt: currentUser.updatedAt.toISOString(),
},

View File

@@ -0,0 +1,14 @@
const getInvoicesMock = async (invoices) => {
return {
data: invoices,
meta: {
count: invoices.length,
currentPage: null,
isArray: true,
totalPages: null,
type: 'Object',
},
};
};
export default getInvoicesMock;

View File

@@ -73,6 +73,7 @@ export default defineConfig({
collapsible: true,
collapsed: true,
items: [
{ text: 'Triggers', link: '/apps/dropbox/triggers' },
{ text: 'Actions', link: '/apps/dropbox/actions' },
{ text: 'Connection', link: '/apps/dropbox/connection' },
],
@@ -252,12 +253,6 @@ export default defineConfig({
{ text: 'Connection', link: '/apps/openai/connection' },
],
},
{
text: 'PDFMonkey',
collapsible: true,
collapsed: true,
items: [{ text: 'Connection', link: '/apps/pdf-monkey/connection' }],
},
{
text: 'Pipedrive',
collapsible: true,

View File

@@ -3,6 +3,8 @@ favicon: /favicons/dropbox.svg
items:
- name: Create a folder
desc: Creates a new folder with the given parent folder and folder name.
- name: Create a text file
desc: Create a new text file from plain text content you specify.
- name: Rename a file
desc: Rename a file with the given file path and new name.
---

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