Compare commits

..

80 Commits

Author SHA1 Message Date
Rıdvan Akca
01472c1fb9 feat(pdf-monkey): add find document action 2024-02-28 16:43:25 +03:00
Rıdvan Akca
703e434e5c feat(pdf-monkey): add delete document action 2024-02-28 16:29:01 +03:00
Rıdvan Akca
c12dacfa40 feat(pdf-monkey): add generate document action 2024-02-28 15:50:17 +03:00
Rıdvan Akca
71a404063c feat(pdf-monkey): add documents generated trigger 2024-02-27 16:40:51 +03:00
Rıdvan Akca
8bb7b16c0e feat(pdf-monkey): add pdf-monkey integration 2024-02-22 17:32:38 +03:00
Ömer Faruk Aydın
d7893d9a32 Merge pull request #1621 from automatisch/show-saml-auth-provider
feat: Implement API endpoint to show saml auth provider
2024-02-20 13:00:25 +01:00
Ömer Faruk Aydın
9cbdda330c Merge pull request #1620 from automatisch/fix-authorization-middleware
fix: Include http methods for route rules
2024-02-20 12:59:12 +01:00
Ömer Faruk Aydın
42a9bfd099 Merge pull request #1619 from automatisch/get-saml-auth-providers
feat: Implement get saml auth providers API endpoint
2024-02-20 12:59:03 +01:00
Faruk AYDIN
eb15bd01ca feat: Implement API endpoint to show saml auth provider 2024-02-19 23:41:37 +01:00
Faruk AYDIN
9e98aebeb3 fix: Include http methods for route rules 2024-02-19 22:22:04 +01:00
Faruk AYDIN
1361cbc826 chore: Remove get saml auth providers from authorization list 2024-02-19 22:19:37 +01:00
Faruk AYDIN
679d0808a9 refactor: Move saml auth providers endpoint to admin namespace 2024-02-19 22:18:15 +01:00
Faruk AYDIN
6fe9a548ad feat: Implement get saml auth providers API endpoint 2024-02-19 21:48:06 +01:00
Faruk AYDIN
2d6d2430d2 fix: Detect types also for not paginated arrays 2024-02-19 21:46:20 +01:00
Faruk AYDIN
a445538e81 feat: Implement isCheckEnterprise middleware 2024-02-19 21:22:36 +01:00
Faruk AYDIN
50d38ffbd8 chore: Make http log level lower than info 2024-02-19 21:14:54 +01:00
Faruk AYDIN
93bcdfd9c9 feat: Implement saml auth provider serializer 2024-02-19 17:59:18 +01:00
Faruk AYDIN
5be3b101a5 feat: Implement saml auth provider factory 2024-02-19 17:58:52 +01:00
Ömer Faruk Aydın
024c7476c7 Merge pull request #1616 from automatisch/node-18-in-devcontainer
refactor: use node 18 in devcontainer
2024-02-19 11:51:59 +01:00
Ali BARIN
30a7ffe93d refactor: use node 18 in devcontainer 2024-02-19 10:17:16 +00:00
kattoczko
e2d803ebf7 feat: do not let users access notifications page when it's turned off (#1583) 2024-02-16 16:15:01 +01:00
kattoczko
be7e67c940 feat: introduce 404 page (#1600) 2024-02-16 15:54:35 +01:00
kattoczko
ead4b13ba5 feat: Show /login directly on / without valid authentication (#1528) 2024-02-16 15:15:25 +01:00
Ömer Faruk Aydın
e02c42ee18 Merge pull request #1605 from automatisch/test-permission-serializer
test: Add tests for permission serializer
2024-02-16 12:41:09 +01:00
Ömer Faruk Aydın
d39886fdf8 Merge pull request #1604 from automatisch/test-role-serializer
test: Add tests for role serializer
2024-02-16 12:40:59 +01:00
Ömer Faruk Aydın
11a425f1de Merge pull request #1603 from automatisch/test-user-serializer
test: Add tests for user serializer
2024-02-16 12:40:48 +01:00
Ömer Faruk Aydın
f0e194e584 Merge pull request #1606 from automatisch/remove-redundant
chore: Remove redundant npm libraries
2024-02-16 12:37:34 +01:00
Faruk AYDIN
d4b9331cf2 chore: Remove redundant npm libraries 2024-02-16 01:01:50 +01:00
Faruk AYDIN
37e1acc5f1 test: Add tests for permission serializer 2024-02-16 00:19:53 +01:00
Faruk AYDIN
ffaf6a577d test: Add tests for role serializer 2024-02-16 00:18:53 +01:00
Faruk AYDIN
afdaf6ba39 test: Add tests for user serializer 2024-02-16 00:10:37 +01:00
Ömer Faruk Aydın
4c49367910 Merge pull request #1602 from automatisch/introduce-serializers
feat: Introduce serializers
2024-02-15 12:46:20 +01:00
Ömer Faruk Aydın
a506c4411d Merge pull request #1601 from automatisch/rest-get-trial
feat: Implement API endpoint for user trial info
2024-02-15 12:45:54 +01:00
Faruk AYDIN
1859c9854e chore: Add permission serializer to serializers 2024-02-15 02:21:26 +01:00
Faruk AYDIN
6ff29b9ae6 refactor: Use serializer for user model instead of formatJson 2024-02-15 02:19:24 +01:00
Faruk AYDIN
3578f6b849 feat: Extend renderer to use serializers 2024-02-15 02:15:44 +01:00
Faruk AYDIN
0347864fde feat: Introduce serializers 2024-02-15 02:15:19 +01:00
Faruk AYDIN
5f9786a2c7 chore: Adjust get user trial file to have .ee extension 2024-02-14 15:55:02 +01:00
Faruk AYDIN
75aeff1898 test: Cover removed user token for authentication tests 2024-02-14 15:48:49 +01:00
Faruk AYDIN
0afcdce6d3 refactor: Do not expose subscription info for get user trial 2024-02-14 14:23:54 +01:00
Faruk AYDIN
a591d0ea87 test: Add tests for get user trial action 2024-02-14 14:18:42 +01:00
Faruk AYDIN
0e111a3532 feat: Implement API endpoint for user trial info 2024-02-14 14:18:28 +01:00
Faruk AYDIN
b599466ffa feat: Add checkIsCloud middleware for routes 2024-02-14 13:06:58 +01:00
Faruk AYDIN
69727e78df fix: Throw not found error for authentication 2024-02-14 13:06:29 +01:00
Ömer Faruk Aydın
02ae67b147 Merge pull request #1597 from automatisch/rest-api-get-users
feat: Implement api/v1/users API endpoint
2024-02-14 11:38:09 +01:00
Faruk AYDIN
a769f78801 test: Add tests for api/v1/users API endpoint 2024-02-14 01:20:29 +01:00
Faruk AYDIN
d583e42428 refactor: Structure api mock data with folders 2024-02-14 01:02:42 +01:00
Faruk AYDIN
da732becb6 feat: Implement api/v1/users API endpoint 2024-02-14 00:52:17 +01:00
Faruk AYDIN
b89a4d58d9 feat: Add pagination for REST endpoints 2024-02-14 00:51:48 +01:00
Faruk AYDIN
09854147d1 feat: Extend renderer functionality to work with pagination 2024-02-14 00:51:16 +01:00
Ömer Faruk Aydın
3648c2bfe3 Merge pull request #1592 from automatisch/rest-api-get-user
feat: Implement api/v1/users/:userId API endpoint
2024-02-13 12:14:37 +01:00
Ömer Faruk Aydın
3f3ee032f6 Merge pull request #1591 from automatisch/rest-api-get-current-user
feat: Implement users/me API endpoint
2024-02-13 11:34:37 +01:00
Ömer Faruk Aydın
68e5d54331 Merge pull request #1590 from automatisch/rest-api-automatisch-version
feat: Implement automatisch version API endpoint
2024-02-13 10:42:44 +01:00
Faruk AYDIN
824c434b0b feat: Implement api/v1/users/:userId API endpoint 2024-02-13 03:44:44 +01:00
Faruk AYDIN
9f0e0ca656 feat: Implement users/me API endpoint 2024-02-13 02:06:24 +01:00
Faruk AYDIN
95f89ba03e refactor: Use objectionjs instead of knex for factories 2024-02-13 01:49:40 +01:00
Faruk AYDIN
697f72ecf4 refactor: Use api/v1 namespace for routes 2024-02-12 23:30:38 +01:00
Faruk AYDIN
4f03f2ab51 feat: Introduce renderer helper 2024-02-12 23:25:09 +01:00
Faruk AYDIN
c81531cb7a feat: Implement automatisch version API endpoint 2024-02-12 22:59:44 +01:00
Ömer Faruk Aydın
7b6e4aa153 Merge pull request #1589 from automatisch/healthcheck-api-endpoint
feat: Implement healthcheck api endpoint
2024-02-12 22:57:58 +01:00
Faruk AYDIN
f21039d19d feat: Implement healthcheck api endpoint 2024-02-12 22:50:57 +01:00
morihoos
8c936a91be fix(csp): remove illegal characters in directive names (#1585) 2024-02-08 17:41:33 +01:00
Ali BARIN
24451892ff feat: add custom additional drawer link (#1586) 2024-02-08 16:33:12 +01:00
morihoos
6bba2c82fe feat(config): add ability to override apiUrl in environment variables (#1581) 2024-02-07 16:17:03 +01:00
Ali BARIN
3320dc6bc4 Merge pull request #1582 from automatisch/toggle-favicon-and-notifications-page
feat: put favicon and notifications page behind feature flags
2024-02-07 14:46:59 +01:00
Ali BARIN
9d42fd9293 test(queries/get-config): incorporate feature flags
cover disableNotificationsPage and disableFavicon feature flags
2024-02-07 13:11:57 +00:00
Ali BARIN
e6b806616f feat: add DISABLE_FAVICON feature flag 2024-02-07 11:51:17 +00:00
Ali BARIN
6ec5872391 feat: add DISABLE_NOTIFICATIONS_PAGE feature flag 2024-02-07 11:47:44 +00:00
Ali BARIN
a26cf932a1 chore(devcontainer): upgrade node to 20 2024-02-07 11:46:12 +00:00
Ali BARIN
38a3e3ab9f Merge pull request #1570 from automatisch/prevent-public-registrations
fix: prevent registration on non-cloud
2024-01-30 14:11:05 +01:00
Ali BARIN
32b17c1418 fix: prevent registration on non-cloud 2024-01-30 13:03:52 +00:00
Ömer Faruk Aydın
44aa6a1579 Merge pull request #1569 from automatisch/compile-email
fix: Adjust dirname for compile email helper
2024-01-29 13:05:58 +01:00
Faruk AYDIN
2369aacd2a fix: Adjust dirname for compile email helper 2024-01-29 12:48:21 +01:00
Ömer Faruk Aydın
7dafc6364b Merge pull request #1552 from automatisch/dependabot/npm_and_yarn/vite-3.2.8
chore(deps): Bump vite from 3.2.7 to 3.2.8
2024-01-29 12:32:10 +01:00
Ömer Faruk Aydın
3d25fa0aeb Merge branch 'main' into dependabot/npm_and_yarn/vite-3.2.8 2024-01-29 11:14:49 +01:00
Ali BARIN
0297b0f296 Merge pull request #1559 from automatisch/base64-to-text
feat(formatter): add base64 to string action
2024-01-26 10:56:54 +01:00
Rıdvan Akca
4c7d09c3d8 feat(formatter): add base64 to string action 2024-01-26 12:41:05 +03:00
Ali BARIN
48a74826e8 Merge pull request #1557 from automatisch/text-to-base64
feat(formatter): add string to base64 action
2024-01-25 16:56:15 +01:00
Rıdvan Akca
ef34068ac4 feat(formatter): add string to base64 action 2024-01-25 18:52:44 +03:00
dependabot[bot]
3987a8db77 chore(deps): Bump vite from 3.2.7 to 3.2.8
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 3.2.7 to 3.2.8.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v3.2.8/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v3.2.8/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-19 22:40:35 +00:00
119 changed files with 5159 additions and 708 deletions

View File

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

View File

@@ -33,7 +33,6 @@
"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",
@@ -45,7 +44,6 @@
"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",
@@ -68,7 +66,6 @@
"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

@@ -1,43 +0,0 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Acknowledge incident',
key: 'acknowledgeIncident',
description: 'Acknowledges an incident.',
arguments: [
{
label: 'Incident ID',
key: 'incidentId',
type: 'string',
required: true,
variables: true,
description:
'This serves as the incident ID that requires your acknowledgment.',
},
{
label: 'Acknowledged by',
key: 'acknowledgedBy',
type: 'string',
required: false,
variables: true,
description:
"This refers to the individual's name, email, or another form of identification that the person who acknowledged the incident has provided.",
},
],
async run($) {
const acknowledgedBy = $.step.parameters.acknowledgedBy;
const incidentId = $.step.parameters.incidentId;
const body = {
acknowledged_by: acknowledgedBy,
};
const response = await $.http.post(
`/v2/incidents/${incidentId}/acknowledge`,
body
);
$.setActionItem({ raw: response.data.data });
},
});

View File

@@ -1,120 +0,0 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Create incident',
key: 'createIncident',
description: 'Creates an incident that informs the team.',
arguments: [
{
label: 'Brief Summary',
key: 'briefSummary',
type: 'string',
required: true,
variables: true,
description: 'A short description outlining the issue.',
},
{
label: 'Description',
key: 'description',
type: 'string',
required: false,
variables: true,
description:
'An elaborate description of the situation, offering insights into what is occurring, along with instructions to reproduce the problem.',
},
{
label: 'Requester Email',
key: 'requesterEmail',
type: 'string',
required: true,
variables: true,
description:
'This represents the email address of the individual who initiated the incident request.',
},
{
label: 'Alert Settings - Call',
key: 'alertSettingsCall',
type: 'dropdown',
required: true,
description: 'Should we call the on-call person?',
variables: true,
options: [
{ label: 'Yes', value: 'true' },
{ label: 'No', value: 'false' },
],
},
{
label: 'Alert Settings - Text',
key: 'alertSettingsText',
type: 'dropdown',
required: true,
description: 'Should we text the on-call person?',
variables: true,
options: [
{ label: 'Yes', value: 'true' },
{ label: 'No', value: 'false' },
],
},
{
label: 'Alert Settings - Email',
key: 'alertSettingsEmail',
type: 'dropdown',
required: true,
description: 'Should we email the on-call person?',
variables: true,
options: [
{ label: 'Yes', value: 'true' },
{ label: 'No', value: 'false' },
],
},
{
label: 'Alert Settings - Push Notification',
key: 'alertSettingsPushNotification',
type: 'dropdown',
required: true,
description: 'Should we send a push notification to the on-call person?',
variables: true,
options: [
{ label: 'Yes', value: 'true' },
{ label: 'No', value: 'false' },
],
},
{
label: 'Team Alert Wait Time',
key: 'teamAlertWaitTime',
type: 'string',
required: true,
variables: true,
description:
"What is the time threshold for acknowledgment before escalating to the entire team? (Specify in seconds) - Use a negative value to indicate no team alert if the on-call person doesn't respond, and use 0 for an immediate alert to the entire team.",
},
],
async run($) {
const {
briefSummary,
description,
requesterEmail,
alertSettingsCall,
alertSettingsText,
alertSettingsEmail,
alertSettingsPushNotification,
teamAlertWaitTime,
} = $.step.parameters;
const body = {
summary: briefSummary,
description,
requester_email: requesterEmail,
call: alertSettingsCall,
sms: alertSettingsText,
email: alertSettingsEmail,
push: alertSettingsPushNotification,
team_wait: teamAlertWaitTime,
};
const response = await $.http.post('/v2/incidents', body);
$.setActionItem({ raw: response.data.data });
},
});

View File

@@ -1,25 +0,0 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Find incident',
key: 'findIncident',
description: 'finds an incident.',
arguments: [
{
label: 'Incident ID',
key: 'incidentId',
type: 'string',
required: true,
variables: true,
description: 'ID for querying incidents.',
},
],
async run($) {
const incidentId = $.step.parameters.incidentId;
const response = await $.http.get(`/v2/incidents/${incidentId}`);
$.setActionItem({ raw: response.data.data });
},
});

