Compare commits
	
		
			29 Commits
		
	
	
		
			dependabot
			...
			AUT-758
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					01472c1fb9 | ||
| 
						 | 
					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 | ||
| 
						 | 
					37e1acc5f1 | ||
| 
						 | 
					ffaf6a577d | ||
| 
						 | 
					afdaf6ba39 | 
@@ -8,7 +8,7 @@
 | 
				
			|||||||
      "version": "latest"
 | 
					      "version": "latest"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "ghcr.io/devcontainers/features/node:1": {
 | 
					    "ghcr.io/devcontainers/features/node:1": {
 | 
				
			||||||
      "version": 20
 | 
					      "version": 18
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "ghcr.io/devcontainers/features/common-utils:1": {
 | 
					    "ghcr.io/devcontainers/features/common-utils:1": {
 | 
				
			||||||
      "username": "vscode",
 | 
					      "username": "vscode",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,27 @@
 | 
				
			|||||||
 | 
					import defineAction from '../../../../helpers/define-action.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default defineAction({
 | 
				
			||||||
 | 
					  name: 'Find document',
 | 
				
			||||||
 | 
					  key: 'findDocument',
 | 
				
			||||||
 | 
					  description: 'Finds a document.',
 | 
				
			||||||
 | 
					  arguments: [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      label: 'Document ID',
 | 
				
			||||||
 | 
					      key: 'documentId',
 | 
				
			||||||
 | 
					      type: 'string',
 | 
				
			||||||
 | 
					      required: true,
 | 
				
			||||||
 | 
					      description: '',
 | 
				
			||||||
 | 
					      variables: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async run($) {
 | 
				
			||||||
 | 
					    const { documentId } = $.step.parameters;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { data } = await $.http.get(`/v1/documents/${documentId}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $.setActionItem({
 | 
				
			||||||
 | 
					      raw: data.document,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -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,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										5
									
								
								packages/backend/src/apps/pdf-monkey/actions/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								packages/backend/src/apps/pdf-monkey/actions/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					import deleteDocument from './delete-document/index.js';
 | 
				
			||||||
 | 
					import findDocument from './find-document/index.js';
 | 
				
			||||||
 | 
					import generateDocument from './generate-document/index.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default [deleteDocument, findDocument, generateDocument];
 | 
				
			||||||
							
								
								
									
										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  | 
							
								
								
									
										21
									
								
								packages/backend/src/apps/pdf-monkey/auth/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								packages/backend/src/apps/pdf-monkey/auth/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					import verifyCredentials from './verify-credentials.js';
 | 
				
			||||||
 | 
					import isStillVerified from './is-still-verified.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  fields: [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      key: 'apiKey',
 | 
				
			||||||
 | 
					      label: 'API Key',
 | 
				
			||||||
 | 
					      type: 'string',
 | 
				
			||||||
 | 
					      required: true,
 | 
				
			||||||
 | 
					      readOnly: false,
 | 
				
			||||||
 | 
					      value: null,
 | 
				
			||||||
 | 
					      placeholder: null,
 | 
				
			||||||
 | 
					      description: 'PDFMonkey API secret key of your account.',
 | 
				
			||||||
 | 
					      clickToCopy: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  verifyCredentials,
 | 
				
			||||||
 | 
					  isStillVerified,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -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,9 @@
 | 
				
			|||||||
 | 
					const addAuthHeader = ($, requestConfig) => {
 | 
				
			||||||
 | 
					  if ($.auth.data?.apiKey) {
 | 
				
			||||||
 | 
					    requestConfig.headers.Authorization = `Bearer ${$.auth.data.apiKey}`;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return requestConfig;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default addAuthHeader;
 | 
				
			||||||
@@ -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];
 | 
				
			||||||
@@ -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);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -1,16 +1,17 @@
 | 
				
			|||||||
const authorizationList = {
 | 
					const authorizationList = {
 | 
				
			||||||
  '/api/v1/users/:userId': {
 | 
					  'GET /api/v1/users/:userId': {
 | 
				
			||||||
    action: 'read',
 | 
					    action: 'read',
 | 
				
			||||||
    subject: 'User',
 | 
					    subject: 'User',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  '/api/v1/users/': {
 | 
					  'GET /api/v1/users/': {
 | 
				
			||||||
    action: 'read',
 | 
					    action: 'read',
 | 
				
			||||||
    subject: 'User',
 | 
					    subject: 'User',
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const authorizeUser = async (request, response, next) => {
 | 
					export const authorizeUser = async (request, response, next) => {
 | 
				
			||||||
  const currentRoute = request.baseUrl + request.route.path;
 | 
					  const currentRoute =
 | 
				
			||||||
 | 
					    request.method + ' ' + request.baseUrl + request.route.path;
 | 
				
			||||||
  const currentRouteRule = authorizationList[currentRoute];
 | 
					  const currentRouteRule = authorizationList[currentRoute];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
@@ -20,3 +21,13 @@ export const authorizeUser = async (request, response, next) => {
 | 
				
			|||||||
    return response.status(403).end();
 | 
					    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();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -4,8 +4,8 @@ import appConfig from '../config/app.js';
 | 
				
			|||||||
const levels = {
 | 
					const levels = {
 | 
				
			||||||
  error: 0,
 | 
					  error: 0,
 | 
				
			||||||
  warn: 1,
 | 
					  warn: 1,
 | 
				
			||||||
  info: 2,
 | 
					  http: 2,
 | 
				
			||||||
  http: 3,
 | 
					  info: 3,
 | 
				
			||||||
  debug: 4,
 | 
					  debug: 4,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,6 +15,8 @@ const renderObject = (response, object) => {
 | 
				
			|||||||
  let data = isPaginated(object) ? object.records : object;
 | 
					  let data = isPaginated(object) ? object.records : object;
 | 
				
			||||||
  const type = isPaginated(object)
 | 
					  const type = isPaginated(object)
 | 
				
			||||||
    ? object.records[0].constructor.name
 | 
					    ? object.records[0].constructor.name
 | 
				
			||||||
 | 
					    : Array.isArray(object)
 | 
				
			||||||
 | 
					    ? object[0].constructor.name
 | 
				
			||||||
    : object.constructor.name;
 | 
					    : object.constructor.name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const serializer = serializers[type];
 | 
					  const serializer = serializers[type];
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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;
 | 
				
			||||||
@@ -5,6 +5,7 @@ import paddleRouter from './paddle.ee.js';
 | 
				
			|||||||
import healthcheckRouter from './healthcheck.js';
 | 
					import healthcheckRouter from './healthcheck.js';
 | 
				
			||||||
import automatischRouter from './api/v1/automatisch.js';
 | 
					import automatischRouter from './api/v1/automatisch.js';
 | 
				
			||||||
import usersRouter from './api/v1/users.js';
 | 
					import usersRouter from './api/v1/users.js';
 | 
				
			||||||
 | 
					import samlAuthProvidersRouter from './api/v1/saml-auth-providers.ee.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const router = Router();
 | 
					const router = Router();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -14,5 +15,6 @@ router.use('/paddle', paddleRouter);
 | 
				
			|||||||
router.use('/healthcheck', healthcheckRouter);
 | 
					router.use('/healthcheck', healthcheckRouter);
 | 
				
			||||||
router.use('/api/v1/automatisch', automatischRouter);
 | 
					router.use('/api/v1/automatisch', automatischRouter);
 | 
				
			||||||
router.use('/api/v1/users', usersRouter);
 | 
					router.use('/api/v1/users', usersRouter);
 | 
				
			||||||
 | 
					router.use('/api/v1/admin/saml-auth-providers', samlAuthProvidersRouter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default router;
 | 
					export default router;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,13 @@
 | 
				
			|||||||
import userSerializer from './user.js';
 | 
					import userSerializer from './user.js';
 | 
				
			||||||
import roleSerializer from './role.js';
 | 
					import roleSerializer from './role.js';
 | 
				
			||||||
import permissionSerializer from './permission.js';
 | 
					import permissionSerializer from './permission.js';
 | 
				
			||||||
 | 
					import samlAuthProviderSerializer from './saml-auth-provider.ee.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const serializers = {
 | 
					const serializers = {
 | 
				
			||||||
  User: userSerializer,
 | 
					  User: userSerializer,
 | 
				
			||||||
  Role: roleSerializer,
 | 
					  Role: roleSerializer,
 | 
				
			||||||
  Permission: permissionSerializer,
 | 
					  Permission: permissionSerializer,
 | 
				
			||||||
 | 
					  SamlAuthProvider: samlAuthProviderSerializer,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default serializers;
 | 
					export default serializers;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										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
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										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);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										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;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -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;
 | 
				
			||||||
@@ -252,6 +252,16 @@ export default defineConfig({
 | 
				
			|||||||
            { text: 'Connection', link: '/apps/openai/connection' },
 | 
					            { 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',
 | 
					          text: 'Pipedrive',
 | 
				
			||||||
          collapsible: true,
 | 
					          collapsible: true,
 | 
				
			||||||
@@ -305,7 +315,7 @@ export default defineConfig({
 | 
				
			|||||||
          collapsed: true,
 | 
					          collapsed: true,
 | 
				
			||||||
          items: [
 | 
					          items: [
 | 
				
			||||||
            { text: 'Actions', link: '/apps/removebg/actions' },
 | 
					            { text: 'Actions', link: '/apps/removebg/actions' },
 | 
				
			||||||
            { text: 'Connection', link: '/apps/removebg/connection' }
 | 
					            { text: 'Connection', link: '/apps/removebg/connection' },
 | 
				
			||||||
          ],
 | 
					          ],
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										16
									
								
								packages/docs/pages/apps/pdf-monkey/actions.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								packages/docs/pages/apps/pdf-monkey/actions.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					favicon: /favicons/pdf-monkey.svg
 | 
				
			||||||
 | 
					items:
 | 
				
			||||||
 | 
					  - name: Generate documents
 | 
				
			||||||
 | 
					    desc: Creates a new document.
 | 
				
			||||||
 | 
					  - name: Find documents
 | 
				
			||||||
 | 
					    desc: Finds a 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 />
 | 
				
			||||||
@@ -26,6 +26,7 @@ The following integrations are currently supported by Automatisch.
 | 
				
			|||||||
- [Ntfy](/apps/ntfy/actions)
 | 
					- [Ntfy](/apps/ntfy/actions)
 | 
				
			||||||
- [Odoo](/apps/odoo/actions)
 | 
					- [Odoo](/apps/odoo/actions)
 | 
				
			||||||
- [OpenAI](/apps/openai/actions)
 | 
					- [OpenAI](/apps/openai/actions)
 | 
				
			||||||
 | 
					- [PDFMonkey](/apps/pdf-monkey/actions)
 | 
				
			||||||
- [Pipedrive](/apps/pipedrive/triggers)
 | 
					- [Pipedrive](/apps/pipedrive/triggers)
 | 
				
			||||||
- [Placetel](/apps/placetel/triggers)
 | 
					- [Placetel](/apps/placetel/triggers)
 | 
				
			||||||
- [PostgreSQL](/apps/postgresql/actions)
 | 
					- [PostgreSQL](/apps/postgresql/actions)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1487
									
								
								packages/docs/pages/public/favicons/pdf-monkey.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1487
									
								
								packages/docs/pages/public/favicons/pdf-monkey.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
		 After Width: | Height: | Size: 112 KiB  | 
@@ -3,6 +3,7 @@ import Box from '@mui/material/Box';
 | 
				
			|||||||
import Toolbar from '@mui/material/Toolbar';
 | 
					import Toolbar from '@mui/material/Toolbar';
 | 
				
			||||||
import { useTheme } from '@mui/material/styles';
 | 
					import { useTheme } from '@mui/material/styles';
 | 
				
			||||||
import useMediaQuery from '@mui/material/useMediaQuery';
 | 
					import useMediaQuery from '@mui/material/useMediaQuery';
 | 
				
			||||||
 | 
					import Stack from '@mui/material/Stack';
 | 
				
			||||||
import AppsIcon from '@mui/icons-material/Apps';
 | 
					import AppsIcon from '@mui/icons-material/Apps';
 | 
				
			||||||
import SwapCallsIcon from '@mui/icons-material/SwapCalls';
 | 
					import SwapCallsIcon from '@mui/icons-material/SwapCalls';
 | 
				
			||||||
import HistoryIcon from '@mui/icons-material/History';
 | 
					import HistoryIcon from '@mui/icons-material/History';
 | 
				
			||||||
@@ -139,7 +140,7 @@ export default function PublicLayout({
 | 
				
			|||||||
        onDrawerClose={closeDrawer}
 | 
					        onDrawerClose={closeDrawer}
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <Box sx={{ display: 'flex' }}>
 | 
					      <Box sx={{ display: 'flex', height: '100%' }}>
 | 
				
			||||||
        <Drawer
 | 
					        <Drawer
 | 
				
			||||||
          links={drawerLinks}
 | 
					          links={drawerLinks}
 | 
				
			||||||
          bottomLinks={bottomLinks}
 | 
					          bottomLinks={bottomLinks}
 | 
				
			||||||
@@ -148,11 +149,10 @@ export default function PublicLayout({
 | 
				
			|||||||
          onClose={closeDrawer}
 | 
					          onClose={closeDrawer}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <Box sx={{ flex: 1 }}>
 | 
					        <Stack flex={1}>
 | 
				
			||||||
          <Toolbar />
 | 
					          <Toolbar />
 | 
				
			||||||
 | 
					 | 
				
			||||||
          {children}
 | 
					          {children}
 | 
				
			||||||
        </Box>
 | 
					        </Stack>
 | 
				
			||||||
      </Box>
 | 
					      </Box>
 | 
				
			||||||
    </>
 | 
					    </>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										44
									
								
								packages/web/src/components/NotFound/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								packages/web/src/components/NotFound/index.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					import { Link } from 'react-router-dom';
 | 
				
			||||||
 | 
					import Button from '@mui/material/Button';
 | 
				
			||||||
 | 
					import Stack from '@mui/material/Stack';
 | 
				
			||||||
 | 
					import Typography from '@mui/material/Typography';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as URLS from 'config/urls';
 | 
				
			||||||
 | 
					import useFormatMessage from 'hooks/useFormatMessage';
 | 
				
			||||||
 | 
					import useAuthentication from 'hooks/useAuthentication';
 | 
				
			||||||
 | 
					import Layout from 'components/Layout';
 | 
				
			||||||
 | 
					import PublicLayout from 'components/PublicLayout';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function NoResultFound(): React.ReactElement {
 | 
				
			||||||
 | 
					  const formatMessage = useFormatMessage();
 | 
				
			||||||
 | 
					  const { isAuthenticated } = useAuthentication();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const pageContent = (
 | 
				
			||||||
 | 
					    <Stack
 | 
				
			||||||
 | 
					      justifyContent="center"
 | 
				
			||||||
 | 
					      alignItems="center"
 | 
				
			||||||
 | 
					      flex={1}
 | 
				
			||||||
 | 
					      spacing={1}
 | 
				
			||||||
 | 
					      p={2}
 | 
				
			||||||
 | 
					      mb={11}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <Typography variant="h1" color="primary" textAlign="center">
 | 
				
			||||||
 | 
					        404
 | 
				
			||||||
 | 
					      </Typography>
 | 
				
			||||||
 | 
					      <Typography variant="body1" textAlign="center">
 | 
				
			||||||
 | 
					        {formatMessage('notFoundPage.title')}
 | 
				
			||||||
 | 
					      </Typography>
 | 
				
			||||||
 | 
					      <Link to={isAuthenticated ? URLS.FLOWS : URLS.LOGIN}>
 | 
				
			||||||
 | 
					        <Button variant="contained" sx={{ mt: 3 }} component="div">
 | 
				
			||||||
 | 
					          {formatMessage('notFoundPage.button')}
 | 
				
			||||||
 | 
					        </Button>
 | 
				
			||||||
 | 
					      </Link>
 | 
				
			||||||
 | 
					    </Stack>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return isAuthenticated ? (
 | 
				
			||||||
 | 
					    <Layout>{pageContent}</Layout>
 | 
				
			||||||
 | 
					  ) : (
 | 
				
			||||||
 | 
					    <PublicLayout>{pageContent}</PublicLayout>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -265,5 +265,7 @@
 | 
				
			|||||||
  "authClient.buttonSubmit": "Submit",
 | 
					  "authClient.buttonSubmit": "Submit",
 | 
				
			||||||
  "authClient.inputName": "Name",
 | 
					  "authClient.inputName": "Name",
 | 
				
			||||||
  "authClient.inputActive": "Active",
 | 
					  "authClient.inputActive": "Active",
 | 
				
			||||||
  "updateAuthClient.title": "Update auth client"
 | 
					  "updateAuthClient.title": "Update auth client",
 | 
				
			||||||
 | 
					  "notFoundPage.title": "We can't seem to find a page you're looking for.",
 | 
				
			||||||
 | 
					  "notFoundPage.button": "Back to home page"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import { Route, Routes, Navigate } from 'react-router-dom';
 | 
					import { Route, Routes as ReactRouterRoutes, Navigate } from 'react-router-dom';
 | 
				
			||||||
import Layout from 'components/Layout';
 | 
					import Layout from 'components/Layout';
 | 
				
			||||||
 | 
					import NoResultFound from 'components/NotFound';
 | 
				
			||||||
import PublicLayout from 'components/PublicLayout';
 | 
					import PublicLayout from 'components/PublicLayout';
 | 
				
			||||||
import Applications from 'pages/Applications';
 | 
					import Applications from 'pages/Applications';
 | 
				
			||||||
import Application from 'pages/Application';
 | 
					import Application from 'pages/Application';
 | 
				
			||||||
@@ -17,127 +18,134 @@ import * as URLS from 'config/urls';
 | 
				
			|||||||
import settingsRoutes from './settingsRoutes';
 | 
					import settingsRoutes from './settingsRoutes';
 | 
				
			||||||
import adminSettingsRoutes from './adminSettingsRoutes';
 | 
					import adminSettingsRoutes from './adminSettingsRoutes';
 | 
				
			||||||
import Notifications from 'pages/Notifications';
 | 
					import Notifications from 'pages/Notifications';
 | 
				
			||||||
 | 
					import useConfig from 'hooks/useConfig';
 | 
				
			||||||
 | 
					import useAuthentication from 'hooks/useAuthentication';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default (
 | 
					function Routes() {
 | 
				
			||||||
  <Routes>
 | 
					  const { config } = useConfig();
 | 
				
			||||||
    <Route
 | 
					  const { isAuthenticated } = useAuthentication();
 | 
				
			||||||
      path={URLS.EXECUTIONS}
 | 
					 | 
				
			||||||
      element={
 | 
					 | 
				
			||||||
        <Layout>
 | 
					 | 
				
			||||||
          <Executions />
 | 
					 | 
				
			||||||
        </Layout>
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    />
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <Route
 | 
					  return (
 | 
				
			||||||
      path={URLS.EXECUTION_PATTERN}
 | 
					    <ReactRouterRoutes>
 | 
				
			||||||
      element={
 | 
					      <Route
 | 
				
			||||||
        <Layout>
 | 
					        path={URLS.EXECUTIONS}
 | 
				
			||||||
          <Execution />
 | 
					        element={
 | 
				
			||||||
        </Layout>
 | 
					          <Layout>
 | 
				
			||||||
      }
 | 
					            <Executions />
 | 
				
			||||||
    />
 | 
					          </Layout>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <Route
 | 
					      <Route
 | 
				
			||||||
      path={URLS.FLOWS}
 | 
					        path={URLS.EXECUTION_PATTERN}
 | 
				
			||||||
      element={
 | 
					        element={
 | 
				
			||||||
        <Layout>
 | 
					          <Layout>
 | 
				
			||||||
          <Flows />
 | 
					            <Execution />
 | 
				
			||||||
        </Layout>
 | 
					          </Layout>
 | 
				
			||||||
      }
 | 
					        }
 | 
				
			||||||
    />
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <Route
 | 
					      <Route
 | 
				
			||||||
      path={URLS.FLOW_PATTERN}
 | 
					        path={URLS.FLOWS}
 | 
				
			||||||
      element={
 | 
					        element={
 | 
				
			||||||
        <Layout>
 | 
					          <Layout>
 | 
				
			||||||
          <Flow />
 | 
					            <Flows />
 | 
				
			||||||
        </Layout>
 | 
					          </Layout>
 | 
				
			||||||
      }
 | 
					        }
 | 
				
			||||||
    />
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <Route
 | 
					      <Route
 | 
				
			||||||
      path={`${URLS.APPS}/*`}
 | 
					        path={URLS.FLOW_PATTERN}
 | 
				
			||||||
      element={
 | 
					        element={
 | 
				
			||||||
        <Layout>
 | 
					          <Layout>
 | 
				
			||||||
          <Applications />
 | 
					            <Flow />
 | 
				
			||||||
        </Layout>
 | 
					          </Layout>
 | 
				
			||||||
      }
 | 
					        }
 | 
				
			||||||
    />
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <Route
 | 
					      <Route
 | 
				
			||||||
      path={`${URLS.APP_PATTERN}/*`}
 | 
					        path={`${URLS.APPS}/*`}
 | 
				
			||||||
      element={
 | 
					        element={
 | 
				
			||||||
        <Layout>
 | 
					          <Layout>
 | 
				
			||||||
          <Application />
 | 
					            <Applications />
 | 
				
			||||||
        </Layout>
 | 
					          </Layout>
 | 
				
			||||||
      }
 | 
					        }
 | 
				
			||||||
    />
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <Route path={`${URLS.EDITOR}/*`} element={<EditorRoutes />} />
 | 
					      <Route
 | 
				
			||||||
 | 
					        path={`${URLS.APP_PATTERN}/*`}
 | 
				
			||||||
 | 
					        element={
 | 
				
			||||||
 | 
					          <Layout>
 | 
				
			||||||
 | 
					            <Application />
 | 
				
			||||||
 | 
					          </Layout>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <Route
 | 
					      <Route path={`${URLS.EDITOR}/*`} element={<EditorRoutes />} />
 | 
				
			||||||
      path={URLS.LOGIN}
 | 
					 | 
				
			||||||
      element={
 | 
					 | 
				
			||||||
        <PublicLayout>
 | 
					 | 
				
			||||||
          <Login />
 | 
					 | 
				
			||||||
        </PublicLayout>
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    />
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <Route
 | 
					      <Route
 | 
				
			||||||
      path={URLS.LOGIN_CALLBACK}
 | 
					        path={URLS.LOGIN}
 | 
				
			||||||
      element={<LoginCallback />}
 | 
					        element={
 | 
				
			||||||
    />
 | 
					          <PublicLayout>
 | 
				
			||||||
 | 
					            <Login />
 | 
				
			||||||
 | 
					          </PublicLayout>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <Route
 | 
					      <Route path={URLS.LOGIN_CALLBACK} element={<LoginCallback />} />
 | 
				
			||||||
      path={URLS.SIGNUP}
 | 
					 | 
				
			||||||
      element={
 | 
					 | 
				
			||||||
        <PublicLayout>
 | 
					 | 
				
			||||||
          <SignUp />
 | 
					 | 
				
			||||||
        </PublicLayout>
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    />
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <Route
 | 
					      <Route
 | 
				
			||||||
      path={URLS.FORGOT_PASSWORD}
 | 
					        path={URLS.SIGNUP}
 | 
				
			||||||
      element={
 | 
					        element={
 | 
				
			||||||
        <PublicLayout>
 | 
					          <PublicLayout>
 | 
				
			||||||
          <ForgotPassword />
 | 
					            <SignUp />
 | 
				
			||||||
        </PublicLayout>
 | 
					          </PublicLayout>
 | 
				
			||||||
      }
 | 
					        }
 | 
				
			||||||
    />
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <Route
 | 
					      <Route
 | 
				
			||||||
      path={URLS.RESET_PASSWORD}
 | 
					        path={URLS.FORGOT_PASSWORD}
 | 
				
			||||||
      element={
 | 
					        element={
 | 
				
			||||||
        <PublicLayout>
 | 
					          <PublicLayout>
 | 
				
			||||||
          <ResetPassword />
 | 
					            <ForgotPassword />
 | 
				
			||||||
        </PublicLayout>
 | 
					          </PublicLayout>
 | 
				
			||||||
      }
 | 
					        }
 | 
				
			||||||
    />
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <Route
 | 
					      <Route
 | 
				
			||||||
      path={URLS.UPDATES}
 | 
					        path={URLS.RESET_PASSWORD}
 | 
				
			||||||
      element={
 | 
					        element={
 | 
				
			||||||
        <Layout>
 | 
					          <PublicLayout>
 | 
				
			||||||
          <Notifications />
 | 
					            <ResetPassword />
 | 
				
			||||||
        </Layout>
 | 
					          </PublicLayout>
 | 
				
			||||||
      }
 | 
					        }
 | 
				
			||||||
    />
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <Route path="/" element={<Navigate to={URLS.FLOWS} replace />} />
 | 
					      {!config?.disableNotificationsPage && (
 | 
				
			||||||
 | 
					        <Route
 | 
				
			||||||
 | 
					          path={URLS.UPDATES}
 | 
				
			||||||
 | 
					          element={
 | 
				
			||||||
 | 
					            <Layout>
 | 
				
			||||||
 | 
					              <Notifications />
 | 
				
			||||||
 | 
					            </Layout>
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <Route path={URLS.SETTINGS}>{settingsRoutes}</Route>
 | 
					      <Route
 | 
				
			||||||
 | 
					        path="/"
 | 
				
			||||||
 | 
					        element={
 | 
				
			||||||
 | 
					          <Navigate to={isAuthenticated ? URLS.FLOWS : URLS.LOGIN} replace />
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <Route path={URLS.ADMIN_SETTINGS}>{adminSettingsRoutes}</Route>
 | 
					      <Route path={URLS.SETTINGS}>{settingsRoutes}</Route>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <Route
 | 
					      <Route path={URLS.ADMIN_SETTINGS}>{adminSettingsRoutes}</Route>
 | 
				
			||||||
      element={
 | 
					
 | 
				
			||||||
        <Layout>
 | 
					      <Route path="*" element={<NoResultFound />} />
 | 
				
			||||||
          <div>404</div>
 | 
					    </ReactRouterRoutes>
 | 
				
			||||||
        </Layout>
 | 
					  );
 | 
				
			||||||
      }
 | 
					}
 | 
				
			||||||
    />
 | 
					
 | 
				
			||||||
  </Routes>
 | 
					export default <Routes />;
 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user