Compare commits

..

2 Commits

Author SHA1 Message Date
Rıdvan Akca
8a8be21d56 test: make better stack first when creating a connection 2024-01-16 12:37:06 +03:00
Rıdvan Akca
e5c4e18fd5 feat(better-stack): add better stack integration 2024-01-16 12:37:06 +03:00
104 changed files with 444 additions and 4746 deletions

View File

@@ -8,7 +8,7 @@
"version": "latest"
},
"ghcr.io/devcontainers/features/node:1": {
"version": 18
"version": 16
},
"ghcr.io/devcontainers/features/common-utils:1": {
"username": "vscode",

View File

@@ -33,6 +33,7 @@
"axios": "1.6.0",
"bcrypt": "^5.0.1",
"bullmq": "^3.0.0",
"copyfiles": "^2.4.1",
"cors": "^2.8.5",
"crypto-js": "^4.1.1",
"debug": "~2.6.9",
@@ -44,6 +45,7 @@
"graphql-middleware": "^6.1.15",
"graphql-shield": "^7.5.0",
"graphql-tools": "^8.2.0",
"graphql-type-json": "^0.3.2",
"handlebars": "^4.7.7",
"http-errors": "~1.6.3",
"http-proxy-agent": "^7.0.0",
@@ -66,6 +68,7 @@
"pluralize": "^8.0.0",
"raw-body": "^2.5.2",
"showdown": "^2.1.0",
"stripe": "^11.13.0",
"winston": "^3.7.1",
"xmlrpc": "^1.3.2"
},

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="200.000000pt" height="200.000000pt" viewBox="0 0 200.000000 200.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,200.000000) scale(0.100000,-0.100000)"
fill="#000" stroke="none">
<path d="M0 1000 l0 -1000 1000 0 1000 0 0 1000 0 1000 -1000 0 -1000 0 0
-1000z m1162 460 c14 -11 113 -184 232 -408 228 -429 231 -439 175 -486 -35
-30 -30 -29 -140 -15 -89 12 -123 25 -152 56 -9 11 -72 147 -140 304 -113 263
-124 284 -149 287 -14 2 -29 10 -32 17 -8 21 67 214 94 242 28 29 78 30 112 3z
m-340 -148 c10 -10 72 -175 139 -367 114 -325 121 -351 108 -374 -8 -14 -27
-32 -41 -41 -25 -13 -34 -12 -126 18 -55 18 -111 43 -125 56 -19 17 -40 67
-76 182 -36 112 -58 164 -73 176 l-22 16 27 99 c63 224 66 232 95 248 31 17
69 12 94 -13z m-314 -219 c16 -15 26 -59 56 -243 42 -262 43 -285 17 -300 -11
-5 -24 -10 -30 -10 -19 0 -140 114 -150 141 -7 20 -4 76 10 191 10 90 19 171
19 181 0 18 33 57 49 57 5 0 18 -8 29 -17z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -3,6 +3,18 @@ import isStillVerified from './is-still-verified.js';
export default {
fields: [
{
key: 'screenName',
label: 'Screen Name',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description:
'Screen name of your connection to be used on Automatisch UI.',
clickToCopy: false,
},
{
key: 'apiKey',
label: 'API Key',
@@ -11,7 +23,7 @@ export default {
readOnly: false,
value: null,
placeholder: null,
description: 'PDFMonkey API secret key of your account.',
description: 'Better Stack API key of your account.',
clickToCopy: false,
},
],

View File

@@ -0,0 +1,8 @@
import verifyCredentials from './verify-credentials.js';
const isStillVerified = async ($) => {
await verifyCredentials($);
return true;
};
export default isStillVerified;

View File

@@ -0,0 +1,10 @@
const verifyCredentials = async ($) => {
await $.http.get('/v2/metadata');
await $.auth.set({
screenName: $.auth.data.screenName,
apiKey: $.auth.data.apiKey,
});
};
export default verifyCredentials;

View File

@@ -0,0 +1,16 @@
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: 'Better Stack',
key: 'better-stack',
iconUrl: '{BASE_URL}/apps/better-stack/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/better-stack/connection',
supportsConnections: true,
baseUrl: 'https://betterstack.com',
apiBaseUrl: 'https://uptime.betterstack.com/api',
primaryColor: '000000',
beforeRequest: [addAuthHeader],
auth,
});

View File

@@ -1,6 +1,5 @@
import defineAction from '../../../../helpers/define-action.js';
import base64ToString from './transformers/base64-to-string.js';
import capitalize from './transformers/capitalize.js';
import extractEmailAddress from './transformers/extract-email-address.js';
import extractNumber from './transformers/extract-number.js';
@@ -9,12 +8,10 @@ import lowercase from './transformers/lowercase.js';
import markdownToHtml from './transformers/markdown-to-html.js';
import pluralize from './transformers/pluralize.js';
import replace from './transformers/replace.js';
import stringToBase64 from './transformers/string-to-base64.js';
import trimWhitespace from './transformers/trim-whitespace.js';
import useDefaultValue from './transformers/use-default-value.js';
const transformers = {
base64ToString,
capitalize,
extractEmailAddress,
extractNumber,
@@ -23,7 +20,6 @@ const transformers = {
markdownToHtml,
pluralize,
replace,
stringToBase64,
trimWhitespace,
useDefaultValue,
};
@@ -41,7 +37,6 @@ export default defineAction({
required: true,
variables: true,
options: [
{ label: 'Base64 to String', value: 'base64ToString' },
{ label: 'Capitalize', value: 'capitalize' },
{ label: 'Convert HTML to Markdown', value: 'htmlToMarkdown' },
{ label: 'Convert Markdown to HTML', value: 'markdownToHtml' },
@@ -50,7 +45,6 @@ export default defineAction({
{ label: 'Lowercase', value: 'lowercase' },
{ label: 'Pluralize', value: 'pluralize' },
{ label: 'Replace', value: 'replace' },
{ label: 'String to Base64', value: 'stringToBase64' },
{ label: 'Trim Whitespace', value: 'trimWhitespace' },
{ label: 'Use Default Value', value: 'useDefaultValue' },
],

View File

@@ -1,8 +0,0 @@
const base64ToString = ($) => {
const input = $.step.parameters.input;
const decodedString = Buffer.from(input, 'base64').toString('utf8');
return decodedString;
};
export default base64ToString;

View File

@@ -1,8 +0,0 @@
const stringtoBase64 = ($) => {
const input = $.step.parameters.input;
const base64String = Buffer.from(input).toString('base64');
return base64String;
};
export default stringtoBase64;

View File

@@ -1,4 +1,3 @@
import base64ToString from './text/base64-to-string.js';
import capitalize from './text/capitalize.js';
import extractEmailAddress from './text/extract-email-address.js';
import extractNumber from './text/extract-number.js';
@@ -7,7 +6,6 @@ import lowercase from './text/lowercase.js';
import markdownToHtml from './text/markdown-to-html.js';
import pluralize from './text/pluralize.js';
import replace from './text/replace.js';
import stringToBase64 from './text/string-to-base64.js';
import trimWhitespace from './text/trim-whitespace.js';
import useDefaultValue from './text/use-default-value.js';
import performMathOperation from './numbers/perform-math-operation.js';
@@ -17,7 +15,6 @@ import formatPhoneNumber from './numbers/format-phone-number.js';
import formatDateTime from './date-time/format-date-time.js';
const options = {
base64ToString,
capitalize,
extractEmailAddress,
extractNumber,
@@ -26,7 +23,6 @@ const options = {
markdownToHtml,
pluralize,
replace,
stringToBase64,
trimWhitespace,
useDefaultValue,
performMathOperation,

View File

@@ -1,12 +0,0 @@
const base64ToString = [
{
label: 'Input',
key: 'input',
type: 'string',
required: true,
description: 'Text that will be converted from Base64 to string.',
variables: true,
},
];
export default base64ToString;

View File

@@ -1,12 +0,0 @@
const stringToBase64 = [
{
label: 'Input',
key: 'input',
type: 'string',
required: true,
description: 'Text that will be converted to Base64.',
variables: true,
},
];
export default stringToBase64;

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 112 KiB

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,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,4 +0,0 @@
import listTemplates from './list-templates/index.js';
import listWorkspaces from './list-workspaces/index.js';
export default [listTemplates, listWorkspaces];

View File

@@ -1,39 +0,0 @@
export default {
name: 'List templates',
key: 'listTemplates',
async run($) {
const templates = {
data: [],
};
const workspaceId = $.step.parameters.workspaceId;
let next = false;
const params = {
page: 'all',
'q[workspace_id]': workspaceId,
};
if (!workspaceId) {
return templates;
}
do {
const { data } = await $.http.get('/v1/document_template_cards', params);
next = data.meta.next_page;
if (!data?.document_template_cards?.length) {
return;
}
for (const template of data.document_template_cards) {
templates.data.push({
value: template.id,
name: template.identifier,
});
}
} while (next);
return templates;
},
};

View File

@@ -1,29 +0,0 @@
export default {
name: 'List workspaces',
key: 'listWorkspaces',
async run($) {
const workspaces = {
data: [],
};
let next = false;
do {
const { data } = await $.http.get('/v1/workspace_cards');
next = data.meta.next_page;
if (!data?.workspace_cards?.length) {
return;
}
for (const workspace of data.workspace_cards) {
workspaces.data.push({
value: workspace.id,
name: workspace.identifier,
});
}
} while (next);
return workspaces;
},
};

View File

@@ -1,20 +0,0 @@
import defineApp from '../../helpers/define-app.js';
import addAuthHeader from './common/add-auth-header.js';
import auth from './auth/index.js';
import triggers from './triggers/index.js';
import dynamicData from './dynamic-data/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: '376794',
beforeRequest: [addAuthHeader],
auth,
triggers,
dynamicData,
});

View File

@@ -1,99 +0,0 @@
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'Documents Generated',
key: 'documentsGenerated',
pollInterval: 15,
description:
'Triggers upon the successful completion of document generation.',
arguments: [
{
label: 'Workspace',
key: 'workspaceId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listWorkspaces',
},
],
},
},
{
label: 'Templates',
key: 'templateIds',
type: 'dynamic',
required: false,
description: 'Apply this trigger exclusively for particular templates.',
fields: [
{
label: 'Template',
key: 'templateId',
type: 'dropdown',
required: false,
depensOn: ['parameters.workspaceId'],
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listTemplates',
},
{
name: 'parameters.workspaceId',
value: '{parameters.workspaceId}',
},
],
},
},
],
},
],
async run($) {
const workspaceId = $.step.parameters.workspaceId;
const templateIds = $.step.parameters.templateIds;
const allTemplates = templateIds
.map((templateId) => templateId.templateId)
.join(',');
const params = {
'page[size]': 100,
'q[workspace_id]': workspaceId,
'q[status]': 'success',
};
if (!templateIds.length) {
params['q[document_template_id]'] = allTemplates;
}
let next = false;
do {
const { data } = await $.http.get('/v1/document_cards', { params });
if (!data?.document_cards?.length) {
return;
}
next = data.meta.next_page;
for (const document of data.document_cards) {
$.pushTriggerItem({
raw: document,
meta: {
internalId: document.id,
},
});
}
} while (next);
},
});