View File

@@ -1,11 +0,0 @@
import acknowledgeIncident from './acknowledge-incident/index.js';
import createIncident from './create-incident/index.js';
import findIncident from './find-incident/index.js';
import resolveIncident from './resolve-incident/index.js';
export default [
acknowledgeIncident,
createIncident,
findIncident,
resolveIncident,
];

View File

@@ -1,43 +0,0 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Resolve incident',
key: 'resolveIncident',
description: 'Resolves an incident.',
arguments: [
{
label: 'Incident ID',
key: 'incidentId',
type: 'string',
required: true,
variables: true,
description:
'This represents the identification for an incident that requires resolution.',
},
{
label: 'Resolved by',
key: 'resolvedBy',
type: 'string',
required: false,
variables: true,
description:
"This refers to the individual's name, email, or another form of identification that the person who resolved the incident has provided.",
},
],
async run($) {
const resolvedBy = $.step.parameters.resolvedBy;
const incidentId = $.step.parameters.incidentId;
const body = {
resolved_by: resolvedBy,
};
const response = await $.http.post(
`/v2/incidents/${incidentId}/resolve`,
body
);
$.setActionItem({ raw: response.data.data });
},
});

View File

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

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
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';
@@ -8,10 +9,12 @@ 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,
@@ -20,6 +23,7 @@ const transformers = {
markdownToHtml,
pluralize,
replace,
stringToBase64,
trimWhitespace,
useDefaultValue,
};
@@ -37,6 +41,7 @@ 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' },
@@ -45,6 +50,7 @@ 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

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

