Compare commits
79 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
703e434e5c | ||
![]() |
c12dacfa40 | ||
![]() |
71a404063c | ||
![]() |
8bb7b16c0e | ||
![]() |
d7893d9a32 | ||
![]() |
9cbdda330c | ||
![]() |
42a9bfd099 | ||
![]() |
eb15bd01ca | ||
![]() |
9e98aebeb3 | ||
![]() |
1361cbc826 | ||
![]() |
679d0808a9 | ||
![]() |
6fe9a548ad | ||
![]() |
2d6d2430d2 | ||
![]() |
a445538e81 | ||
![]() |
50d38ffbd8 | ||
![]() |
93bcdfd9c9 | ||
![]() |
5be3b101a5 | ||
![]() |
024c7476c7 | ||
![]() |
30a7ffe93d | ||
![]() |
e2d803ebf7 | ||
![]() |
be7e67c940 | ||
![]() |
ead4b13ba5 | ||
![]() |
e02c42ee18 | ||
![]() |
d39886fdf8 | ||
![]() |
11a425f1de | ||
![]() |
f0e194e584 | ||
![]() |
d4b9331cf2 | ||
![]() |
37e1acc5f1 | ||
![]() |
ffaf6a577d | ||
![]() |
afdaf6ba39 | ||
![]() |
4c49367910 | ||
![]() |
a506c4411d | ||
![]() |
1859c9854e | ||
![]() |
6ff29b9ae6 | ||
![]() |
3578f6b849 | ||
![]() |
0347864fde | ||
![]() |
5f9786a2c7 | ||
![]() |
75aeff1898 | ||
![]() |
0afcdce6d3 | ||
![]() |
a591d0ea87 | ||
![]() |
0e111a3532 | ||
![]() |
b599466ffa | ||
![]() |
69727e78df | ||
![]() |
02ae67b147 | ||
![]() |
a769f78801 | ||
![]() |
d583e42428 | ||
![]() |
da732becb6 | ||
![]() |
b89a4d58d9 | ||
![]() |
09854147d1 | ||
![]() |
3648c2bfe3 | ||
![]() |
3f3ee032f6 | ||
![]() |
68e5d54331 | ||
![]() |
824c434b0b | ||
![]() |
9f0e0ca656 | ||
![]() |
95f89ba03e | ||
![]() |
697f72ecf4 | ||
![]() |
4f03f2ab51 | ||
![]() |
c81531cb7a | ||
![]() |
7b6e4aa153 | ||
![]() |
f21039d19d | ||
![]() |
8c936a91be | ||
![]() |
24451892ff | ||
![]() |
6bba2c82fe | ||
![]() |
3320dc6bc4 | ||
![]() |
9d42fd9293 | ||
![]() |
e6b806616f | ||
![]() |
6ec5872391 | ||
![]() |
a26cf932a1 | ||
![]() |
38a3e3ab9f | ||
![]() |
32b17c1418 | ||
![]() |
44aa6a1579 | ||
![]() |
2369aacd2a | ||
![]() |
7dafc6364b | ||
![]() |
3d25fa0aeb | ||
![]() |
0297b0f296 | ||
![]() |
4c7d09c3d8 | ||
![]() |
48a74826e8 | ||
![]() |
ef34068ac4 | ||
![]() |
3987a8db77 |
@@ -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",
|
||||
|
@@ -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"
|
||||
},
|
||||
|
@@ -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 });
|
||||
},
|
||||
});
|
@@ -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 });
|
||||
},
|
||||
});
|
@@ -1,4 +0,0 @@
|
||||
import acknowledgeIncident from './acknowledge-incident/index.js';
|
||||
import createIncident from './create-incident/index.js';
|
||||
|
||||
export default [acknowledgeIncident, createIncident];
|
@@ -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 |
@@ -1,8 +0,0 @@
|
||||
import verifyCredentials from './verify-credentials.js';
|
||||
|
||||
const isStillVerified = async ($) => {
|
||||
await verifyCredentials($);
|
||||
return true;
|
||||
};
|
||||
|
||||
export default isStillVerified;
|
@@ -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;
|
@@ -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,
|
||||
});
|
@@ -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' },
|
||||
],
|
||||
|
@@ -0,0 +1,8 @@
|
||||
const base64ToString = ($) => {
|
||||
const input = $.step.parameters.input;
|
||||
const decodedString = Buffer.from(input, 'base64').toString('utf8');
|
||||
|
||||
return decodedString;
|
||||
};
|
||||
|
||||
export default base64ToString;
|
@@ -0,0 +1,8 @@
|
||||
const stringtoBase64 = ($) => {
|
||||
const input = $.step.parameters.input;
|
||||
const base64String = Buffer.from(input).toString('base64');
|
||||
|
||||
return base64String;
|
||||
};
|
||||
|
||||
export default stringtoBase64;
|
@@ -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,
|
||||
|
@@ -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;
|
@@ -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;
|
@@ -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',
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
@@ -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,
|
||||
});
|
||||
},
|
||||
});
|
4
packages/backend/src/apps/pdf-monkey/actions/index.js
Normal file
4
packages/backend/src/apps/pdf-monkey/actions/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import deleteDocument from './delete-document/index.js';
|
||||
import generateDocument from './generate-document/index.js';
|
||||
|
||||
export default [deleteDocument, generateDocument];
|
1487
packages/backend/src/apps/pdf-monkey/assets/favicon.svg
Normal file
1487
packages/backend/src/apps/pdf-monkey/assets/favicon.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 112 KiB |
@@ -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,
|
||||
},
|
||||
],
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
@@ -0,0 +1,4 @@
|
||||
import listTemplates from './list-templates/index.js';
|
||||
import listWorkspaces from './list-workspaces/index.js';
|
||||
|
||||
export default [listTemplates, listWorkspaces];
|
@@ -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;
|
||||
},
|
||||
};
|
@@ -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;
|
||||
},
|
||||
};
|
@@ -0,0 +1,4 @@
|
||||
import listDocumentData from './list-document-data/index.js';
|
||||
import listLineItems from './list-line-items/index.js';
|
||||
|
||||
export default [listDocumentData, listLineItems];
|
@@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
},
|
||||
};
|
@@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
},
|
||||
};
|
24
packages/backend/src/apps/pdf-monkey/index.js
Normal file
24
packages/backend/src/apps/pdf-monkey/index.js
Normal 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,
|
||||
});
|
@@ -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);
|
||||
},
|
||||
});
|
3
packages/backend/src/apps/pdf-monkey/triggers/index.js
Normal file
3
packages/backend/src/apps/pdf-monkey/triggers/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import documentsGenerated from './documents-generated/index.js';
|
||||
|
||||
export default [documentsGenerated];
|
@@ -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) {
|
||||
|
@@ -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);
|
||||
};
|
@@ -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);
|
||||
});
|
||||
});
|
@@ -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);
|
||||
};
|
@@ -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);
|
||||
});
|
||||
});
|
@@ -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 });
|
||||
};
|
@@ -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);
|
||||
});
|
||||
});
|
@@ -0,0 +1,5 @@
|
||||
import { renderObject } from '../../../../helpers/renderer.js';
|
||||
|
||||
export default async (request, response) => {
|
||||
renderObject(response, request.currentUser);
|
||||
};
|
@@ -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);
|
||||
});
|
||||
});
|
@@ -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);
|
||||
};
|
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
16
packages/backend/src/controllers/api/v1/users/get-user.js
Normal file
16
packages/backend/src/controllers/api/v1/users/get-user.js
Normal 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);
|
||||
};
|
@@ -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);
|
||||
});
|
||||
});
|
18
packages/backend/src/controllers/api/v1/users/get-users.js
Normal file
18
packages/backend/src/controllers/api/v1/users/get-users.js
Normal 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);
|
||||
};
|
@@ -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);
|
||||
});
|
||||
});
|
3
packages/backend/src/controllers/healthcheck/index.js
Normal file
3
packages/backend/src/controllers/healthcheck/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export default async (request, response) => {
|
||||
response.status(200).end();
|
||||
};
|
@@ -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);
|
||||
});
|
||||
});
|
@@ -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({
|
||||
|
@@ -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;
|
||||
|
@@ -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',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@@ -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 = {
|
||||
|
@@ -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', () => {
|
||||
|
33
packages/backend/src/helpers/authorization.js
Normal file
33
packages/backend/src/helpers/authorization.js
Normal 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();
|
||||
}
|
||||
};
|
11
packages/backend/src/helpers/check-is-cloud.js
Normal file
11
packages/backend/src/helpers/check-is-cloud.js
Normal 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;
|
9
packages/backend/src/helpers/check-is-enterprise.js
Normal file
9
packages/backend/src/helpers/check-is-enterprise.js
Normal 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();
|
||||
}
|
||||
};
|
@@ -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`);
|
||||
|
@@ -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,
|
||||
};
|
||||
|
||||
|
25
packages/backend/src/helpers/pagination-rest.js
Normal file
25
packages/backend/src/helpers/pagination-rest.js
Normal 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;
|
44
packages/backend/src/helpers/renderer.js
Normal file
44
packages/backend/src/helpers/renderer.js
Normal 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 };
|
@@ -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);
|
||||
|
8
packages/backend/src/routes/api/v1/automatisch.js
Normal file
8
packages/backend/src/routes/api/v1/automatisch.js
Normal 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;
|
26
packages/backend/src/routes/api/v1/saml-auth-providers.ee.js
Normal file
26
packages/backend/src/routes/api/v1/saml-auth-providers.ee.js
Normal 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;
|
22
packages/backend/src/routes/api/v1/users.js
Normal file
22
packages/backend/src/routes/api/v1/users.js
Normal 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;
|
8
packages/backend/src/routes/healthcheck.js
Normal file
8
packages/backend/src/routes/healthcheck.js
Normal 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;
|
@@ -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;
|
||||
|
13
packages/backend/src/serializers/index.js
Normal file
13
packages/backend/src/serializers/index.js
Normal 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;
|
13
packages/backend/src/serializers/permission.js
Normal file
13
packages/backend/src/serializers/permission.js
Normal 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;
|
25
packages/backend/src/serializers/permission.test.js
Normal file
25
packages/backend/src/serializers/permission.test.js
Normal 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);
|
||||
});
|
||||
});
|
13
packages/backend/src/serializers/role.js
Normal file
13
packages/backend/src/serializers/role.js
Normal 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;
|
25
packages/backend/src/serializers/role.test.js
Normal file
25
packages/backend/src/serializers/role.test.js
Normal 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);
|
||||
});
|
||||
});
|
18
packages/backend/src/serializers/saml-auth-provider.ee.js
Normal file
18
packages/backend/src/serializers/saml-auth-provider.ee.js
Normal 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;
|
@@ -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
|
||||
);
|
||||
});
|
||||
});
|
32
packages/backend/src/serializers/user.js
Normal file
32
packages/backend/src/serializers/user.js
Normal 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;
|
76
packages/backend/src/serializers/user.test.js
Normal file
76
packages/backend/src/serializers/user.test.js
Normal 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);
|
||||
});
|
||||
});
|
@@ -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;
|
||||
};
|
||||
|
@@ -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;
|
||||
};
|
||||
|
@@ -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('*');
|
||||
|
||||
|
@@ -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;
|
||||
};
|
||||
|
@@ -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;
|
||||
};
|
||||
|
@@ -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;
|
||||
};
|
||||
|
@@ -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;
|
||||
};
|
||||
|
33
packages/backend/test/factories/saml-auth-provider.ee.js
Normal file
33
packages/backend/test/factories/saml-auth-provider.ee.js
Normal 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;
|
||||
};
|
@@ -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;
|
||||
};
|
||||
|
@@ -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;
|
||||
};
|
||||
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
@@ -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;
|
31
packages/backend/test/mocks/rest/api/v1/users/get-user.js
Normal file
31
packages/backend/test/mocks/rest/api/v1/users/get-user.js
Normal 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;
|
38
packages/backend/test/mocks/rest/api/v1/users/get-users.js
Normal file
38
packages/backend/test/mocks/rest/api/v1/users/get-users.js
Normal 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;
|
@@ -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,
|
||||
|
@@ -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 |
|
||||
|
@@ -1,14 +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.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import CustomListing from '../../components/CustomListing.vue'
|
||||
</script>
|
||||
|
||||
<CustomListing />
|
@@ -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.
|
14
packages/docs/pages/apps/pdf-monkey/actions.md
Normal file
14
packages/docs/pages/apps/pdf-monkey/actions.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
favicon: /favicons/pdf-monkey.svg
|
||||
items:
|
||||
- name: Generate documents
|
||||
desc: Creates a new document.
|
||||
- name: Delete documents
|
||||
desc: Deletes a document.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import CustomListing from '../../components/CustomListing.vue'
|
||||
</script>
|
||||
|
||||
<CustomListing />
|
11
packages/docs/pages/apps/pdf-monkey/connection.md
Normal file
11
packages/docs/pages/apps/pdf-monkey/connection.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# 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.
|
12
packages/docs/pages/apps/pdf-monkey/triggers.md
Normal file
12
packages/docs/pages/apps/pdf-monkey/triggers.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
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 />
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user