View File

@@ -1,3 +0,0 @@
import documentsGenerated from './documents-generated/index.js';
export default [documentsGenerated];

View File

@@ -18,9 +18,7 @@ const port = process.env.PORT || '3000';
const serveWebAppSeparately =
process.env.SERVE_WEB_APP_SEPARATELY === 'true' ? true : false;
let apiUrl = new URL(
process.env.API_URL || `${protocol}://${host}:${port}`
).toString();
let apiUrl = new URL(`${protocol}://${host}:${port}`).toString();
apiUrl = apiUrl.substring(0, apiUrl.length - 1);
// use apiUrl by default, which has less priority over the following cases
@@ -90,10 +88,6 @@ const appConfig = {
licenseKey: process.env.LICENSE_KEY,
sentryDsn: process.env.SENTRY_DSN,
CI: process.env.CI === 'true',
disableNotificationsPage: process.env.DISABLE_NOTIFICATIONS_PAGE === 'true',
disableFavicon: process.env.DISABLE_FAVICON === 'true',
additionalDrawerLink: process.env.ADDITIONAL_DRAWER_LINK,
additionalDrawerLinkText: process.env.ADDITIONAL_DRAWER_LINK_TEXT,
};
if (!appConfig.encryptionKey) {

View File

@@ -1,10 +0,0 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import SamlAuthProvider from '../../../../../models/saml-auth-provider.ee.js';
export default async (request, response) => {
const samlAuthProvider = await SamlAuthProvider.query()
.findById(request.params.samlAuthProviderId)
.throwIfNotFound();
renderObject(response, samlAuthProvider);
};

View File

@@ -1,34 +0,0 @@
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 { 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 * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/saml-auth-provider/:samlAuthProviderId', () => {
let samlAuthProvider, currentUser, token;
beforeEach(async () => {
const role = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: role.id });
samlAuthProvider = await createSamlAuthProvider();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return saml auth provider with specified id', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const response = await request(app)
.get(`/api/v1/admin/saml-auth-providers/${samlAuthProvider.id}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getSamlAuthProviderMock(samlAuthProvider);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -1,11 +0,0 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import SamlAuthProvider from '../../../../../models/saml-auth-provider.ee.js';
export default async (request, response) => {
const samlAuthProviders = await SamlAuthProvider.query().orderBy(
'created_at',
'desc'
);
renderObject(response, samlAuthProviders);
};

View File

@@ -1,39 +0,0 @@
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 { 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 * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/saml-auth-providers', () => {
let samlAuthProviderOne, samlAuthProviderTwo, currentUser, token;
beforeEach(async () => {
const role = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: role.id });
samlAuthProviderOne = await createSamlAuthProvider();
samlAuthProviderTwo = await createSamlAuthProvider();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return saml auth providers', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const response = await request(app)
.get('/api/v1/admin/saml-auth-providers')
.set('Authorization', token)
.expect(200);
const expectedPayload = await getSamlAuthProvidersMock([
samlAuthProviderTwo,
samlAuthProviderOne,
]);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -1,6 +0,0 @@
import appConfig from '../../../../config/app.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
renderObject(response, { version: appConfig.version });
};

View File

@@ -1,26 +0,0 @@
import { describe, it, expect } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
describe('GET /api/v1/automatisch/version', () => {
it('should return Automatisch version', async () => {
const response = await request(app)
.get('/api/v1/automatisch/version')
.expect(200);
const expectedPayload = {
data: {
version: '0.10.0',
},
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'Object',
},
};
expect(response.body).toEqual(expectedPayload);
});
});

View File

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

View File

@@ -1,26 +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 getCurrentUserMock from '../../../../../test/mocks/rest/api/v1/users/get-current-user';
describe('GET /api/v1/users/me', () => {
let role, currentUser, token;
beforeEach(async () => {
currentUser = await createUser();
role = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id);
});
it('should return current user info', async () => {
const response = await request(app)
.get('/api/v1/users/me')
.set('Authorization', token)
.expect(200);
const expectedPayload = getCurrentUserMock(currentUser, role);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -1,12 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const inTrial = await request.currentUser.inTrial();
const trialInfo = {
inTrial,
expireAt: request.currentUser.trialExpiryDate,
};
renderObject(response, trialInfo);
};

View File

@@ -1,38 +0,0 @@
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 getUserTrialMock from '../../../../../test/mocks/rest/api/v1/users/get-user-trial.js';
import appConfig from '../../../../config/app.js';
import { DateTime } from 'luxon';
import User from '../../../../models/user.js';
describe('GET /api/v1/users/:userId/trial', () => {
let user, token;
beforeEach(async () => {
const trialExpiryDate = DateTime.now().plus({ days: 30 }).toISODate();
user = await createUser({ trialExpiryDate });
token = createAuthTokenByUserId(user.id);
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
});
describe('should return in trial, active subscription and expire at info', () => {
beforeEach(async () => {
vi.spyOn(User.prototype, 'inTrial').mockResolvedValue(false);
vi.spyOn(User.prototype, 'hasActiveSubscription').mockResolvedValue(true);
});
it('should return null', async () => {
const response = await request(app)
.get(`/api/v1/users/${user.id}/trial`)
.set('Authorization', token)
.expect(200);
const expectedResponsePayload = await getUserTrialMock(user);
expect(response.body).toEqual(expectedResponsePayload);
});
});
});

View File

@@ -1,16 +0,0 @@
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,
})
.findById(request.params.userId)
.throwIfNotFound();
renderObject(response, user);
};

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,18 +0,0 @@
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,
})
.orderBy('full_name', 'asc');
const users = await paginateRest(usersQuery, request.query.page);
renderObject(response, users);
};

View File

@@ -1,56 +0,0 @@
import { 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';
describe('GET /api/v1/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,
});
currentUser = await createUser({
roleId: currentUserRole.id,
fullName: 'Current User',
});
anotherUserRole = await createRole({
key: 'anotherUser',
name: 'Another user role',
});
anotherUser = await createUser({
roleId: anotherUserRole.id,
fullName: 'Another User',
});
token = createAuthTokenByUserId(currentUser.id);
});
it('should return users data', async () => {
const response = await request(app)
.get('/api/v1/users')
.set('Authorization', token)
.expect(200);
const expectedResponsePayload = await getUsersMock(
[anotherUser, currentUser],
[anotherUserRole, currentUserRole]
);
expect(response.body).toEqual(expectedResponsePayload);
});
});

View File

@@ -1,3 +0,0 @@
export default async (request, response) => {
response.status(200).end();
};

View File

@@ -1,9 +0,0 @@
import { describe, it } from 'vitest';
import request from 'supertest';
import app from '../../app.js';
describe('GET /healthcheck', () => {
it('should return 200 response with version data', async () => {
await request(app).get('/healthcheck').expect(200);
});
});

View File

@@ -1,10 +1,7 @@
import appConfig from '../../config/app.js';
import User from '../../models/user.js';
import Role from '../../models/role.js';
const registerUser = async (_parent, params) => {
if (!appConfig.isCloud) return;
const { fullName, email, password } = params.input;
const existingUser = await User.query().findOne({

View File

@@ -1,17 +1,9 @@
import appConfig from '../../config/app.js';
import { hasValidLicense } from '../../helpers/license.ee.js';
import Config from '../../models/config.js';
const getConfig = async (_parent, params) => {
if (!(await hasValidLicense())) return {};
const defaultConfig = {
disableNotificationsPage: appConfig.disableNotificationsPage,
disableFavicon: appConfig.disableFavicon,
additionalDrawerLink: appConfig.additionalDrawerLink,
additionalDrawerLinkText: appConfig.additionalDrawerLinkText,
};
const configQuery = Config.query();
if (Array.isArray(params.keys)) {
@@ -26,7 +18,7 @@ const getConfig = async (_parent, params) => {
computedConfig[key] = value?.data;
return computedConfig;
}, defaultConfig);
}, {});
};
export default getConfig;

View File

@@ -2,7 +2,6 @@ import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../app';
import { createConfig } from '../../../test/factories/config';
import appConfig from '../../config/app';
import * as license from '../../helpers/license.ee';
describe('graphQL getConfig query', () => {
@@ -57,10 +56,6 @@ describe('graphQL getConfig query', () => {
[configOne.key]: configOne.value.data,
[configTwo.key]: configTwo.value.data,
[configThree.key]: configThree.value.data,
disableNotificationsPage: false,
disableFavicon: false,
additionalDrawerLink: undefined,
additionalDrawerLinkText: undefined,
},
},
};
@@ -87,48 +82,6 @@ describe('graphQL getConfig query', () => {
getConfig: {
[configOne.key]: configOne.value.data,
[configTwo.key]: configTwo.value.data,
disableNotificationsPage: false,
disableFavicon: false,
additionalDrawerLink: undefined,
additionalDrawerLinkText: undefined,
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
describe('and with different defaults', () => {
beforeEach(async () => {
vi.spyOn(appConfig, 'disableNotificationsPage', 'get').mockReturnValue(
true
);
vi.spyOn(appConfig, 'disableFavicon', 'get').mockReturnValue(true);
vi.spyOn(appConfig, 'additionalDrawerLink', 'get').mockReturnValue(
'https://automatisch.io'
);
vi.spyOn(appConfig, 'additionalDrawerLinkText', 'get').mockReturnValue(
'Automatisch'
);
});
it('should return custom config', async () => {
const response = await request(app)
.post('/graphql')
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getConfig: {
[configOne.key]: configOne.value.data,
[configTwo.key]: configTwo.value.data,
[configThree.key]: configThree.value.data,
disableNotificationsPage: true,
disableFavicon: true,
additionalDrawerLink: 'https://automatisch.io',
additionalDrawerLinkText: 'Automatisch',
},
},
};

View File

@@ -20,8 +20,7 @@ export const isAuthenticated = async (_parent, _args, req) => {
.withGraphFetched({
role: true,
permissions: true,
})
.throwIfNotFound();
});
return true;
} catch (error) {
@@ -29,14 +28,6 @@ export const isAuthenticated = async (_parent, _args, req) => {
}
};
export const authenticateUser = async (request, response, next) => {
if (await isAuthenticated(null, null, request)) {
next();
} else {
return response.status(401).end();
}
};
const isAuthenticatedRule = rule()(isAuthenticated);
export const authenticationRules = {

View File

@@ -1,8 +1,11 @@
import { describe, it, expect } from 'vitest';
import { describe, it, expect, vi } from 'vitest';
import { allow } from 'graphql-shield';
import jwt from 'jsonwebtoken';
import User from '../models/user.js';
import { isAuthenticated, authenticationRules } from './authentication.js';
import { createUser } from '../../test/factories/user.js';
import createAuthTokenByUserId from '../helpers/create-auth-token-by-user-id.js';
vi.mock('jsonwebtoken');
vi.mock('../models/user.js');
describe('isAuthenticated', () => {
it('should return false if no token is provided', async () => {
@@ -11,26 +14,29 @@ describe('isAuthenticated', () => {
});
it('should return false if token is invalid', async () => {
jwt.verify.mockImplementation(() => {
throw new Error('invalid token');
});
const req = { headers: { authorization: 'invalidToken' } };
expect(await isAuthenticated(null, null, req)).toBe(false);
});
it('should return true if token is valid and there is a user', async () => {
const user = await createUser();
const token = createAuthTokenByUserId(user.id);
it('should return true if token is valid', async () => {
jwt.verify.mockReturnValue({ userId: '123' });
const req = { headers: { authorization: token } };
User.query.mockReturnValue({
findById: vi.fn().mockReturnValue({
leftJoinRelated: vi.fn().mockReturnThis(),
withGraphFetched: vi
.fn()
.mockResolvedValue({ id: '123', role: {}, permissions: {} }),
}),
});
const req = { headers: { authorization: 'validToken' } };
expect(await isAuthenticated(null, null, req)).toBe(true);
});
it('should return false if token is valid and but there is no user', async () => {
const user = await createUser();
const token = createAuthTokenByUserId(user.id);
await user.$query().delete();
const req = { headers: { authorization: token } };
expect(await isAuthenticated(null, null, req)).toBe(false);
});
});
describe('authentication rules', () => {

View File

@@ -1,33 +0,0 @@
const authorizationList = {
'GET /api/v1/users/:userId': {
action: 'read',
subject: 'User',
},
'GET /api/v1/users/': {
action: 'read',
subject: 'User',
},
};
export const authorizeUser = async (request, response, next) => {
const currentRoute =
request.method + ' ' + request.baseUrl + request.route.path;
const currentRouteRule = authorizationList[currentRoute];
try {
request.currentUser.can(currentRouteRule.action, currentRouteRule.subject);
next();
} catch (error) {
return response.status(403).end();
}
};
export const authorizeAdmin = async (request, response, next) => {
const role = await request.currentUser.$relatedQuery('role');
if (role?.isAdmin) {
next();
} else {
return response.status(403).end();
}
};

View File

@@ -1,11 +0,0 @@
import appConfig from '../config/app.js';
export const checkIsCloud = async (request, response, next) => {
if (appConfig.isCloud) {
next();
} else {
return response.status(404).end();
}
};
export default checkIsCloud;

View File

@@ -1,9 +0,0 @@
import { hasValidLicense } from './license.ee.js';
export const checkIsEnterprise = async (request, response, next) => {
if (await hasValidLicense()) {
next();
} else {
return response.status(404).end();
}
};

View File

@@ -1,9 +1,6 @@
import path from 'path';
import fs from 'fs';
import handlebars from 'handlebars';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
import * as path from 'path';
import * as fs from 'fs';
import * as handlebars from 'handlebars';
const compileEmail = (emailPath, replacements = {}) => {
const filePath = path.join(__dirname, `../views/emails/${emailPath}.ee.hbs`);

View File

@@ -4,8 +4,8 @@ import appConfig from '../config/app.js';
const levels = {
error: 0,
warn: 1,
http: 2,
info: 3,
info: 2,
http: 3,
debug: 4,
};

View File

@@ -1,25 +0,0 @@
const paginateRest = async (query, page) => {
const pageSize = 10;
page = parseInt(page, 10);
if (isNaN(page) || page < 1) {
page = 1;
}
const [records, count] = await Promise.all([
query.limit(pageSize).offset((page - 1) * pageSize),
query.resultSize(),
]);
return {
pageInfo: {
currentPage: page,
totalPages: Math.ceil(count / pageSize),
},
totalCount: count,
records,
};
};
export default paginateRest;

View File

@@ -1,44 +0,0 @@
import serializers from '../serializers/index.js';
const isPaginated = (object) =>
object?.pageInfo &&
object?.totalCount !== undefined &&
Array.isArray(object?.records);
const isArray = (object) =>
Array.isArray(object) || Array.isArray(object?.records);
const totalCount = (object) =>
isPaginated(object) ? object.totalCount : isArray(object) ? object.length : 1;
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.constructor.name;
const serializer = serializers[type];
if (serializer) {
data = Array.isArray(data)
? data.map((item) => serializer(item))
: serializer(data);
}
const computedPayload = {
data,
meta: {
type,
count: totalCount(object),
isArray: isArray(object),
currentPage: isPaginated(object) ? object.pageInfo.currentPage : null,
totalPages: isPaginated(object) ? object.pageInfo.totalPages : null,
},
};
return response.json(computedPayload);
};
export { renderObject };

View File

@@ -15,7 +15,7 @@ const webUIHandler = async (app) => {
app.use(express.static(webBuildPath));
app.get('*', (_req, res) => {
res.set('Content-Security-Policy', 'frame-ancestors \'none\';');
res.set('Content-Security-Policy', 'frame-ancestors: none;');
res.set('X-Frame-Options', 'DENY');
res.sendFile(indexHtml);

View File

@@ -1,8 +0,0 @@
import { Router } from 'express';
import versionAction from '../../../controllers/api/v1/automatisch/version.js';
const router = Router();
router.get('/version', versionAction);
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 +0,0 @@
import { Router } from 'express';
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';
const router = Router();
router.get('/', authenticateUser, authorizeUser, getUsersAction);
router.get('/me', authenticateUser, getCurrentUserAction);
router.get('/:userId', authenticateUser, authorizeUser, getUserAction);
router.get(
'/:userId/trial',
authenticateUser,
checkIsCloud,
getUserTrialAction
);
export default router;

View File

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

View File

@@ -2,19 +2,11 @@ import { Router } from 'express';
import graphQLInstance from '../helpers/graphql-instance.js';
import webhooksRouter from './webhooks.js';
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';
const router = Router();
router.use('/graphql', graphQLInstance);
router.use('/webhooks', webhooksRouter);
router.use('/paddle', paddleRouter);
router.use('/healthcheck', healthcheckRouter);
router.use('/api/v1/automatisch', automatischRouter);
router.use('/api/v1/users', usersRouter);
router.use('/api/v1/admin/saml-auth-providers', samlAuthProvidersRouter);
export default router;

View File

@@ -1,13 +0,0 @@
import userSerializer from './user.js';
import roleSerializer from './role.js';
import permissionSerializer from './permission.js';
import samlAuthProviderSerializer from './saml-auth-provider.ee.js';
const serializers = {
User: userSerializer,
Role: roleSerializer,
Permission: permissionSerializer,
SamlAuthProvider: samlAuthProviderSerializer,
};
export default serializers;

View File

@@ -1,13 +0,0 @@
const permissionSerializer = (permission) => {
return {
id: permission.id,
roleId: permission.roleId,
action: permission.action,
subject: permission.subject,
conditions: permission.conditions,
createdAt: permission.createdAt,
updatedAt: permission.updatedAt,
};
};
export default permissionSerializer;

View File

@@ -1,25 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { createPermission } from '../../test/factories/permission';
import permissionSerializer from './permission';
describe('permissionSerializer', () => {
let permission;
beforeEach(async () => {
permission = await createPermission();
});
it('should return permission data', async () => {
const expectedPayload = {
id: permission.id,
roleId: permission.roleId,
action: permission.action,
subject: permission.subject,
conditions: permission.conditions,
createdAt: permission.createdAt,
updatedAt: permission.updatedAt,
};
expect(permissionSerializer(permission)).toEqual(expectedPayload);
});
});

View File

@@ -1,13 +0,0 @@
const roleSerializer = (role) => {
return {
id: role.id,
name: role.name,
key: role.key,
description: role.description,
createdAt: role.createdAt,
updatedAt: role.updatedAt,
isAdmin: role.isAdmin,
};
};
export default roleSerializer;

View File

@@ -1,25 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { createRole } from '../../test/factories/role';
import roleSerializer from './role';
describe('roleSerializer', () => {
let role;
beforeEach(async () => {
role = await createRole();
});
it('should return role data', async () => {
const expectedPayload = {
id: role.id,
name: role.name,
key: role.key,
description: role.description,
createdAt: role.createdAt,
updatedAt: role.updatedAt,
isAdmin: role.isAdmin,
};
expect(roleSerializer(role)).toEqual(expectedPayload);
});
});

View File

@@ -1,18 +0,0 @@
const samlAuthProviderSerializer = (samlAuthProvider) => {
return {
id: samlAuthProvider.id,
name: samlAuthProvider.name,
certificate: samlAuthProvider.certificate,
signatureAlgorithm: samlAuthProvider.signatureAlgorithm,
issuer: samlAuthProvider.issuer,
entryPoint: samlAuthProvider.entryPoint,
firstnameAttributeName: samlAuthProvider.firstnameAttributeName,
surnameAttributeName: samlAuthProvider.surnameAttributeName,
emailAttributeName: samlAuthProvider.emailAttributeName,
roleAttributeName: samlAuthProvider.roleAttributeName,
active: samlAuthProvider.active,
defaultRoleId: samlAuthProvider.defaultRoleId,
};
};
export default samlAuthProviderSerializer;

View File

@@ -1,32 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { createSamlAuthProvider } from '../../test/factories/saml-auth-provider.ee.js';
import samlAuthProviderSerializer from './saml-auth-provider.ee.js';
describe('samlAuthProviderSerializer', () => {
let samlAuthProvider;
beforeEach(async () => {
samlAuthProvider = await createSamlAuthProvider();
});
it('should return saml auth provider data', async () => {
const expectedPayload = {
id: samlAuthProvider.id,
name: samlAuthProvider.name,
certificate: samlAuthProvider.certificate,
signatureAlgorithm: samlAuthProvider.signatureAlgorithm,
issuer: samlAuthProvider.issuer,
entryPoint: samlAuthProvider.entryPoint,
firstnameAttributeName: samlAuthProvider.firstnameAttributeName,
surnameAttributeName: samlAuthProvider.surnameAttributeName,
emailAttributeName: samlAuthProvider.emailAttributeName,
roleAttributeName: samlAuthProvider.roleAttributeName,
active: samlAuthProvider.active,
defaultRoleId: samlAuthProvider.defaultRoleId,
};
expect(samlAuthProviderSerializer(samlAuthProvider)).toEqual(
expectedPayload
);
});
});

View File

@@ -1,32 +0,0 @@
import roleSerializer from './role.js';
import permissionSerializer from './permission.js';
import appConfig from '../config/app.js';
const userSerializer = (user) => {
let userData = {
id: user.id,
email: user.email,
createdAt: user.createdAt,
updatedAt: user.updatedAt,
fullName: user.fullName,
roleId: user.roleId,
};
if (user.role) {
userData.role = roleSerializer(user.role);
}
if (user.permissions) {
userData.permissions = user.permissions.map((permission) =>
permissionSerializer(permission)
);
}
if (appConfig.isCloud && user.trialExpiryDate) {
userData.trialExpiryDate = user.trialExpiryDate;
}
return userData;
};
export default userSerializer;

View File

@@ -1,76 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import { DateTime } from 'luxon';
import appConfig from '../config/app';
import { createUser } from '../../test/factories/user';
import { createPermission } from '../../test/factories/permission';
import userSerializer from './user';
describe('userSerializer', () => {
let user, role, permissionOne, permissionTwo;
beforeEach(async () => {
user = await createUser();
role = await user.$relatedQuery('role');
permissionOne = await createPermission({
roleId: role.id,
action: 'read',
subject: 'User',
});
permissionTwo = await createPermission({
roleId: role.id,
action: 'read',
subject: 'Role',
});
});
it('should return user data', async () => {
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false);
const expectedPayload = {
createdAt: user.createdAt,
email: user.email,
fullName: user.fullName,
id: user.id,
roleId: user.roleId,
updatedAt: user.updatedAt,
};
expect(userSerializer(user)).toEqual(expectedPayload);
});
it('should return user data with the role', async () => {
user.role = role;
const expectedPayload = {
role,
};
expect(userSerializer(user)).toMatchObject(expectedPayload);
});
it('should return user data with the permissions', async () => {
user.permissions = [permissionOne, permissionTwo];
const expectedPayload = {
permissions: [permissionOne, permissionTwo],
};
expect(userSerializer(user)).toMatchObject(expectedPayload);
});
it('should return user data with trial expiry date', async () => {
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
await user.$query().patch({
trialExpiryDate: DateTime.now().plus({ days: 30 }).toISODate(),
});
const expectedPayload = {
trialExpiryDate: user.trialExpiryDate,
};
expect(userSerializer(user)).toMatchObject(expectedPayload);
});
});

View File

@@ -1,5 +1,4 @@
import { faker } from '@faker-js/faker';
import Config from '../../src/models/config';
export const createConfig = async (params = {}) => {
const configData = {
@@ -7,7 +6,10 @@ export const createConfig = async (params = {}) => {
value: params?.value || { data: 'sampleConfig' },
};
const config = await Config.query().insert(configData).returning('*');
const [config] = await global.knex
.table('config')
.insert(configData)
.returning('*');
return config;
};

View File

@@ -1,6 +1,5 @@
import appConfig from '../../src/config/app';
import { AES } from 'crypto-js';
import Connection from '../../src/models/connection';
export const createConnection = async (params = {}) => {
params.key = params?.key || 'deepl';
@@ -17,7 +16,10 @@ export const createConnection = async (params = {}) => {
appConfig.encryptionKey
).toString();
const connection = await Connection.query().insert(params).returning('*');
const [connection] = await global.knex
.table('connections')
.insert(params)
.returning('*');
return connection;
};

View File

@@ -1,4 +1,3 @@
import ExecutionStep from '../../src/models/execution-step';
import { createExecution } from './execution';
import { createStep } from './step';
@@ -9,7 +8,8 @@ export const createExecutionStep = async (params = {}) => {
params.dataIn = params?.dataIn || { dataIn: 'dataIn' };
params.dataOut = params?.dataOut || { dataOut: 'dataOut' };
const executionStep = await ExecutionStep.query()
const [executionStep] = await global.knex
.table('executionSteps')
.insert(params)
.returning('*');

View File

@@ -1,4 +1,3 @@
import Execution from '../../src/models/execution';
import { createFlow } from './flow';
export const createExecution = async (params = {}) => {
@@ -7,7 +6,10 @@ export const createExecution = async (params = {}) => {
params.createdAt = params?.createdAt || new Date().toISOString();
params.updatedAt = params?.updatedAt || new Date().toISOString();
const execution = await Execution.query().insert(params).returning('*');
const [execution] = await global.knex
.table('executions')
.insert(params)
.returning('*');
return execution;
};

View File

@@ -1,4 +1,3 @@
import Flow from '../../src/models/flow';
import { createUser } from './user';
export const createFlow = async (params = {}) => {
@@ -7,7 +6,7 @@ export const createFlow = async (params = {}) => {
params.createdAt = params?.createdAt || new Date().toISOString();
params.updatedAt = params?.updatedAt || new Date().toISOString();
const flow = await Flow.query().insert(params).returning('*');
const [flow] = await global.knex.table('flows').insert(params).returning('*');
return flow;
};

View File

@@ -1,4 +1,3 @@
import Permission from '../../src/models/permission';
import { createRole } from './role';
export const createPermission = async (params = {}) => {
@@ -7,7 +6,10 @@ export const createPermission = async (params = {}) => {
params.subject = params?.subject || 'User';
params.conditions = params?.conditions || ['isCreator'];
const permission = await Permission.query().insert(params).returning('*');
const [permission] = await global.knex
.table('permissions')
.insert(params)
.returning('*');
return permission;
};

View File

@@ -1,10 +1,8 @@
import Role from '../../src/models/role';
export const createRole = async (params = {}) => {
params.name = params?.name || 'Viewer';
params.key = params?.key || 'viewer';
const role = await Role.query().insert(params).returning('*');
const [role] = await global.knex.table('roles').insert(params).returning('*');
return role;
};

View File

@@ -1,33 +0,0 @@
import { createRole } from './role';
import SamlAuthProvider from '../../src/models/saml-auth-provider.ee.js';
export const createSamlAuthProvider = async (params = {}) => {
params.name = params?.name || 'Keycloak SAML';
params.certificate = params?.certificate || 'certificate';
params.signatureAlgorithm = params?.signatureAlgorithm || 'sha512';
params.entryPoint =
params?.entryPoint ||
'https://example.com/auth/realms/automatisch/protocol/saml';
params.issuer = params?.issuer || 'automatisch-client';
params.firstnameAttributeName =
params?.firstnameAttributeName || 'urn:oid:2.1.1.42';
params.surnameAttributeName =
params?.surnameAttributeName || 'urn:oid:2.1.1.4';
params.emailAttributeName =
params?.emailAttributeName || 'urn:oid:1.1.2342.19200300.100.1.1';
params.roleAttributeName = params?.roleAttributeName || 'Role';
params.defaultRoleId = params?.defaultRoleId || (await createRole()).id;
params.active = params?.active || true;
const samlAuthProvider = await SamlAuthProvider.query()
.insert(params)
.returning('*');
return samlAuthProvider;
};

View File

@@ -1,4 +1,3 @@
import Step from '../../src/models/step';
import { createFlow } from './flow';
export const createStep = async (params = {}) => {
@@ -17,7 +16,7 @@ export const createStep = async (params = {}) => {
params.appKey =
params?.appKey || (params.type === 'action' ? 'deepl' : 'webhook');
const step = await Step.query().insert(params).returning('*');
const [step] = await global.knex.table('steps').insert(params).returning('*');
return step;
};

View File

@@ -1,6 +1,5 @@
import { createRole } from './role';
import { faker } from '@faker-js/faker';
import User from '../../src/models/user';
export const createUser = async (params = {}) => {
params.roleId = params?.roleId || (await createRole()).id;
@@ -8,7 +7,7 @@ export const createUser = async (params = {}) => {
params.email = params?.email || faker.internet.email();
params.password = params?.password || faker.internet.password();
const user = await User.query().insert(params).returning('*');
const [user] = await global.knex.table('users').insert(params).returning('*');
return user;
};

View File

@@ -1,29 +0,0 @@
const getSamlAuthProvidersMock = async (samlAuthProvider) => {
const data = {
active: samlAuthProvider.active,
certificate: samlAuthProvider.certificate,
defaultRoleId: samlAuthProvider.defaultRoleId,
emailAttributeName: samlAuthProvider.emailAttributeName,
entryPoint: samlAuthProvider.entryPoint,
firstnameAttributeName: samlAuthProvider.firstnameAttributeName,
id: samlAuthProvider.id,
issuer: samlAuthProvider.issuer,
name: samlAuthProvider.name,
roleAttributeName: samlAuthProvider.roleAttributeName,
signatureAlgorithm: samlAuthProvider.signatureAlgorithm,
surnameAttributeName: samlAuthProvider.surnameAttributeName,
};
return {
data: data,
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'SamlAuthProvider',
},
};
};
export default getSamlAuthProvidersMock;

View File

@@ -1,31 +0,0 @@
const getSamlAuthProvidersMock = async (samlAuthProviders) => {
const data = samlAuthProviders.map((samlAuthProvider) => {
return {
active: samlAuthProvider.active,
certificate: samlAuthProvider.certificate,
defaultRoleId: samlAuthProvider.defaultRoleId,
emailAttributeName: samlAuthProvider.emailAttributeName,
entryPoint: samlAuthProvider.entryPoint,
firstnameAttributeName: samlAuthProvider.firstnameAttributeName,
id: samlAuthProvider.id,
issuer: samlAuthProvider.issuer,
name: samlAuthProvider.name,
roleAttributeName: samlAuthProvider.roleAttributeName,
signatureAlgorithm: samlAuthProvider.signatureAlgorithm,
surnameAttributeName: samlAuthProvider.surnameAttributeName,
};
});
return {
data: data,
meta: {
count: data.length,
currentPage: null,
isArray: true,
totalPages: null,
type: 'SamlAuthProvider',
},
};
};
export default getSamlAuthProvidersMock;

View File

@@ -1,32 +0,0 @@
const getCurrentUserMock = (currentUser, role) => {
return {
data: {
createdAt: currentUser.createdAt.toISOString(),
email: currentUser.email,
fullName: currentUser.fullName,
id: currentUser.id,
permissions: [],
role: {
createdAt: role.createdAt.toISOString(),
description: null,
id: role.id,
isAdmin: role.isAdmin,
key: role.key,
name: role.name,
updatedAt: role.updatedAt.toISOString(),
},
roleId: role.id,
trialExpiryDate: currentUser.trialExpiryDate.toISOString(),
updatedAt: currentUser.updatedAt.toISOString(),
},
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'User',
},
};
};
export default getCurrentUserMock;

View File

@@ -1,17 +0,0 @@
const getUserTrialMock = async (currentUser) => {
return {
data: {
inTrial: await currentUser.inTrial(),
expireAt: currentUser.trialExpiryDate.toISOString(),
},
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'Object',
},
};
};
export default getUserTrialMock;

View File

@@ -1,31 +0,0 @@
const getUserMock = (currentUser, role) => {
return {
data: {
createdAt: currentUser.createdAt.toISOString(),
email: currentUser.email,
fullName: currentUser.fullName,
id: currentUser.id,
role: {
createdAt: role.createdAt.toISOString(),
description: null,
id: role.id,
isAdmin: role.isAdmin,
key: role.key,
name: role.name,
updatedAt: role.updatedAt.toISOString(),
},
roleId: role.id,
trialExpiryDate: currentUser.trialExpiryDate.toISOString(),
updatedAt: currentUser.updatedAt.toISOString(),
},
meta: {
count: 1,
currentPage: null,
isArray: false,
totalPages: null,
type: 'User',
},
};
};
export default getUserMock;

View File

@@ -1,38 +0,0 @@
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,
fullName: user.fullName,
id: user.id,
role: role
? {
createdAt: role.createdAt.toISOString(),
description: role.description,
id: role.id,
isAdmin: role.isAdmin,
key: role.key,
name: role.name,
updatedAt: role.updatedAt.toISOString(),
}
: null, // Fallback to null if role not found
roleId: user.roleId,
trialExpiryDate: user.trialExpiryDate.toISOString(),
updatedAt: user.updatedAt.toISOString(),
};
});
return {
data: data,
meta: {
count: data.length,
currentPage: 1,
isArray: true,
totalPages: 1,
type: 'User',
},
};
};
export default getUsersMock;

View File

@@ -32,6 +32,14 @@ export default defineConfig({
],
sidebar: {
'/apps/': [
{
text: 'Better Stack',
collapsible: true,
collapsed: true,
items: [
{ text: 'Connection', link: '/apps/better-stack/connection' },
],
},
{
text: 'Carbone',
collapsible: true,
@@ -252,15 +260,6 @@ export default defineConfig({
{ text: 'Connection', link: '/apps/openai/connection' },
],
},
{
text: 'PDFMonkey',
collapsible: true,
collapsed: true,
items: [
{ text: 'Triggers', link: '/apps/pdf-monkey/triggers' },
{ text: 'Connection', link: '/apps/pdf-monkey/connection' },
],
},
{
text: 'Pipedrive',
collapsible: true,

View File

@@ -14,33 +14,31 @@ The default values for some environment variables might be different in our deve
Please be careful with the `ENCRYPTION_KEY` and `WEBHOOK_SECRET_KEY` environment variables. They are used to encrypt your credentials from third-party services and verify webhook requests. If you change them, your existing connections and flows will not continue to work.
:::
| Variable Name | Type | Default Value | Description |
| ---------------------------- | ------- | ------------------ | ----------------------------------------------------------------------------------- |
| `HOST` | string | `localhost` | HTTP Host |
| `PROTOCOL` | string | `http` | HTTP Protocol |
| `PORT` | string | `3000` | HTTP Port |
| `APP_ENV` | string | `production` | Automatisch Environment |
| `WEB_APP_URL` | string | | Can be used to override connection URLs and CORS URL |
| `WEBHOOK_URL` | string | | Can be used to override webhook URL |
| `LOG_LEVEL` | string | `info` | Can be used to configure log level such as `error`, `warn`, `info`, `http`, `debug` |
| `POSTGRES_DATABASE` | string | `automatisch` | Database Name |
| `POSTGRES_SCHEMA` | string | `public` | Database Schema |
| `POSTGRES_PORT` | number | `5432` | Database Port |
| `POSTGRES_ENABLE_SSL` | boolean | `false` | Enable/Disable SSL for the database |
| `POSTGRES_HOST` | string | `postgres` | Database Host |
| `POSTGRES_USERNAME` | string | `automatisch_user` | Database User |
| `POSTGRES_PASSWORD` | string | | Password of Database User |
| `ENCRYPTION_KEY` | string | | Encryption Key to store credentials |
| `WEBHOOK_SECRET_KEY` | string | | Webhook Secret Key to verify webhook requests |
| `APP_SECRET_KEY` | string | | Secret Key to authenticate the user |
| `REDIS_HOST` | string | `redis` | Redis Host |
| `REDIS_PORT` | number | `6379` | Redis Port |
| `REDIS_USERNAME` | string | | Redis Username |
| `REDIS_PASSWORD` | string | | Redis Password |
| `REDIS_TLS` | boolean | `false` | Redis TLS |
| `TELEMETRY_ENABLED` | boolean | `true` | Enable/Disable Telemetry |
| `ENABLE_BULLMQ_DASHBOARD` | boolean | `false` | Enable BullMQ Dashboard |
| `BULLMQ_DASHBOARD_USERNAME` | string | | Username to login BullMQ Dashboard |
| `BULLMQ_DASHBOARD_PASSWORD` | string | | Password to login BullMQ Dashboard |
| `DISABLE_NOTIFICATIONS_PAGE` | boolean | `false` | Enable/Disable notifications page |
| `DISABLE_FAVICON` | boolean | `false` | Enable/Disable favicon |
| Variable Name | Type | Default Value | Description |
| --------------------------- | ------- | ------------------ | ---------------------------------------------------------------------------------------------------- |
| `HOST` | string | `localhost` | HTTP Host |
| `PROTOCOL` | string | `http` | HTTP Protocol |
| `PORT` | string | `3000` | HTTP Port |
| `APP_ENV` | string | `production` | Automatisch Environment |
| `WEB_APP_URL` | string | | Can be used to override connection URLs and CORS URL |
| `WEBHOOK_URL` | string | | Can be used to override webhook URL |
| `LOG_LEVEL` | string | `info` | Can be used to configure log level such as `error`, `warn`, `info`, `http`, `debug` |
| `POSTGRES_DATABASE` | string | `automatisch` | Database Name |
| `POSTGRES_SCHEMA` | string | `public` | Database Schema |
| `POSTGRES_PORT` | number | `5432` | Database Port |
| `POSTGRES_ENABLE_SSL` | boolean | `false` | Enable/Disable SSL for the database |
| `POSTGRES_HOST` | string | `postgres` | Database Host |
| `POSTGRES_USERNAME` | string | `automatisch_user` | Database User |
| `POSTGRES_PASSWORD` | string | | Password of Database User |
| `ENCRYPTION_KEY` | string | | Encryption Key to store credentials |
| `WEBHOOK_SECRET_KEY` | string | | Webhook Secret Key to verify webhook requests |
| `APP_SECRET_KEY` | string | | Secret Key to authenticate the user |
| `REDIS_HOST` | string | `redis` | Redis Host |
| `REDIS_PORT` | number | `6379` | Redis Port |
| `REDIS_USERNAME` | string | | Redis Username |
| `REDIS_PASSWORD` | string | | Redis Password |
| `REDIS_TLS` | boolean | `false` | Redis TLS |
| `TELEMETRY_ENABLED` | boolean | `true` | Enable/Disable Telemetry |
| `ENABLE_BULLMQ_DASHBOARD` | boolean | `false` | Enable BullMQ Dashboard |
| `BULLMQ_DASHBOARD_USERNAME` | string | | Username to login BullMQ Dashboard |
| `BULLMQ_DASHBOARD_PASSWORD` | string | | Password to login BullMQ Dashboard |

View File

@@ -0,0 +1,14 @@
# Better Stack
:::info
This page explains the steps you need to follow to set up the Better Stack
connection in Automatisch. If any of the steps are outdated, please let us know!
:::
1. Login to your Better Stack account: [https://betterstack.com/](https://betterstack.com/).
2. Click on the team name bottom left and select **Manage Teams** option.
3. Click on the three dots icon of your team and select **manage** option.
4. Click on the **API tokens** tab.
5. Copy the token next to **Direct API tokens** to the `API Key` field on Automatisch.
6. Fill the screen name on Automatisch.
7. Now, you can start using the Better Stack connection with Automatisch.

View File

@@ -1,11 +0,0 @@
# PDFMonkey
:::info
This page explains the steps you need to follow to set up the PDFMonkey
connection in Automatisch. If any of the steps are outdated, please let us know!
:::
1. Login to your PDFMonkey account: [https://dashboard.pdfmonkey.io/login](https://dashboard.pdfmonkey.io/login).
2. Go to **My Account** section from your profile.
3. Copy `API SECRET KEY` from the page to the `API Key` field on Automatisch.
4. Now, you can start using the PDFMonkey connection with Automatisch.

View File

@@ -1,12 +0,0 @@
---
favicon: /favicons/pdf-monkey.svg
items:
- name: Documents Generated
desc: Triggers upon the successful completion of document generation.
---
<script setup>
import CustomListing from '../../components/CustomListing.vue'
</script>
<CustomListing />

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="200.000000pt" height="200.000000pt" viewBox="0 0 200.000000 200.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,200.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M0 1000 l0 -1000 1000 0 1000 0 0 1000 0 1000 -1000 0 -1000 0 0
-1000z m1162 460 c14 -11 113 -184 232 -408 228 -429 231 -439 175 -486 -35
-30 -30 -29 -140 -15 -89 12 -123 25 -152 56 -9 11 -72 147 -140 304 -113 263
-124 284 -149 287 -14 2 -29 10 -32 17 -8 21 67 214 94 242 28 29 78 30 112 3z
m-340 -148 c10 -10 72 -175 139 -367 114 -325 121 -351 108 -374 -8 -14 -27
-32 -41 -41 -25 -13 -34 -12 -126 18 -55 18 -111 43 -125 56 -19 17 -40 67
-76 182 -36 112 -58 164 -73 176 l-22 16 27 99 c63 224 66 232 95 248 31 17
69 12 94 -13z m-314 -219 c16 -15 26 -59 56 -243 42 -262 43 -285 17 -300 -11
-5 -24 -10 -30 -10 -19 0 -140 114 -150 141 -7 20 -4 76 10 191 10 90 19 171
19 181 0 18 33 57 49 57 5 0 18 -8 29 -17z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 112 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -2,6 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#0059F7" />
<meta

View File

@@ -2,6 +2,13 @@
"short_name": "automatisch",
"name": "automatisch",
"description": "Build workflow automation without spending time and money. No code is required.",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",

View File

@@ -15,7 +15,6 @@ import { SvgIconComponent } from '@mui/icons-material';
import AppBar from 'components/AppBar';
import Drawer from 'components/Drawer';
import * as URLS from 'config/urls';
import useFormatMessage from 'hooks/useFormatMessage';
import useCurrentUserAbility from 'hooks/useCurrentUserAbility';
type SettingsLayoutProps = {
@@ -87,11 +86,19 @@ function createDrawerLinks({
return items;
}
const drawerBottomLinks = [
{
Icon: ArrowBackIosNewIcon,
primary: 'adminSettingsDrawer.goBack',
to: '/',
dataTest: 'go-back-drawer-link',
},
];
export default function SettingsLayout({
children,
}: SettingsLayoutProps): React.ReactElement {
const theme = useTheme();
const formatMessage = useFormatMessage();
const currentUserAbility = useCurrentUserAbility();
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'));
const [isDrawerOpen, setDrawerOpen] = React.useState(!matchSmallScreens);
@@ -109,15 +116,6 @@ export default function SettingsLayout({
canUpdateApp: currentUserAbility.can('update', 'App'),
});
const drawerBottomLinks = [
{
Icon: ArrowBackIosNewIcon,
primary: formatMessage('adminSettingsDrawer.goBack'),
to: '/',
dataTest: 'go-back-drawer-link',
},
];
return (
<>
<AppBar

View File

@@ -19,7 +19,6 @@ type DrawerLink = {
Icon: React.ElementType;
primary: string;
to: string;
target?: '_blank';
badgeContent?: React.ReactNode;
dataTest?: string;
};
@@ -70,7 +69,7 @@ export default function Drawer(props: DrawerProps): React.ReactElement {
<List sx={{ py: 0, mt: 3 }}>
{bottomLinks.map(
({ Icon, badgeContent, primary, to, dataTest, target }, index) => (
({ Icon, badgeContent, primary, to, dataTest }, index) => (
<ListItemLink
key={`${to}-${index}`}
icon={
@@ -78,10 +77,9 @@ export default function Drawer(props: DrawerProps): React.ReactElement {
<Icon htmlColor={theme.palette.primary.main} />
</Badge>
}
primary={primary}
primary={formatMessage(primary)}
to={to}
onClick={closeOnClick}
target={target}
data-test={dataTest}
/>
)

View File

@@ -3,19 +3,16 @@ import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';
import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
import Stack from '@mui/material/Stack';
import AppsIcon from '@mui/icons-material/Apps';
import SwapCallsIcon from '@mui/icons-material/SwapCalls';
import HistoryIcon from '@mui/icons-material/History';
import NotificationsIcon from '@mui/icons-material/Notifications';
import ArrowBackIosNew from '@mui/icons-material/ArrowBackIosNew';
import * as URLS from 'config/urls';
import useFormatMessage from 'hooks/useFormatMessage';
import useVersion from 'hooks/useVersion';
import AppBar from 'components/AppBar';
import Drawer from 'components/Drawer';
import useConfig from 'hooks/useConfig';
import useAutomatischInfo from 'hooks/useAutomatischInfo';
type PublicLayoutProps = {
children: React.ReactNode;
@@ -43,94 +40,47 @@ const drawerLinks = [
];
type GenerateDrawerBottomLinksOptions = {
disableNotificationsPage: boolean;
isMation: boolean;
loading: boolean;
notificationBadgeContent: number;
additionalDrawerLink?: string;
additionalDrawerLinkText?: string;
additionalDrawerLinkIcon?: string;
formatMessage: ReturnType<typeof useFormatMessage>;
};
const generateDrawerBottomLinks = async ({
disableNotificationsPage,
const generateDrawerBottomLinks = ({
isMation,
loading,
notificationBadgeContent = 0,
additionalDrawerLink,
additionalDrawerLinkText,
formatMessage,
}: GenerateDrawerBottomLinksOptions) => {
const notificationsPageLinkObject = {
Icon: NotificationsIcon,
primary: formatMessage('settingsDrawer.notifications'),
to: URLS.UPDATES,
badgeContent: notificationBadgeContent,
};
const hasAdditionalDrawerLink =
additionalDrawerLink && additionalDrawerLinkText;
const additionalDrawerLinkObject = {
Icon: ArrowBackIosNew,
primary: additionalDrawerLinkText || '',
to: additionalDrawerLink || '',
target: '_blank' as const,
};
const links = [];
if (!disableNotificationsPage) {
links.push(notificationsPageLinkObject);
if (loading || isMation) {
return [];
}
if (hasAdditionalDrawerLink) {
links.push(additionalDrawerLinkObject);
}
return links;
};
type Link = {
Icon: React.ElementType;
primary: string;
target?: '_blank';
to: string;
badgeContent?: React.ReactNode;
return [
{
Icon: NotificationsIcon,
primary: 'settingsDrawer.notifications',
to: URLS.UPDATES,
badgeContent: notificationBadgeContent,
},
];
};
export default function PublicLayout({
children,
}: PublicLayoutProps): React.ReactElement {
const version = useVersion();
const { config, loading } = useConfig([
'disableNotificationsPage',
'additionalDrawerLink',
'additionalDrawerLinkText',
]);
const { isMation, loading } = useAutomatischInfo();
const theme = useTheme();
const formatMessage = useFormatMessage();
const [bottomLinks, setBottomLinks] = React.useState<Link[]>([]);
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'));
const [isDrawerOpen, setDrawerOpen] = React.useState(!matchSmallScreens);
const openDrawer = () => setDrawerOpen(true);
const closeDrawer = () => setDrawerOpen(false);
React.useEffect(() => {
async function perform() {
const newBottomLinks = await generateDrawerBottomLinks({
notificationBadgeContent: version.newVersionCount,
disableNotificationsPage: config?.disableNotificationsPage as boolean,
additionalDrawerLink: config?.additionalDrawerLink as string,
additionalDrawerLinkText: config?.additionalDrawerLinkText as string,
formatMessage,
});
setBottomLinks(newBottomLinks);
}
if (loading) return;
perform();
}, [config, loading, version.newVersionCount]);
const drawerBottomLinks = generateDrawerBottomLinks({
notificationBadgeContent: version.newVersionCount,
loading,
isMation,
});
return (
<>
@@ -140,19 +90,20 @@ export default function PublicLayout({
onDrawerClose={closeDrawer}
/>
<Box sx={{ display: 'flex', height: '100%' }}>
<Box sx={{ display: 'flex' }}>
<Drawer
links={drawerLinks}
bottomLinks={bottomLinks}
bottomLinks={drawerBottomLinks}
open={isDrawerOpen}
onOpen={openDrawer}
onClose={closeDrawer}
/>
<Stack flex={1}>
<Box sx={{ flex: 1 }}>
<Toolbar />
{children}
</Stack>
</Box>
</Box>
</>
);

View File

@@ -9,7 +9,6 @@ type ListItemLinkProps = {
icon: React.ReactNode;
primary: string;
to: string;
target?: '_blank';
onClick?: (event: React.SyntheticEvent) => void;
'data-test'?: string;
};
@@ -17,29 +16,14 @@ type ListItemLinkProps = {
export default function ListItemLink(
props: ListItemLinkProps
): React.ReactElement {
const { icon, primary, to, onClick, 'data-test': dataTest, target } = props;
const { icon, primary, to, onClick, 'data-test': dataTest } = props;
const selected = useMatch({ path: to, end: true });
const CustomLink = React.useMemo(
() =>
React.forwardRef<HTMLAnchorElement, Omit<LinkProps, 'to'>>(
function InLineLink(linkProps, ref) {
try {
// challenge the link to check if it's absolute URL
new URL(to); // should throw an error if it's not an absolute URL
return (
<a
{...linkProps}
ref={ref}
href={to}
target={target}
rel="noopener noreferrer"
/>
);
} catch {
return <Link ref={ref} {...linkProps} to={to} />;
}
return <Link ref={ref} to={to} {...linkProps} />;
}
),
[to]
@@ -53,7 +37,6 @@ export default function ListItemLink(
selected={!!selected}
onClick={onClick}
data-test={dataTest}
target={target}
>
<ListItemIcon sx={{ minWidth: 52 }}>{icon}</ListItemIcon>
<ListItemText

View File

@@ -15,27 +15,6 @@ const MetadataProvider = ({
document.title = (config?.title as string) || 'Automatisch';
}, [config?.title]);
React.useEffect(() => {
const existingFaviconElement = document.querySelector(
"link[rel~='icon']"
) as HTMLLinkElement | null;
if (config?.disableFavicon === true) {
existingFaviconElement?.remove();
}
if (config?.disableFavicon === false) {
if (existingFaviconElement) {
existingFaviconElement.href = '/browser-tab.ico';
} else {
const newFaviconElement = document.createElement('link');
newFaviconElement.rel = 'icon';
document.head.appendChild(newFaviconElement);
newFaviconElement.href = '/browser-tab.ico';
}
}
}, [config?.disableFavicon]);
return <>{children}</>;
};

View File

@@ -1,44 +0,0 @@
import { Link } from 'react-router-dom';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import * as URLS from 'config/urls';
import useFormatMessage from 'hooks/useFormatMessage';
import useAuthentication from 'hooks/useAuthentication';
import Layout from 'components/Layout';
import PublicLayout from 'components/PublicLayout';
export default function NoResultFound(): React.ReactElement {
const formatMessage = useFormatMessage();
const { isAuthenticated } = useAuthentication();
const pageContent = (
<Stack
justifyContent="center"
alignItems="center"
flex={1}
spacing={1}
p={2}
mb={11}
>
<Typography variant="h1" color="primary" textAlign="center">
404
</Typography>
<Typography variant="body1" textAlign="center">
{formatMessage('notFoundPage.title')}
</Typography>
<Link to={isAuthenticated ? URLS.FLOWS : URLS.LOGIN}>
<Button variant="contained" sx={{ mt: 3 }} component="div">
{formatMessage('notFoundPage.button')}
</Button>
</Link>
</Stack>
);
return isAuthenticated ? (
<Layout>{pageContent}</Layout>
) : (
<PublicLayout>{pageContent}</PublicLayout>
);
}

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