View File

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

View File

@@ -1,3 +1,4 @@
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';
@@ -6,6 +7,7 @@ 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';
@@ -15,6 +17,7 @@ import formatPhoneNumber from './numbers/format-phone-number.js';
import formatDateTime from './date-time/format-date-time.js';
const options = {
base64ToString,
capitalize,
extractEmailAddress,
extractNumber,
@@ -23,6 +26,7 @@ const options = {
markdownToHtml,
pluralize,
replace,
stringToBase64,
trimWhitespace,
useDefaultValue,
performMathOperation,

View File

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

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

View File

@@ -0,0 +1,29 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Delete document',
key: 'deleteDocument',
description: 'Deletes a document.',
arguments: [
{
label: 'Document ID',
key: 'documentId',
type: 'string',
required: true,
description: '',
variables: true,
},
],
async run($) {
const { documentId } = $.step.parameters;
await $.http.delete(`/v1/documents/${documentId}`);
$.setActionItem({
raw: {
result: 'successful',
},
});
},
});

View File

@@ -0,0 +1,27 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Find document',
key: 'findDocument',
description: 'Finds a document.',
arguments: [
{
label: 'Document ID',
key: 'documentId',
type: 'string',
required: true,
description: '',
variables: true,
},
],
async run($) {
const { documentId } = $.step.parameters;
const { data } = await $.http.get(`/v1/documents/${documentId}`);
$.setActionItem({
raw: data.document,
});
},
});

View File

@@ -0,0 +1,241 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Generate document',
key: 'generateDocument',
description: 'Creates a new document.',
arguments: [
{
label: 'Workspace',
key: 'workspaceId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listWorkspaces',
},
],
},
},
{
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}',
},
],
},
},
{
label: 'Use a Custom Json Structure?',
key: 'useCustomJsonStructure',
type: 'dropdown',
required: true,
description:
'Please indicate "yes" if you would rather create a full JSON payload instead of relying on Automatisch mapping for the Document data.',
variables: true,
options: [
{
label: 'Yes',
value: true,
},
{
label: 'No',
value: false,
},
],
additionalFields: {
type: 'query',
name: 'getDynamicFields',
arguments: [
{
name: 'key',
value: 'listDocumentData',
},
{
name: 'parameters.useCustomJsonStructure',
value: '{parameters.useCustomJsonStructure}',
},
],
},
},
{
label: 'Add Line Items?',
key: 'addLineItems',
type: 'dropdown',
required: true,
description:
'Choose "yes" to include information for Line Items (such as in an invoice).',
variables: true,
options: [
{
label: 'Yes',
value: true,
},
{
label: 'No',
value: false,
},
],
additionalFields: {
type: 'query',
name: 'getDynamicFields',
arguments: [
{
name: 'key',
value: 'listLineItems',
},
{
name: 'parameters.addLineItems',
value: '{parameters.addLineItems}',
},
],
},
},
{
label: 'Custom Filename',
key: 'customFilename',
type: 'string',
required: false,
description:
'You have the option to define a custom filename for generated documents. If left blank, a random value will be assigned.',
variables: true,
},
{
label: 'Meta Data',
key: 'metaData',
type: 'dynamic',
required: false,
description:
'Extra information appended to the generated Document but not accessible within its Template.',
fields: [
{
label: 'Key',
key: 'metaDataKey',
type: 'string',
required: false,
description: '',
variables: true,
},
{
label: 'Value',
key: 'metaDataValue',
type: 'string',
required: false,
description: '',
variables: true,
},
],
},
],
async run($) {
const {
templateId,
useCustomJsonStructure,
customJsonPayload,
customFilename,
addLineItems,
lineItems,
documentData,
metaData,
} = $.step.parameters;
let payload = {};
let meta = {};
const documentDataObject = documentData.reduce((result, entry) => {
const key = entry.documentDataKey?.toLowerCase();
const value = entry.documentDataValue;
if (key && value) {
return {
...result,
[entry.documentDataKey?.toLowerCase()]: entry.documentDataValue,
};
}
return result;
}, {});
const lineItemsObject = lineItems.reduce((result, entry) => {
const key = entry.lineItemKey?.toLowerCase();
const value = entry.lineItemValue;
if (key && value) {
return {
...result,
[entry.lineItemKey?.toLowerCase()]: entry.lineItemValue,
};
}
return result;
}, {});
const metaDataObject = metaData.reduce((result, entry) => {
const key = entry.metaDataKey?.toLowerCase();
const value = entry.metaDataValue;
if (key && value) {
return {
...result,
[entry.metaDataKey?.toLowerCase()]: entry.metaDataValue,
};
}
return result;
}, {});
if (metaDataObject) {
meta = metaDataObject;
}
if (customFilename) {
meta._filename = customFilename;
}
if (useCustomJsonStructure) {
payload = JSON.parse(customJsonPayload);
} else {
payload = documentDataObject;
}
if (addLineItems) {
payload.lineItems = [lineItemsObject];
}
const body = {
document: {
document_template_id: templateId,
meta: JSON.stringify(meta),
payload: JSON.stringify(payload),
status: 'pending',
},
};
const { data } = await $.http.post('/v1/documents', body);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -0,0 +1,5 @@
import deleteDocument from './delete-document/index.js';
import findDocument from './find-document/index.js';
import generateDocument from './generate-document/index.js';
export default [deleteDocument, findDocument, generateDocument];

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 112 KiB

View File

@@ -3,18 +3,6 @@ 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',
@@ -23,7 +11,7 @@ export default {
readOnly: false,
value: null,
placeholder: null,
description: 'Better Stack API key of your account.',
description: 'PDFMonkey API secret key of your account.',
clickToCopy: false,
},
],

View File

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

View File

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

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

View File

@@ -0,0 +1,4 @@
import listTemplates from './list-templates/index.js';
import listWorkspaces from './list-workspaces/index.js';
export default [listTemplates, listWorkspaces];

View File

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

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

@@ -0,0 +1,4 @@
import listDocumentData from './list-document-data/index.js';
import listLineItems from './list-line-items/index.js';
export default [listDocumentData, listLineItems];

View File

@@ -0,0 +1,48 @@
export default {
name: 'List document data',
key: 'listDocumentData',
async run($) {
if ($.step.parameters.useCustomJsonStructure) {
return [
{
label: 'Data for the Document (JSON Payload)',
key: 'customJsonPayload',
type: 'string',
required: false,
description:
'Use the JSON format { "firstname": "John", "lastname": "Doe" }.',
variables: true,
},
];
} else {
return [
{
label: 'Data for the Document',
key: 'documentData',
type: 'dynamic',
required: false,
description: '',
fields: [
{
label: 'Key',
key: 'documentDataKey',
type: 'string',
required: false,
description: '',
variables: true,
},
{
label: 'Value',
key: 'documentDataValue',
type: 'string',
required: false,
description: '',
variables: true,
},
],
},
];
}
},
};

View File

@@ -0,0 +1,37 @@
export default {
name: 'List line items',
key: 'listLineItems',
async run($) {
if ($.step.parameters.addLineItems) {
return [
{
label: 'Line Items',
key: 'lineItems',
type: 'dynamic',
required: false,
description:
'Data for a single item. Available as "lineItems" in your PDFMonkey Template.',
fields: [
{
label: 'Key',
key: 'lineItemKey',
type: 'string',
required: false,
description: '',
variables: true,
},
{
label: 'Value',
key: 'lineItemValue',
type: 'string',
required: false,
description: '',
variables: true,
},
],
},
];
}
},
};

View File

@@ -0,0 +1,24 @@
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';
import actions from './actions/index.js';
import dynamicFields from './dynamic-fields/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,
actions,
dynamicFields,
});

View File

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

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

View File

@@ -18,7 +18,9 @@ const port = process.env.PORT || '3000';
const serveWebAppSeparately =
process.env.SERVE_WEB_APP_SEPARATELY === 'true' ? true : false;
let apiUrl = new URL(`${protocol}://${host}:${port}`).toString();
let apiUrl = new URL(
process.env.API_URL || `${protocol}://${host}:${port}`
).toString();
apiUrl = apiUrl.substring(0, apiUrl.length - 1);
// use apiUrl by default, which has less priority over the following cases
@@ -88,6 +90,10 @@ 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

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

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

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

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

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

View File

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

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

View File

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

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

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

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

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

View File

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

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

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

View File

@@ -0,0 +1,9 @@
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,7 +1,10 @@
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,9 +1,17 @@
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)) {
@@ -18,7 +26,7 @@ const getConfig = async (_parent, params) => {
computedConfig[key] = value?.data;
return computedConfig;
}, {});
}, defaultConfig);
};
export default getConfig;

View File

@@ -2,6 +2,7 @@ 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', () => {
@@ -56,6 +57,10 @@ 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,
},
},
};
@@ -82,6 +87,48 @@ 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,7 +20,8 @@ export const isAuthenticated = async (_parent, _args, req) => {
.withGraphFetched({
role: true,
permissions: true,
});
})
.throwIfNotFound();
return true;
} catch (error) {
@@ -28,6 +29,14 @@ 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,11 +1,8 @@
import { describe, it, expect, vi } from 'vitest';
import { describe, it, expect } from 'vitest';
import { allow } from 'graphql-shield';
import jwt from 'jsonwebtoken';
import User from '../models/user.js';
import { isAuthenticated, authenticationRules } from './authentication.js';
vi.mock('jsonwebtoken');
vi.mock('../models/user.js');
import { createUser } from '../../test/factories/user.js';
import createAuthTokenByUserId from '../helpers/create-auth-token-by-user-id.js';
describe('isAuthenticated', () => {
it('should return false if no token is provided', async () => {
@@ -14,29 +11,26 @@ 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', async () => {
jwt.verify.mockReturnValue({ userId: '123' });
it('should return true if token is valid and there is a user', async () => {
const user = await createUser();
const token = createAuthTokenByUserId(user.id);
User.query.mockReturnValue({
findById: vi.fn().mockReturnValue({
leftJoinRelated: vi.fn().mockReturnThis(),
withGraphFetched: vi
.fn()
.mockResolvedValue({ id: '123', role: {}, permissions: {} }),
}),
});
const req = { headers: { authorization: 'validToken' } };
const req = { headers: { authorization: token } };
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

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

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

@@ -0,0 +1,9 @@
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,6 +1,9 @@
import * as path from 'path';
import * as fs from 'fs';
import * as handlebars from 'handlebars';
import path from 'path';
import fs from 'fs';
import handlebars from 'handlebars';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
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,
info: 2,
http: 3,
http: 2,
info: 3,
debug: 4,
};

View File

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

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

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

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

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

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

View File

@@ -2,11 +2,19 @@ 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

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

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

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

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

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

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

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

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

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

View File

@@ -1,5 +1,6 @@
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';
@@ -16,10 +17,7 @@ export const createConnection = async (params = {}) => {
appConfig.encryptionKey
).toString();
const [connection] = await global.knex
.table('connections')
.insert(params)
.returning('*');
const connection = await Connection.query().insert(params).returning('*');
return connection;
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
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;
@@ -7,7 +8,7 @@ export const createUser = async (params = {}) => {
params.email = params?.email || faker.internet.email();
params.password = params?.password || faker.internet.password();
const [user] = await global.knex.table('users').insert(params).returning('*');
const user = await User.query().insert(params).returning('*');
return user;
};

View File

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

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

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

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

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

@@ -0,0 +1,38 @@
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,15 +32,6 @@ export default defineConfig({
],
sidebar: {
'/apps/': [
{
text: 'Better Stack',
collapsible: true,
collapsed: true,
items: [
{ text: 'Actions', link: '/apps/better-stack/actions' },
{ text: 'Connection', link: '/apps/better-stack/connection' },
],
},
{
text: 'Carbone',
collapsible: true,
@@ -261,6 +252,16 @@ export default defineConfig({
{ text: 'Connection', link: '/apps/openai/connection' },
],
},
{
text: 'PDFMonkey',
collapsible: true,
collapsed: true,
items: [
{ text: 'Triggers', link: '/apps/pdf-monkey/triggers' },
{ text: 'Actions', link: '/apps/pdf-monkey/actions' },
{ text: 'Connection', link: '/apps/pdf-monkey/connection' },
],
},
{
text: 'Pipedrive',
collapsible: true,

View File

@@ -14,31 +14,33 @@ 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 |
| 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 |

View File

@@ -1,18 +0,0 @@
---
favicon: /favicons/better-stack.svg
items:
- name: Acknowledge incident
desc: Acknowledges an incident.
- name: Create incident
desc: Creates an incident that informs the team.
- name: Find incident
desc: Finds an incident.
- name: Resolve incident
desc: Resolves an incident.
---
<script setup>
import CustomListing from '../../components/CustomListing.vue'
</script>
<CustomListing />

View File

@@ -1,14 +0,0 @@
# 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.

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