Compare commits
	
		
			167 Commits
		
	
	
		
			dependabot
			...
			custom-see
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 7dee26c16d | ||
|   | 3d6847a3a2 | ||
|   | 613abaec1b | ||
|   | b51ae9ac38 | ||
|   | c4fd03542b | ||
|   | e40d6c5ef0 | ||
|   | 9f7dee3baa | ||
|   | d3da62c04a | ||
|   | aa7bb3f8c9 | ||
|   | 3a9dfe339a | ||
|   | 08830003a3 | ||
|   | efeeb6cb02 | ||
|   | ac59ce2deb | ||
|   | 3ff89a03ac | ||
|   | 25e231cd7c | ||
|   | f4d8d909b0 | ||
|   | 35ea18a117 | ||
|   | ee9433261b | ||
|   | 1cb48c7760 | ||
|   | 172bf4bd51 | ||
|   | fe1039cfbc | ||
|   | a9de79546b | ||
|   | 7afdf43872 | ||
|   | bfc7d5d0dd | ||
|   | 690832052a | ||
|   | f7c1a47d52 | ||
|   | 930843d065 | ||
|   | a9ee609502 | ||
|   | 9fd2125923 | ||
|   | ede8703f9d | ||
|   | 6d85623d9b | ||
|   | 6236ee8f6d | ||
|   | 92665d80d6 | ||
|   | 70ae0bc77e | ||
|   | e28c757352 | ||
|   | 7cdcf7ebab | ||
|   | 7c368af5ed | ||
|   | df2fbbabc6 | ||
|   | 48141eb199 | ||
|   | 343fbb282c | ||
|   | cea9ed056b | ||
|   | 73bd90c555 | ||
|   | 917de46c45 | ||
|   | b592092923 | ||
|   | 760bc1c22a | ||
|   | 1f8b81ee78 | ||
|   | 70af7d05e3 | ||
|   | 14c04ee4ac | ||
|   | 83815d3caa | ||
|   | 487c621fa5 | ||
|   | 304eab801c | ||
|   | dfe3add1cc | ||
|   | a32bf5539e | ||
|   | e944333e5f | ||
|   | dad23a52b0 | ||
|   | 53606c306d | ||
|   | 53b03f8231 | ||
|   | ac5f6dc024 | ||
|   | 2c15e0dd32 | ||
|   | 007334fef0 | ||
|   | b3ae2d2748 | ||
|   | 7149c766d0 | ||
|   | 5dca0191d2 | ||
|   | 356668a68d | ||
|   | 63c9442126 | ||
|   | 0031d7911d | ||
|   | 31c92b43b4 | ||
|   | 2667548041 | ||
|   | 54282ba7e0 | ||
|   | 7f324abd44 | ||
|   | 65a0c3b40a | ||
|   | 2449baac5b | ||
|   | 0ab03e1856 | ||
|   | 9a3f85106c | ||
|   | 42c495d8ab | ||
|   | 58def585f1 | ||
|   | 047034d831 | ||
|   | bdb2f24a81 | ||
|   | 636870a075 | ||
|   | 8981174302 | ||
|   | dd5f05334b | ||
|   | 929b626b51 | ||
|   | 7d5b2ec81e | ||
|   | f0e2d36c34 | ||
|   | 94f171d757 | ||
|   | 04e06db430 | ||
|   | d74b215169 | ||
|   | 404ea94dd2 | ||
|   | 4afe7c6b46 | ||
|   | 60b20c4d01 | ||
|   | 58658c6b1a | ||
|   | ec444317b3 | ||
|   | 8b4aee1afa | ||
|   | 51abd74304 | ||
|   | b93b465f09 | ||
|   | 5aad68ec62 | ||
|   | 74fbc937a1 | ||
|   | 7e35f544eb | ||
|   | ed1c3cffc1 | ||
|   | c4983a9f9b | ||
|   | 5b43262e7a | ||
|   | dad4408679 | ||
|   | a78c4d12b4 | ||
|   | 74664a9df8 | ||
|   | fce5281a03 | ||
|   | de0bd2f486 | ||
|   | 079fb5d108 | ||
|   | 1c7435a32b | ||
|   | 1afd374cf6 | ||
|   | 3adf549915 | ||
|   | e94d669eca | ||
|   | 5fac0b4689 | ||
|   | 832d323a6e | ||
|   | 03f1dbd5b2 | ||
|   | c0a216f109 | ||
|   | ad67b13270 | ||
|   | 5d420c08c6 | ||
|   | 3d8235c670 | ||
|   | 5a209f81d1 | ||
|   | d17d8e2805 | ||
|   | ca7636e7bc | ||
|   | 532cfc10d0 | ||
|   | 72d68c4377 | ||
|   | 00f5964aa4 | ||
|   | fcf345abab | ||
|   | 24ad43d3e4 | ||
|   | 9a7cdf42e1 | ||
|   | c36b652d5b | ||
|   | 553070fc23 | ||
|   | 5d69f7e24f | ||
|   | bc0e2bada0 | ||
|   | 80b6cc1d94 | ||
|   | bce3273e64 | ||
|   | 3abf61152a | ||
|   | 14923d4cd6 | ||
|   | 6fdc4bf900 | ||
|   | d21e1f75b5 | ||
|   | 84a0b37fcc | ||
|   | f135a0f09e | ||
|   | 0f24c99456 | ||
|   | 9eae0ab947 | ||
|   | 3bf1f79c79 | ||
|   | b21074c871 | ||
|   | d7893d9a32 | ||
|   | 9cbdda330c | ||
|   | 42a9bfd099 | ||
|   | eb15bd01ca | ||
|   | 9e98aebeb3 | ||
|   | 1361cbc826 | ||
|   | 679d0808a9 | ||
|   | 6fe9a548ad | ||
|   | 2d6d2430d2 | ||
|   | a445538e81 | ||
|   | 50d38ffbd8 | ||
|   | 93bcdfd9c9 | ||
|   | 5be3b101a5 | ||
|   | 024c7476c7 | ||
|   | 30a7ffe93d | ||
|   | e2d803ebf7 | ||
|   | be7e67c940 | ||
|   | ead4b13ba5 | ||
|   | e02c42ee18 | ||
|   | d39886fdf8 | ||
|   | 11a425f1de | ||
|   | 37e1acc5f1 | ||
|   | ffaf6a577d | ||
|   | afdaf6ba39 | 
| @@ -28,7 +28,7 @@ cd packages/web | ||||
| rm -rf .env | ||||
| echo " | ||||
| PORT=$WEB_PORT | ||||
| REACT_APP_GRAPHQL_URL=http://localhost:$BACKEND_PORT/graphql | ||||
| REACT_APP_BACKEND_URL=http://localhost:$BACKEND_PORT | ||||
| " >> .env | ||||
| cd $CURRENT_DIR | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
|       "version": "latest" | ||||
|     }, | ||||
|     "ghcr.io/devcontainers/features/node:1": { | ||||
|       "version": 20 | ||||
|       "version": 18 | ||||
|     }, | ||||
|     "ghcr.io/devcontainers/features/common-utils:1": { | ||||
|       "username": "vscode", | ||||
|   | ||||
							
								
								
									
										18
									
								
								.eslintrc.js
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								.eslintrc.js
									
									
									
									
									
								
							| @@ -1,18 +0,0 @@ | ||||
| module.exports = { | ||||
|   root: true, | ||||
|   parser: '@typescript-eslint/parser', | ||||
|   plugins: ['@typescript-eslint'], | ||||
|   extends: [ | ||||
|     'eslint:recommended', | ||||
|     'plugin:@typescript-eslint/recommended', | ||||
|     'prettier', | ||||
|   ], | ||||
|   overrides: [ | ||||
|     { | ||||
|       files: ['**/*.test.ts', '**/test/**/*.ts'], | ||||
|       rules: { | ||||
|         '@typescript-eslint/ban-ts-comment': ['off'], | ||||
|       }, | ||||
|     }, | ||||
|   ], | ||||
| }; | ||||
							
								
								
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -22,7 +22,7 @@ jobs: | ||||
|       - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." | ||||
|       - run: echo "🖥️ The workflow is now ready to test your code on the runner." | ||||
|       - run: yarn --frozen-lockfile | ||||
|       - run: yarn lint | ||||
|       - run: cd packages/backend && yarn lint | ||||
|       - run: echo "🍏 This job's status is ${{ job.status }}." | ||||
|   start-backend-server: | ||||
|     runs-on: ubuntu-latest | ||||
|   | ||||
							
								
								
									
										5
									
								
								.github/workflows/playwright.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/workflows/playwright.yml
									
									
									
									
										vendored
									
									
								
							| @@ -62,8 +62,9 @@ jobs: | ||||
|         run: yarn && yarn lerna bootstrap | ||||
|       - name: Install Playwright Browsers | ||||
|         run: yarn playwright install --with-deps | ||||
|       - name: Build Automatisch | ||||
|         run: yarn lerna run --scope=@*/{web,cli} build | ||||
|       - name: Build Automatisch web | ||||
|         working-directory: ./packages/web | ||||
|         run: yarn build | ||||
|         env: | ||||
|           # Keep this until we clean up warnings in build processes | ||||
|           CI: false | ||||
|   | ||||
| @@ -6,7 +6,6 @@ | ||||
|     "start": "lerna run --stream --parallel --scope=@*/{web,backend} dev", | ||||
|     "start:web": "lerna run --stream --scope=@*/web dev", | ||||
|     "start:backend": "lerna run --stream --scope=@*/backend dev", | ||||
|     "lint": "lerna run --no-bail --stream --parallel --scope=@*/{web,backend} lint", | ||||
|     "build:docs": "cd ./packages/docs && yarn install && yarn build" | ||||
|   }, | ||||
|   "workspaces": { | ||||
| @@ -21,8 +20,6 @@ | ||||
|     ] | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@typescript-eslint/eslint-plugin": "^5.9.1", | ||||
|     "@typescript-eslint/parser": "^5.9.1", | ||||
|     "eslint": "^8.13.0", | ||||
|     "eslint-config-prettier": "^8.3.0", | ||||
|     "eslint-plugin-prettier": "^4.0.0", | ||||
|   | ||||
| @@ -18,8 +18,8 @@ async function fetchAdminRole() { | ||||
| } | ||||
|  | ||||
| export async function createUser( | ||||
|   email = 'user@automatisch.io', | ||||
|   password = 'sample' | ||||
|   email = appConfig.seedUserEmail, | ||||
|   password = appConfig.seedUserPassword | ||||
| ) { | ||||
|   const UNIQUE_VIOLATION_CODE = '23505'; | ||||
|  | ||||
|   | ||||
| @@ -11,7 +11,7 @@ | ||||
|     "start:worker": "node src/worker.js", | ||||
|     "pretest": "APP_ENV=test node ./test/setup/prepare-test-env.js", | ||||
|     "test": "APP_ENV=test vitest run", | ||||
|     "lint": "eslint . --ignore-path ../../.eslintignore", | ||||
|     "lint": "eslint .", | ||||
|     "db:create": "node ./bin/database/create.js", | ||||
|     "db:seed:user": "node ./bin/database/seed-user.js", | ||||
|     "db:drop": "node ./bin/database/drop.js", | ||||
| @@ -38,6 +38,7 @@ | ||||
|     "debug": "~2.6.9", | ||||
|     "dotenv": "^10.0.0", | ||||
|     "express": "~4.18.2", | ||||
|     "express-async-handler": "^1.2.0", | ||||
|     "express-basic-auth": "^1.2.1", | ||||
|     "express-graphql": "^0.12.0", | ||||
|     "fast-xml-parser": "^4.0.11", | ||||
|   | ||||
| @@ -0,0 +1,27 @@ | ||||
| import defineAction from '../../../../helpers/define-action.js'; | ||||
|  | ||||
| export default defineAction({ | ||||
|   name: 'Get value', | ||||
|   key: 'getValue', | ||||
|   description: 'Get value from the persistent datastore.', | ||||
|   arguments: [ | ||||
|     { | ||||
|       label: 'Key', | ||||
|       key: 'key', | ||||
|       type: 'string', | ||||
|       required: true, | ||||
|       description: 'The key of your value to get.', | ||||
|       variables: true, | ||||
|     }, | ||||
|   ], | ||||
|  | ||||
|   async run($) { | ||||
|     const keyValuePair = await $.datastore.get({ | ||||
|       key: $.step.parameters.key, | ||||
|     }); | ||||
|  | ||||
|     $.setActionItem({ | ||||
|       raw: keyValuePair, | ||||
|     }); | ||||
|   }, | ||||
| }); | ||||
							
								
								
									
										4
									
								
								packages/backend/src/apps/datastore/actions/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								packages/backend/src/apps/datastore/actions/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| import getValue from './get-value/index.js'; | ||||
| import setValue from './set-value/index.js'; | ||||
|  | ||||
| export default [getValue, setValue]; | ||||
| @@ -0,0 +1,36 @@ | ||||
| import defineAction from '../../../../helpers/define-action.js'; | ||||
|  | ||||
| export default defineAction({ | ||||
|   name: 'Set value', | ||||
|   key: 'setValue', | ||||
|   description: 'Set value to the persistent datastore.', | ||||
|   arguments: [ | ||||
|     { | ||||
|       label: 'Key', | ||||
|       key: 'key', | ||||
|       type: 'string', | ||||
|       required: true, | ||||
|       description: 'The key of your value to set.', | ||||
|       variables: true, | ||||
|     }, | ||||
|     { | ||||
|       label: 'Value', | ||||
|       key: 'value', | ||||
|       type: 'string', | ||||
|       required: true, | ||||
|       description: 'The value to set.', | ||||
|       variables: true, | ||||
|     }, | ||||
|   ], | ||||
|  | ||||
|   async run($) { | ||||
|     const keyValuePair = await $.datastore.set({ | ||||
|       key: $.step.parameters.key, | ||||
|       value: $.step.parameters.value, | ||||
|     }); | ||||
|  | ||||
|     $.setActionItem({ | ||||
|       raw: keyValuePair, | ||||
|     }); | ||||
|   }, | ||||
| }); | ||||
							
								
								
									
										13
									
								
								packages/backend/src/apps/datastore/assets/favicon.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								packages/backend/src/apps/datastore/assets/favicon.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| <?xml version="1.0"?> | ||||
| <svg xmlns="http://www.w3.org/2000/svg" fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" id="icon"> | ||||
|   <defs> | ||||
|     <style>.cls-1{fill:none;}</style> | ||||
|   </defs> | ||||
|   <title>datastore</title> | ||||
|   <circle cx="23" cy="23" r="1"/> | ||||
|   <rect x="8" y="22" width="12" height="2"/> | ||||
|   <circle cx="23" cy="9" r="1"/> | ||||
|   <rect x="8" y="8" width="12" height="2"/> | ||||
|   <path d="M26,14a2,2,0,0,0,2-2V6a2,2,0,0,0-2-2H6A2,2,0,0,0,4,6v6a2,2,0,0,0,2,2H8v4H6a2,2,0,0,0-2,2v6a2,2,0,0,0,2,2H26a2,2,0,0,0,2-2V20a2,2,0,0,0-2-2H24V14ZM6,6H26v6H6ZM26,26H6V20H26Zm-4-8H10V14H22Z"/> | ||||
|   <rect id="_Transparent_Rectangle_" data-name="<Transparent Rectangle>" class="cls-1" width="32" height="32"/> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 704 B | 
							
								
								
									
										14
									
								
								packages/backend/src/apps/datastore/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								packages/backend/src/apps/datastore/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| import defineApp from '../../helpers/define-app.js'; | ||||
| import actions from './actions/index.js'; | ||||
|  | ||||
| export default defineApp({ | ||||
|   name: 'Datastore', | ||||
|   key: 'datastore', | ||||
|   iconUrl: '{BASE_URL}/apps/datastore/assets/favicon.svg', | ||||
|   authDocUrl: 'https://automatisch.io/docs/apps/datastore/connection', | ||||
|   supportsConnections: false, | ||||
|   baseUrl: '', | ||||
|   apiBaseUrl: '', | ||||
|   primaryColor: '001F52', | ||||
|   actions, | ||||
| }); | ||||
| @@ -1,4 +1,3 @@ | ||||
| import FormData from 'form-data'; | ||||
| import defineAction from '../../../../helpers/define-action.js'; | ||||
|  | ||||
| export default defineAction({ | ||||
| @@ -6,45 +5,51 @@ export default defineAction({ | ||||
|   key: 'newChat', | ||||
|   description: 'Create a new chat session for Helix AI.', | ||||
|   arguments: [ | ||||
|     { | ||||
|       label: 'Session ID', | ||||
|       key: 'sessionId', | ||||
|       type: 'string', | ||||
|       required: false, | ||||
|       description: | ||||
|         'ID of the chat session to continue. Leave empty to start a new chat.', | ||||
|       variables: true, | ||||
|     }, | ||||
|     { | ||||
|       label: 'System Prompt', | ||||
|       key: 'systemPrompt', | ||||
|       type: 'string', | ||||
|       required: false, | ||||
|       description: | ||||
|         'Optional system prompt to start the chat with. It will be used only for new chat sessions.', | ||||
|       variables: true, | ||||
|     }, | ||||
|     { | ||||
|       label: 'Input', | ||||
|       key: 'input', | ||||
|       type: 'string', | ||||
|       required: true, | ||||
|       description: 'Prompt to start the chat with.', | ||||
|       description: 'User input to start the chat with.', | ||||
|       variables: true, | ||||
|     }, | ||||
|   ], | ||||
|  | ||||
|   async run($) { | ||||
|     const formData = new FormData(); | ||||
|     formData.append('input', $.step.parameters.input); | ||||
|     formData.append('mode', 'inference'); | ||||
|     formData.append('type', 'text'); | ||||
|  | ||||
|     const sessionResponse = await $.http.post('/api/v1/sessions', formData, { | ||||
|       headers: { | ||||
|         ...formData.getHeaders(), | ||||
|       }, | ||||
|     const response = await $.http.post('/api/v1/sessions/chat', { | ||||
|       session_id: $.step.parameters.sessionId, | ||||
|       system: $.step.parameters.systemPrompt, | ||||
|       messages: [ | ||||
|         { | ||||
|           role: 'user', | ||||
|           content: { | ||||
|             content_type: 'text', | ||||
|             parts: [$.step.parameters.input], | ||||
|           }, | ||||
|         }, | ||||
|       ], | ||||
|     }); | ||||
|  | ||||
|     const sessionId = sessionResponse.data.id; | ||||
|  | ||||
|     let chatGenerated = false; | ||||
|  | ||||
|     while (!chatGenerated) { | ||||
|       const response = await $.http.get(`/api/v1/sessions/${sessionId}`); | ||||
|  | ||||
|       const message = | ||||
|         response.data.interactions[response.data.interactions.length - 1]; | ||||
|  | ||||
|       if (message.creator === 'system' && message.state === 'complete') { | ||||
|         $.setActionItem({ | ||||
|           raw: message, | ||||
|         }); | ||||
|  | ||||
|         chatGenerated = true; | ||||
|       } | ||||
|     } | ||||
|     $.setActionItem({ | ||||
|       raw: response.data, | ||||
|     }); | ||||
|   }, | ||||
| }); | ||||
|   | ||||
| @@ -94,6 +94,8 @@ const appConfig = { | ||||
|   disableFavicon: process.env.DISABLE_FAVICON === 'true', | ||||
|   additionalDrawerLink: process.env.ADDITIONAL_DRAWER_LINK, | ||||
|   additionalDrawerLinkText: process.env.ADDITIONAL_DRAWER_LINK_TEXT, | ||||
|   seedUserEmail: process.env.SEED_USER_EMAIL || 'user@automatisch.io', | ||||
|   seedUserPassword: process.env.SEED_USER_PASSWORD || 'sample', | ||||
| }; | ||||
|  | ||||
| if (!appConfig.encryptionKey) { | ||||
|   | ||||
| @@ -0,0 +1,10 @@ | ||||
| import { renderObject } from '../../../../../helpers/renderer.js'; | ||||
| import AppAuthClient from '../../../../../models/app-auth-client.js'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   const appAuthClient = await AppAuthClient.query() | ||||
|     .findById(request.params.appAuthClientId) | ||||
|     .throwIfNotFound(); | ||||
|  | ||||
|   renderObject(response, appAuthClient); | ||||
| }; | ||||
| @@ -0,0 +1,52 @@ | ||||
| import { vi, describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import Crypto from 'crypto'; | ||||
| import app from '../../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js'; | ||||
| import { createUser } from '../../../../../../test/factories/user.js'; | ||||
| import getAdminAppAuthClientMock from '../../../../../../test/mocks/rest/api/v1/admin/get-app-auth-client.js'; | ||||
| import { createAppAuthClient } from '../../../../../../test/factories/app-auth-client.js'; | ||||
| import { createRole } from '../../../../../../test/factories/role.js'; | ||||
| import * as license from '../../../../../helpers/license.ee.js'; | ||||
|  | ||||
| describe('GET /api/v1/admin/app-auth-clients/:appAuthClientId', () => { | ||||
|   let currentUser, currentUserRole, currentAppAuthClient, token; | ||||
|  | ||||
|   describe('with valid license key', () => { | ||||
|     beforeEach(async () => { | ||||
|       vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||
|  | ||||
|       currentUserRole = await createRole({ key: 'admin' }); | ||||
|       currentUser = await createUser({ roleId: currentUserRole.id }); | ||||
|       currentAppAuthClient = await createAppAuthClient(); | ||||
|  | ||||
|       token = createAuthTokenByUserId(currentUser.id); | ||||
|     }); | ||||
|  | ||||
|     it('should return specified app auth client info', async () => { | ||||
|       const response = await request(app) | ||||
|         .get(`/api/v1/admin/app-auth-clients/${currentAppAuthClient.id}`) | ||||
|         .set('Authorization', token) | ||||
|         .expect(200); | ||||
|  | ||||
|       const expectedPayload = getAdminAppAuthClientMock(currentAppAuthClient); | ||||
|       expect(response.body).toEqual(expectedPayload); | ||||
|     }); | ||||
|  | ||||
|     it('should return not found response for not existing app auth client UUID', async () => { | ||||
|       const notExistingAppAuthClientUUID = Crypto.randomUUID(); | ||||
|  | ||||
|       await request(app) | ||||
|         .get(`/api/v1/admin/app-auth-clients/${notExistingAppAuthClientUUID}`) | ||||
|         .set('Authorization', token) | ||||
|         .expect(404); | ||||
|     }); | ||||
|  | ||||
|     it('should return bad request response for invalid UUID', async () => { | ||||
|       await request(app) | ||||
|         .get('/api/v1/admin/app-auth-clients/invalidAppAuthClientUUID') | ||||
|         .set('Authorization', token) | ||||
|         .expect(400); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,6 @@ | ||||
| import { renderObject } from '../../../../../helpers/renderer.js'; | ||||
| import permissionCatalog from '../../../../../helpers/permission-catalog.ee.js'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   renderObject(response, permissionCatalog); | ||||
| }; | ||||
| @@ -0,0 +1,32 @@ | ||||
| import { vi, describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import app from '../../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js'; | ||||
| import { createRole } from '../../../../../../test/factories/role.js'; | ||||
| import { createUser } from '../../../../../../test/factories/user.js'; | ||||
| import getPermissionsCatalogMock from '../../../../../../test/mocks/rest/api/v1/admin/permissions/get-permissions-catalog.ee.js'; | ||||
| import * as license from '../../../../../helpers/license.ee.js'; | ||||
|  | ||||
| describe('GET /api/v1/admin/permissions/catalog', () => { | ||||
|   let role, currentUser, token; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     role = await createRole({ key: 'admin' }); | ||||
|     currentUser = await createUser({ roleId: role.id }); | ||||
|  | ||||
|     token = createAuthTokenByUserId(currentUser.id); | ||||
|   }); | ||||
|  | ||||
|   it('should return roles', async () => { | ||||
|     vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get('/api/v1/admin/permissions/catalog') | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = await getPermissionsCatalogMock(); | ||||
|  | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,16 @@ | ||||
| import { renderObject } from '../../../../../helpers/renderer.js'; | ||||
| import Role from '../../../../../models/role.js'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   const role = await Role.query() | ||||
|     .leftJoinRelated({ | ||||
|       permissions: true, | ||||
|     }) | ||||
|     .withGraphFetched({ | ||||
|       permissions: true, | ||||
|     }) | ||||
|     .findById(request.params.roleId) | ||||
|     .throwIfNotFound(); | ||||
|  | ||||
|   renderObject(response, role); | ||||
| }; | ||||
| @@ -0,0 +1,59 @@ | ||||
| import { vi, describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import Crypto from 'crypto'; | ||||
| import app from '../../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js'; | ||||
| import { createRole } from '../../../../../../test/factories/role.js'; | ||||
| import { createUser } from '../../../../../../test/factories/user.js'; | ||||
| import { createPermission } from '../../../../../../test/factories/permission.js'; | ||||
| import getRoleMock from '../../../../../../test/mocks/rest/api/v1/admin/roles/get-role.ee.js'; | ||||
| import * as license from '../../../../../helpers/license.ee.js'; | ||||
|  | ||||
| describe('GET /api/v1/admin/roles/:roleId', () => { | ||||
|   let role, currentUser, token, permissionOne, permissionTwo; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     role = await createRole({ key: 'admin' }); | ||||
|     permissionOne = await createPermission({ roleId: role.id }); | ||||
|     permissionTwo = await createPermission({ roleId: role.id }); | ||||
|     currentUser = await createUser({ roleId: role.id }); | ||||
|  | ||||
|     token = createAuthTokenByUserId(currentUser.id); | ||||
|   }); | ||||
|  | ||||
|   it('should return role', async () => { | ||||
|     vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get(`/api/v1/admin/roles/${role.id}`) | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = await getRoleMock(role, [ | ||||
|       permissionOne, | ||||
|       permissionTwo, | ||||
|     ]); | ||||
|  | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
|  | ||||
|   it('should return not found response for not existing role UUID', async () => { | ||||
|     vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||
|  | ||||
|     const notExistingRoleUUID = Crypto.randomUUID(); | ||||
|  | ||||
|     await request(app) | ||||
|       .get(`/api/v1/admin/roles/${notExistingRoleUUID}`) | ||||
|       .set('Authorization', token) | ||||
|       .expect(404); | ||||
|   }); | ||||
|  | ||||
|   it('should return bad request response for invalid UUID', async () => { | ||||
|     vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||
|  | ||||
|     await request(app) | ||||
|       .get('/api/v1/admin/roles/invalidRoleUUID') | ||||
|       .set('Authorization', token) | ||||
|       .expect(400); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,8 @@ | ||||
| import { renderObject } from '../../../../../helpers/renderer.js'; | ||||
| import Role from '../../../../../models/role.js'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   const roles = await Role.query().orderBy('name'); | ||||
|  | ||||
|   renderObject(response, roles); | ||||
| }; | ||||
| @@ -0,0 +1,33 @@ | ||||
| import { vi, describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import app from '../../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js'; | ||||
| import { createRole } from '../../../../../../test/factories/role.js'; | ||||
| import { createUser } from '../../../../../../test/factories/user.js'; | ||||
| import getRolesMock from '../../../../../../test/mocks/rest/api/v1/admin/roles/get-roles.ee.js'; | ||||
| import * as license from '../../../../../helpers/license.ee.js'; | ||||
|  | ||||
| describe('GET /api/v1/admin/roles', () => { | ||||
|   let roleOne, roleTwo, currentUser, token; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     roleOne = await createRole({ key: 'admin' }); | ||||
|     roleTwo = await createRole({ key: 'user' }); | ||||
|     currentUser = await createUser({ roleId: roleOne.id }); | ||||
|  | ||||
|     token = createAuthTokenByUserId(currentUser.id); | ||||
|   }); | ||||
|  | ||||
|   it('should return roles', async () => { | ||||
|     vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get('/api/v1/admin/roles') | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = await getRolesMock([roleOne, roleTwo]); | ||||
|  | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
| }); | ||||
| @@ -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,57 @@ | ||||
| import { vi, describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import Crypto from 'crypto'; | ||||
| 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/admin/saml-auth-providers/get-saml-auth-provider.ee.js'; | ||||
| import * as license from '../../../../../helpers/license.ee.js'; | ||||
|  | ||||
| describe('GET /api/v1/admin/saml-auth-provider/:samlAuthProviderId', () => { | ||||
|   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); | ||||
|   }); | ||||
|  | ||||
|   it('should return not found response for not existing saml auth provider UUID', async () => { | ||||
|     vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||
|  | ||||
|     const notExistingSamlAuthProviderUUID = Crypto.randomUUID(); | ||||
|  | ||||
|     await request(app) | ||||
|       .get( | ||||
|         `/api/v1/admin/saml-auth-providers/${notExistingSamlAuthProviderUUID}` | ||||
|       ) | ||||
|       .set('Authorization', token) | ||||
|       .expect(404); | ||||
|   }); | ||||
|  | ||||
|   it('should return bad request response for invalid UUID', async () => { | ||||
|     vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||
|  | ||||
|     await request(app) | ||||
|       .get('/api/v1/admin/saml-auth-providers/invalidSamlAuthProviderUUID') | ||||
|       .set('Authorization', token) | ||||
|       .expect(400); | ||||
|   }); | ||||
| }); | ||||
| @@ -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/admin/saml-auth-providers/get-saml-auth-providers.ee.js'; | ||||
| import * as license from '../../../../../helpers/license.ee.js'; | ||||
|  | ||||
| describe('GET /api/v1/admin/saml-auth-providers', () => { | ||||
|   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,11 +1,8 @@ | ||||
| import { renderObject } from '../../../../helpers/renderer.js'; | ||||
| import User from '../../../../models/user.js'; | ||||
| import { renderObject } from '../../../../../helpers/renderer.js'; | ||||
| import User from '../../../../../models/user.js'; | ||||
| 
 | ||||
| export default async (request, response) => { | ||||
|   const user = await User.query() | ||||
|     .leftJoinRelated({ | ||||
|       role: true, | ||||
|     }) | ||||
|     .withGraphFetched({ | ||||
|       role: true, | ||||
|     }) | ||||
| @@ -0,0 +1,55 @@ | ||||
| import { vi, describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import Crypto from 'crypto'; | ||||
| import app from '../../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id'; | ||||
| import { createUser } from '../../../../../../test/factories/user'; | ||||
| import { createRole } from '../../../../../../test/factories/role'; | ||||
| import getUserMock from '../../../../../../test/mocks/rest/api/v1/admin/users/get-user.js'; | ||||
| import * as license from '../../../../../helpers/license.ee.js'; | ||||
|  | ||||
| describe('GET /api/v1/admin/users/:userId', () => { | ||||
|   let currentUser, currentUserRole, anotherUser, anotherUserRole, token; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     currentUserRole = await createRole({ key: 'admin' }); | ||||
|     currentUser = await createUser({ roleId: currentUserRole.id }); | ||||
|  | ||||
|     anotherUser = await createUser(); | ||||
|     anotherUserRole = await anotherUser.$relatedQuery('role'); | ||||
|  | ||||
|     token = createAuthTokenByUserId(currentUser.id); | ||||
|   }); | ||||
|  | ||||
|   it('should return specified user info', async () => { | ||||
|     vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get(`/api/v1/admin/users/${anotherUser.id}`) | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = getUserMock(anotherUser, anotherUserRole); | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
|  | ||||
|   it('should return not found response for not existing user UUID', async () => { | ||||
|     vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||
|  | ||||
|     const notExistingUserUUID = Crypto.randomUUID(); | ||||
|  | ||||
|     await request(app) | ||||
|       .get(`/api/v1/admin/users/${notExistingUserUUID}`) | ||||
|       .set('Authorization', token) | ||||
|       .expect(404); | ||||
|   }); | ||||
|  | ||||
|   it('should return bad request response for invalid UUID', async () => { | ||||
|     vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||
|  | ||||
|     await request(app) | ||||
|       .get('/api/v1/admin/users/invalidUserUUID') | ||||
|       .set('Authorization', token) | ||||
|       .expect(400); | ||||
|   }); | ||||
| }); | ||||
| @@ -1,12 +1,9 @@ | ||||
| import { renderObject } from '../../../../helpers/renderer.js'; | ||||
| import User from '../../../../models/user.js'; | ||||
| import paginateRest from '../../../../helpers/pagination-rest.js'; | ||||
| import { renderObject } from '../../../../../helpers/renderer.js'; | ||||
| import User from '../../../../../models/user.js'; | ||||
| import paginateRest from '../../../../../helpers/pagination-rest.js'; | ||||
| 
 | ||||
| export default async (request, response) => { | ||||
|   const usersQuery = User.query() | ||||
|     .leftJoinRelated({ | ||||
|       role: true, | ||||
|     }) | ||||
|     .withGraphFetched({ | ||||
|       role: true, | ||||
|     }) | ||||
| @@ -1,26 +1,17 @@ | ||||
| import { describe, it, expect, beforeEach } from 'vitest'; | ||||
| import { vi, describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import app from '../../../../app'; | ||||
| import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id'; | ||||
| import { createRole } from '../../../../../test/factories/role'; | ||||
| import { createPermission } from '../../../../../test/factories/permission'; | ||||
| import { createUser } from '../../../../../test/factories/user'; | ||||
| import getUsersMock from '../../../../../test/mocks/rest/api/v1/users/get-users'; | ||||
| import app from '../../../../../app'; | ||||
| import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id'; | ||||
| import { createRole } from '../../../../../../test/factories/role'; | ||||
| import { createUser } from '../../../../../../test/factories/user'; | ||||
| import getUsersMock from '../../../../../../test/mocks/rest/api/v1/admin/users/get-users.js'; | ||||
| import * as license from '../../../../../helpers/license.ee.js'; | ||||
| 
 | ||||
| describe('GET /api/v1/users', () => { | ||||
| describe('GET /api/v1/admin/users', () => { | ||||
|   let currentUser, currentUserRole, anotherUser, anotherUserRole, token; | ||||
| 
 | ||||
|   beforeEach(async () => { | ||||
|     currentUserRole = await createRole({ | ||||
|       key: 'currentUser', | ||||
|       name: 'Current user role', | ||||
|     }); | ||||
| 
 | ||||
|     await createPermission({ | ||||
|       action: 'read', | ||||
|       subject: 'User', | ||||
|       roleId: currentUserRole.id, | ||||
|     }); | ||||
|     currentUserRole = await createRole({ key: 'admin' }); | ||||
| 
 | ||||
|     currentUser = await createUser({ | ||||
|       roleId: currentUserRole.id, | ||||
| @@ -41,8 +32,10 @@ describe('GET /api/v1/users', () => { | ||||
|   }); | ||||
| 
 | ||||
|   it('should return users data', async () => { | ||||
|     vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||
| 
 | ||||
|     const response = await request(app) | ||||
|       .get('/api/v1/users') | ||||
|       .get('/api/v1/admin/users') | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
| 
 | ||||
| @@ -0,0 +1,11 @@ | ||||
| import { renderObject } from '../../../../helpers/renderer.js'; | ||||
| import AppAuthClient from '../../../../models/app-auth-client.js'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   const appAuthClient = await AppAuthClient.query() | ||||
|     .findById(request.params.appAuthClientId) | ||||
|     .where({ active: true }) | ||||
|     .throwIfNotFound(); | ||||
|  | ||||
|   renderObject(response, appAuthClient); | ||||
| }; | ||||
| @@ -0,0 +1,48 @@ | ||||
| import { vi, describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import Crypto from 'crypto'; | ||||
| import app from '../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js'; | ||||
| import { createUser } from '../../../../../test/factories/user.js'; | ||||
| import getAppAuthClientMock from '../../../../../test/mocks/rest/api/v1/admin/get-app-auth-client.js'; | ||||
| import { createAppAuthClient } from '../../../../../test/factories/app-auth-client.js'; | ||||
| import * as license from '../../../../helpers/license.ee.js'; | ||||
|  | ||||
| describe('GET /api/v1/app-auth-clients/:id', () => { | ||||
|   let currentUser, currentAppAuthClient, token; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||
|  | ||||
|     currentUser = await createUser(); | ||||
|     currentAppAuthClient = await createAppAuthClient(); | ||||
|  | ||||
|     token = createAuthTokenByUserId(currentUser.id); | ||||
|   }); | ||||
|  | ||||
|   it('should return specified app auth client info', async () => { | ||||
|     const response = await request(app) | ||||
|       .get(`/api/v1/app-auth-clients/${currentAppAuthClient.id}`) | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = getAppAuthClientMock(currentAppAuthClient); | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
|  | ||||
|   it('should return not found response for not existing app auth client ID', async () => { | ||||
|     const notExistingAppAuthClientUUID = Crypto.randomUUID(); | ||||
|  | ||||
|     await request(app) | ||||
|       .get(`/api/v1/app-auth-clients/${notExistingAppAuthClientUUID}`) | ||||
|       .set('Authorization', token) | ||||
|       .expect(404); | ||||
|   }); | ||||
|  | ||||
|   it('should return bad request response for invalid UUID', async () => { | ||||
|     await request(app) | ||||
|       .get('/api/v1/app-auth-clients/invalidAppAuthClientUUID') | ||||
|       .set('Authorization', token) | ||||
|       .expect(400); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,11 @@ | ||||
| import App from '../../../../models/app.js'; | ||||
| import { renderObject } from '../../../../helpers/renderer.js'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   const substeps = await App.findActionSubsteps( | ||||
|     request.params.appKey, | ||||
|     request.params.actionKey | ||||
|   ); | ||||
|  | ||||
|   renderObject(response, substeps); | ||||
| }; | ||||
| @@ -0,0 +1,52 @@ | ||||
| import { describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import App from '../../../../models/app'; | ||||
| import app from '../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id'; | ||||
| import { createUser } from '../../../../../test/factories/user'; | ||||
| import getActionSubstepsMock from '../../../../../test/mocks/rest/api/v1/apps/get-action-substeps.js'; | ||||
|  | ||||
| describe('GET /api/v1/apps/:appKey/actions/:actionKey/substeps', () => { | ||||
|   let currentUser, exampleApp, token; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     currentUser = await createUser(); | ||||
|     token = createAuthTokenByUserId(currentUser.id); | ||||
|     exampleApp = await App.findOneByKey('github'); | ||||
|   }); | ||||
|  | ||||
|   it('should return the app auth info', async () => { | ||||
|     const actions = await App.findActionsByKey('github'); | ||||
|     const exampleAction = actions.find( | ||||
|       (action) => action.key === 'createIssue' | ||||
|     ); | ||||
|  | ||||
|     const endpointUrl = `/api/v1/apps/${exampleApp.key}/actions/${exampleAction.key}/substeps`; | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get(endpointUrl) | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = getActionSubstepsMock(exampleAction.substeps); | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
|  | ||||
|   it('should return not found response for invalid app key', async () => { | ||||
|     await request(app) | ||||
|       .get('/api/v1/apps/invalid-app-key/actions/invalid-actions-key/substeps') | ||||
|       .set('Authorization', token) | ||||
|       .expect(404); | ||||
|   }); | ||||
|  | ||||
|   it('should return empty array for invalid action key', async () => { | ||||
|     const endpointUrl = `/api/v1/apps/${exampleApp.key}/actions/invalid-action-key/substeps`; | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get(endpointUrl) | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     expect(response.body.data).toEqual([]); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,8 @@ | ||||
| import App from '../../../../models/app.js'; | ||||
| import { renderObject } from '../../../../helpers/renderer.js'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   const actions = await App.findActionsByKey(request.params.appKey); | ||||
|  | ||||
|   renderObject(response, actions, { serializer: 'Action' }); | ||||
| }; | ||||
| @@ -0,0 +1,35 @@ | ||||
| import { describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import App from '../../../../models/app'; | ||||
| import app from '../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id'; | ||||
| import { createUser } from '../../../../../test/factories/user'; | ||||
| import getActionsMock from '../../../../../test/mocks/rest/api/v1/apps/get-actions.js'; | ||||
|  | ||||
| describe('GET /api/v1/apps/:appKey/actions', () => { | ||||
|   let currentUser, token; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     currentUser = await createUser(); | ||||
|     token = createAuthTokenByUserId(currentUser.id); | ||||
|   }); | ||||
|  | ||||
|   it('should return the app actions', async () => { | ||||
|     const exampleApp = await App.findOneByKey('github'); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get(`/api/v1/apps/${exampleApp.key}/actions`) | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = getActionsMock(exampleApp.actions); | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
|  | ||||
|   it('should return not found response for invalid app key', async () => { | ||||
|     await request(app) | ||||
|       .get('/api/v1/apps/invalid-app-key/actions') | ||||
|       .set('Authorization', token) | ||||
|       .expect(404); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										8
									
								
								packages/backend/src/controllers/api/v1/apps/get-app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/backend/src/controllers/api/v1/apps/get-app.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| import App from '../../../../models/app.js'; | ||||
| import { renderObject } from '../../../../helpers/renderer.js'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   const app = await App.findOneByKey(request.params.appKey); | ||||
|  | ||||
|   renderObject(response, app, { serializer: 'App' }); | ||||
| }; | ||||
							
								
								
									
										35
									
								
								packages/backend/src/controllers/api/v1/apps/get-app.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								packages/backend/src/controllers/api/v1/apps/get-app.test.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| import { describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import App from '../../../../models/app'; | ||||
| import app from '../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id'; | ||||
| import { createUser } from '../../../../../test/factories/user'; | ||||
| import getAppMock from '../../../../../test/mocks/rest/api/v1/apps/get-app.js'; | ||||
|  | ||||
| describe('GET /api/v1/apps/:appKey', () => { | ||||
|   let currentUser, token; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     currentUser = await createUser(); | ||||
|     token = createAuthTokenByUserId(currentUser.id); | ||||
|   }); | ||||
|  | ||||
|   it('should return the app info', async () => { | ||||
|     const exampleApp = await App.findOneByKey('github'); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get(`/api/v1/apps/${exampleApp.key}`) | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = getAppMock(exampleApp); | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
|  | ||||
|   it('should return not found response for invalid app key', async () => { | ||||
|     await request(app) | ||||
|       .get('/api/v1/apps/invalid-app-key') | ||||
|       .set('Authorization', token) | ||||
|       .expect(404); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										16
									
								
								packages/backend/src/controllers/api/v1/apps/get-apps.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								packages/backend/src/controllers/api/v1/apps/get-apps.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| import App from '../../../../models/app.js'; | ||||
| import { renderObject } from '../../../../helpers/renderer.js'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   let apps = await App.findAll(request.query.name); | ||||
|  | ||||
|   if (request.query.onlyWithTriggers) { | ||||
|     apps = apps.filter((app) => app.triggers?.length); | ||||
|   } | ||||
|  | ||||
|   if (request.query.onlyWithActions) { | ||||
|     apps = apps.filter((app) => app.actions?.length); | ||||
|   } | ||||
|  | ||||
|   renderObject(response, apps, { serializer: 'App' }); | ||||
| }; | ||||
| @@ -0,0 +1,63 @@ | ||||
| import { describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import App from '../../../../models/app'; | ||||
| import app from '../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id'; | ||||
| import { createUser } from '../../../../../test/factories/user'; | ||||
| import getAppsMock from '../../../../../test/mocks/rest/api/v1/apps/get-apps.js'; | ||||
|  | ||||
| describe('GET /api/v1/apps', () => { | ||||
|   let currentUser, apps, token; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     currentUser = await createUser(); | ||||
|     token = createAuthTokenByUserId(currentUser.id); | ||||
|     apps = await App.findAll(); | ||||
|   }); | ||||
|  | ||||
|   it('should return all apps', async () => { | ||||
|     const response = await request(app) | ||||
|       .get('/api/v1/apps') | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = getAppsMock(apps); | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
|  | ||||
|   it('should return all apps filtered by name', async () => { | ||||
|     const appsWithNameGit = apps.filter((app) => app.name.includes('Git')); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get('/api/v1/apps?name=Git') | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = getAppsMock(appsWithNameGit); | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
|  | ||||
|   it('should return only the apps with triggers', async () => { | ||||
|     const appsWithTriggers = apps.filter((app) => app.triggers?.length > 0); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get('/api/v1/apps?onlyWithTriggers=true') | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = getAppsMock(appsWithTriggers); | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
|  | ||||
|   it('should return only the apps with actions', async () => { | ||||
|     const appsWithActions = apps.filter((app) => app.actions?.length > 0); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get('/api/v1/apps?onlyWithActions=true') | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = getAppsMock(appsWithActions); | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										8
									
								
								packages/backend/src/controllers/api/v1/apps/get-auth.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/backend/src/controllers/api/v1/apps/get-auth.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| import App from '../../../../models/app.js'; | ||||
| import { renderObject } from '../../../../helpers/renderer.js'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   const auth = await App.findAuthByKey(request.params.appKey); | ||||
|  | ||||
|   renderObject(response, auth, { serializer: 'Auth' }); | ||||
| }; | ||||
| @@ -0,0 +1,35 @@ | ||||
| import { describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import App from '../../../../models/app'; | ||||
| import app from '../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id'; | ||||
| import { createUser } from '../../../../../test/factories/user'; | ||||
| import getAuthMock from '../../../../../test/mocks/rest/api/v1/apps/get-auth.js'; | ||||
|  | ||||
| describe('GET /api/v1/apps/:appKey/auth', () => { | ||||
|   let currentUser, token; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     currentUser = await createUser(); | ||||
|     token = createAuthTokenByUserId(currentUser.id); | ||||
|   }); | ||||
|  | ||||
|   it('should return the app auth info', async () => { | ||||
|     const exampleApp = await App.findOneByKey('github'); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get(`/api/v1/apps/${exampleApp.key}/auth`) | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = getAuthMock(exampleApp.auth); | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
|  | ||||
|   it('should return not found response for invalid app key', async () => { | ||||
|     await request(app) | ||||
|       .get('/api/v1/apps/invalid-app-key/auth') | ||||
|       .set('Authorization', token) | ||||
|       .expect(404); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,11 @@ | ||||
| import App from '../../../../models/app.js'; | ||||
| import { renderObject } from '../../../../helpers/renderer.js'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   const substeps = await App.findTriggerSubsteps( | ||||
|     request.params.appKey, | ||||
|     request.params.triggerKey | ||||
|   ); | ||||
|  | ||||
|   renderObject(response, substeps); | ||||
| }; | ||||
| @@ -0,0 +1,52 @@ | ||||
| import { describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import App from '../../../../models/app'; | ||||
| import app from '../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id'; | ||||
| import { createUser } from '../../../../../test/factories/user'; | ||||
| import getTriggerSubstepsMock from '../../../../../test/mocks/rest/api/v1/apps/get-trigger-substeps.js'; | ||||
|  | ||||
| describe('GET /api/v1/apps/:appKey/triggers/:triggerKey/substeps', () => { | ||||
|   let currentUser, exampleApp, token; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     currentUser = await createUser(); | ||||
|     token = createAuthTokenByUserId(currentUser.id); | ||||
|     exampleApp = await App.findOneByKey('github'); | ||||
|   }); | ||||
|  | ||||
|   it('should return the app auth info', async () => { | ||||
|     const triggers = await App.findTriggersByKey('github'); | ||||
|     const exampleTrigger = triggers.find( | ||||
|       (trigger) => trigger.key === 'newIssues' | ||||
|     ); | ||||
|  | ||||
|     const endpointUrl = `/api/v1/apps/${exampleApp.key}/triggers/${exampleTrigger.key}/substeps`; | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get(endpointUrl) | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = getTriggerSubstepsMock(exampleTrigger.substeps); | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
|  | ||||
|   it('should return not found response for invalid app key', async () => { | ||||
|     await request(app) | ||||
|       .get('/api/v1/apps/invalid-app-key/triggers/invalid-trigger-key/substeps') | ||||
|       .set('Authorization', token) | ||||
|       .expect(404); | ||||
|   }); | ||||
|  | ||||
|   it('should return empty array for invalid trigger key', async () => { | ||||
|     const endpointUrl = `/api/v1/apps/${exampleApp.key}/triggers/invalid-trigger-key/substeps`; | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get(endpointUrl) | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     expect(response.body.data).toEqual([]); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,8 @@ | ||||
| import App from '../../../../models/app.js'; | ||||
| import { renderObject } from '../../../../helpers/renderer.js'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   const triggers = await App.findTriggersByKey(request.params.appKey); | ||||
|  | ||||
|   renderObject(response, triggers, { serializer: 'Trigger' }); | ||||
| }; | ||||
| @@ -0,0 +1,35 @@ | ||||
| import { describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import App from '../../../../models/app'; | ||||
| import app from '../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id'; | ||||
| import { createUser } from '../../../../../test/factories/user'; | ||||
| import getTriggersMock from '../../../../../test/mocks/rest/api/v1/apps/get-triggers.js'; | ||||
|  | ||||
| describe('GET /api/v1/apps/:appKey/triggers', () => { | ||||
|   let currentUser, token; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     currentUser = await createUser(); | ||||
|     token = createAuthTokenByUserId(currentUser.id); | ||||
|   }); | ||||
|  | ||||
|   it('should return the app triggers', async () => { | ||||
|     const exampleApp = await App.findOneByKey('github'); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get(`/api/v1/apps/${exampleApp.key}/triggers`) | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = getTriggersMock(exampleApp.triggers); | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
|  | ||||
|   it('should return not found response for invalid app key', async () => { | ||||
|     await request(app) | ||||
|       .get('/api/v1/apps/invalid-app-key/triggers') | ||||
|       .set('Authorization', token) | ||||
|       .expect(404); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										13
									
								
								packages/backend/src/controllers/api/v1/automatisch/info.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								packages/backend/src/controllers/api/v1/automatisch/info.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| import appConfig from '../../../../config/app.js'; | ||||
| import { hasValidLicense } from '../../../../helpers/license.ee.js'; | ||||
| import { renderObject } from '../../../../helpers/renderer.js'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   const info = { | ||||
|     isCloud: appConfig.isCloud, | ||||
|     isMation: appConfig.isMation, | ||||
|     isEnterprise: await hasValidLicense(), | ||||
|   }; | ||||
|  | ||||
|   renderObject(response, info); | ||||
| }; | ||||
| @@ -0,0 +1,22 @@ | ||||
| import { vi, expect, describe, it } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import appConfig from '../../../../config/app.js'; | ||||
| import app from '../../../../app.js'; | ||||
| import infoMock from '../../../../../test/mocks/rest/api/v1/automatisch/info.js'; | ||||
| import * as license from '../../../../helpers/license.ee.js'; | ||||
|  | ||||
| describe('GET /api/v1/automatisch/info', () => { | ||||
|   it('should return Automatisch info', async () => { | ||||
|     vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false); | ||||
|     vi.spyOn(appConfig, 'isMation', 'get').mockReturnValue(false); | ||||
|     vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get('/api/v1/automatisch/info') | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = infoMock(); | ||||
|  | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,15 @@ | ||||
| import { getLicense } from '../../../../helpers/license.ee.js'; | ||||
| import { renderObject } from '../../../../helpers/renderer.js'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   const license = await getLicense(); | ||||
|  | ||||
|   const computedLicense = { | ||||
|     id: license ? license.id : null, | ||||
|     name: license ? license.name : null, | ||||
|     expireAt: license ? license.expireAt : null, | ||||
|     verified: license ? true : false, | ||||
|   }; | ||||
|  | ||||
|   renderObject(response, computedLicense); | ||||
| }; | ||||
| @@ -0,0 +1,23 @@ | ||||
| import { vi, expect, describe, it } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import app from '../../../../app.js'; | ||||
| import licenseMock from '../../../../../test/mocks/rest/api/v1/automatisch/license.js'; | ||||
| import * as license from '../../../../helpers/license.ee.js'; | ||||
|  | ||||
| describe('GET /api/v1/automatisch/license', () => { | ||||
|   it('should return Automatisch license info', async () => { | ||||
|     vi.spyOn(license, 'getLicense').mockResolvedValue({ | ||||
|       id: '123', | ||||
|       name: 'license-name', | ||||
|       expireAt: '2025-12-31T23:59:59Z', | ||||
|     }); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get('/api/v1/automatisch/license') | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = licenseMock(); | ||||
|  | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,19 @@ | ||||
| import { renderObject } from '../../../../helpers/renderer.js'; | ||||
| import axios from '../../../../helpers/axios-with-proxy.js'; | ||||
| import logger from '../../../../helpers/logger.js'; | ||||
|  | ||||
| const NOTIFICATIONS_URL = | ||||
|   'https://notifications.automatisch.io/notifications.json'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   let notifications = []; | ||||
|  | ||||
|   try { | ||||
|     const response = await axios.get(NOTIFICATIONS_URL); | ||||
|     notifications = response.data; | ||||
|   } catch (error) { | ||||
|     logger.error('Error fetching notifications API endpoint!', error); | ||||
|   } | ||||
|  | ||||
|   renderObject(response, notifications); | ||||
| }; | ||||
| @@ -0,0 +1,9 @@ | ||||
| import { describe, it } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import app from '../../../../app.js'; | ||||
|  | ||||
| describe('GET /api/v1/automatisch/notifications', () => { | ||||
|   it('should return Automatisch notifications', async () => { | ||||
|     await request(app).get('/api/v1/automatisch/notifications').expect(200); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,23 @@ | ||||
| import { renderObject } from '../../../../helpers/renderer.js'; | ||||
| import paginateRest from '../../../../helpers/pagination-rest.js'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   const execution = await request.currentUser.authorizedExecutions | ||||
|     .clone() | ||||
|     .withSoftDeleted() | ||||
|     .findById(request.params.executionId) | ||||
|     .throwIfNotFound(); | ||||
|  | ||||
|   const executionStepsQuery = execution | ||||
|     .$relatedQuery('executionSteps') | ||||
|     .withSoftDeleted() | ||||
|     .withGraphFetched('step') | ||||
|     .orderBy('created_at', 'asc'); | ||||
|  | ||||
|   const executionSteps = await paginateRest( | ||||
|     executionStepsQuery, | ||||
|     request.query.page | ||||
|   ); | ||||
|  | ||||
|   renderObject(response, executionSteps); | ||||
| }; | ||||
| @@ -0,0 +1,153 @@ | ||||
| import { describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import Crypto from 'crypto'; | ||||
| import app from '../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id'; | ||||
| import { createUser } from '../../../../../test/factories/user'; | ||||
| import { createFlow } from '../../../../../test/factories/flow.js'; | ||||
| import { createStep } from '../../../../../test/factories/step.js'; | ||||
| import { createExecution } from '../../../../../test/factories/execution.js'; | ||||
| import { createExecutionStep } from '../../../../../test/factories/execution-step.js'; | ||||
| import { createPermission } from '../../../../../test/factories/permission'; | ||||
| import getExecutionStepsMock from '../../../../../test/mocks/rest/api/v1/executions/get-execution-steps'; | ||||
|  | ||||
| describe('GET /api/v1/executions/:executionId/execution-steps', () => { | ||||
|   let currentUser, currentUserRole, anotherUser, token; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     currentUser = await createUser(); | ||||
|     currentUserRole = await currentUser.$relatedQuery('role'); | ||||
|  | ||||
|     anotherUser = await createUser(); | ||||
|  | ||||
|     token = createAuthTokenByUserId(currentUser.id); | ||||
|   }); | ||||
|  | ||||
|   it('should return the execution steps of current user execution', async () => { | ||||
|     const currentUserFlow = await createFlow({ | ||||
|       userId: currentUser.id, | ||||
|     }); | ||||
|  | ||||
|     const stepOne = await createStep({ | ||||
|       flowId: currentUserFlow.id, | ||||
|       type: 'trigger', | ||||
|     }); | ||||
|  | ||||
|     const stepTwo = await createStep({ | ||||
|       flowId: currentUserFlow.id, | ||||
|       type: 'action', | ||||
|     }); | ||||
|  | ||||
|     const currentUserExecution = await createExecution({ | ||||
|       flowId: currentUserFlow.id, | ||||
|     }); | ||||
|  | ||||
|     const currentUserExecutionStepOne = await createExecutionStep({ | ||||
|       executionId: currentUserExecution.id, | ||||
|       stepId: stepOne.id, | ||||
|     }); | ||||
|  | ||||
|     const currentUserExecutionStepTwo = await createExecutionStep({ | ||||
|       executionId: currentUserExecution.id, | ||||
|       stepId: stepTwo.id, | ||||
|     }); | ||||
|  | ||||
|     await createPermission({ | ||||
|       action: 'read', | ||||
|       subject: 'Execution', | ||||
|       roleId: currentUserRole.id, | ||||
|       conditions: ['isCreator'], | ||||
|     }); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get(`/api/v1/executions/${currentUserExecution.id}/execution-steps`) | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = await getExecutionStepsMock( | ||||
|       [currentUserExecutionStepOne, currentUserExecutionStepTwo], | ||||
|       [stepOne, stepTwo] | ||||
|     ); | ||||
|  | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
|  | ||||
|   it('should return the execution steps of another user execution', async () => { | ||||
|     const anotherUserFlow = await createFlow({ | ||||
|       userId: anotherUser.id, | ||||
|     }); | ||||
|  | ||||
|     const stepOne = await createStep({ | ||||
|       flowId: anotherUserFlow.id, | ||||
|       type: 'trigger', | ||||
|     }); | ||||
|  | ||||
|     const stepTwo = await createStep({ | ||||
|       flowId: anotherUserFlow.id, | ||||
|       type: 'action', | ||||
|     }); | ||||
|  | ||||
|     const anotherUserExecution = await createExecution({ | ||||
|       flowId: anotherUserFlow.id, | ||||
|     }); | ||||
|  | ||||
|     const anotherUserExecutionStepOne = await createExecutionStep({ | ||||
|       executionId: anotherUserExecution.id, | ||||
|       stepId: stepOne.id, | ||||
|     }); | ||||
|  | ||||
|     const anotherUserExecutionStepTwo = await createExecutionStep({ | ||||
|       executionId: anotherUserExecution.id, | ||||
|       stepId: stepTwo.id, | ||||
|     }); | ||||
|  | ||||
|     await createPermission({ | ||||
|       action: 'read', | ||||
|       subject: 'Execution', | ||||
|       roleId: currentUserRole.id, | ||||
|       conditions: [], | ||||
|     }); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get(`/api/v1/executions/${anotherUserExecution.id}/execution-steps`) | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = await getExecutionStepsMock( | ||||
|       [anotherUserExecutionStepOne, anotherUserExecutionStepTwo], | ||||
|       [stepOne, stepTwo] | ||||
|     ); | ||||
|  | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
|  | ||||
|   it('should return not found response for not existing execution step UUID', async () => { | ||||
|     await createPermission({ | ||||
|       action: 'read', | ||||
|       subject: 'Execution', | ||||
|       roleId: currentUserRole.id, | ||||
|       conditions: [], | ||||
|     }); | ||||
|  | ||||
|     const notExistingExcecutionUUID = Crypto.randomUUID(); | ||||
|  | ||||
|     await request(app) | ||||
|       .get(`/api/v1/executions/${notExistingExcecutionUUID}/execution-steps`) | ||||
|       .set('Authorization', token) | ||||
|       .expect(404); | ||||
|   }); | ||||
|  | ||||
|   it('should return bad request response for invalid UUID', async () => { | ||||
|     await createPermission({ | ||||
|       action: 'read', | ||||
|       subject: 'Execution', | ||||
|       roleId: currentUserRole.id, | ||||
|       conditions: [], | ||||
|     }); | ||||
|  | ||||
|     await request(app) | ||||
|       .get('/api/v1/executions/invalidExecutionUUID/execution-steps') | ||||
|       .set('Authorization', token) | ||||
|       .expect(400); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,15 @@ | ||||
| import { renderObject } from '../../../../helpers/renderer.js'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   const execution = await request.currentUser.authorizedExecutions | ||||
|     .withGraphFetched({ | ||||
|       flow: { | ||||
|         steps: true, | ||||
|       }, | ||||
|     }) | ||||
|     .withSoftDeleted() | ||||
|     .findById(request.params.executionId) | ||||
|     .throwIfNotFound(); | ||||
|  | ||||
|   renderObject(response, execution); | ||||
| }; | ||||
| @@ -0,0 +1,134 @@ | ||||
| import { describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import Crypto from 'crypto'; | ||||
| import app from '../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id'; | ||||
| import { createUser } from '../../../../../test/factories/user'; | ||||
| import { createFlow } from '../../../../../test/factories/flow.js'; | ||||
| import { createStep } from '../../../../../test/factories/step.js'; | ||||
| import { createExecution } from '../../../../../test/factories/execution.js'; | ||||
| import { createPermission } from '../../../../../test/factories/permission'; | ||||
| import getExecutionMock from '../../../../../test/mocks/rest/api/v1/executions/get-execution'; | ||||
|  | ||||
| describe('GET /api/v1/executions/:executionId', () => { | ||||
|   let currentUser, currentUserRole, token; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     currentUser = await createUser(); | ||||
|     currentUserRole = await currentUser.$relatedQuery('role'); | ||||
|  | ||||
|     token = createAuthTokenByUserId(currentUser.id); | ||||
|   }); | ||||
|  | ||||
|   it('should return the execution data of current user', async () => { | ||||
|     const currentUserFlow = await createFlow({ | ||||
|       userId: currentUser.id, | ||||
|     }); | ||||
|  | ||||
|     const stepOne = await createStep({ | ||||
|       flowId: currentUserFlow.id, | ||||
|       type: 'trigger', | ||||
|     }); | ||||
|  | ||||
|     const stepTwo = await createStep({ | ||||
|       flowId: currentUserFlow.id, | ||||
|       type: 'action', | ||||
|     }); | ||||
|  | ||||
|     const currentUserExecution = await createExecution({ | ||||
|       flowId: currentUserFlow.id, | ||||
|     }); | ||||
|  | ||||
|     await createPermission({ | ||||
|       action: 'read', | ||||
|       subject: 'Execution', | ||||
|       roleId: currentUserRole.id, | ||||
|       conditions: ['isCreator'], | ||||
|     }); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get(`/api/v1/executions/${currentUserExecution.id}`) | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = await getExecutionMock( | ||||
|       currentUserExecution, | ||||
|       currentUserFlow, | ||||
|       [stepOne, stepTwo] | ||||
|     ); | ||||
|  | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
|  | ||||
|   it('should return the execution data of another user', async () => { | ||||
|     const anotherUser = await createUser(); | ||||
|  | ||||
|     const anotherUserFlow = await createFlow({ | ||||
|       userId: anotherUser.id, | ||||
|     }); | ||||
|  | ||||
|     const stepOne = await createStep({ | ||||
|       flowId: anotherUserFlow.id, | ||||
|       type: 'trigger', | ||||
|     }); | ||||
|  | ||||
|     const stepTwo = await createStep({ | ||||
|       flowId: anotherUserFlow.id, | ||||
|       type: 'action', | ||||
|     }); | ||||
|  | ||||
|     const anotherUserExecution = await createExecution({ | ||||
|       flowId: anotherUserFlow.id, | ||||
|     }); | ||||
|  | ||||
|     await createPermission({ | ||||
|       action: 'read', | ||||
|       subject: 'Execution', | ||||
|       roleId: currentUserRole.id, | ||||
|       conditions: [], | ||||
|     }); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get(`/api/v1/executions/${anotherUserExecution.id}`) | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = await getExecutionMock( | ||||
|       anotherUserExecution, | ||||
|       anotherUserFlow, | ||||
|       [stepOne, stepTwo] | ||||
|     ); | ||||
|  | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
|  | ||||
|   it('should return not found response for not existing execution UUID', async () => { | ||||
|     await createPermission({ | ||||
|       action: 'read', | ||||
|       subject: 'Execution', | ||||
|       roleId: currentUserRole.id, | ||||
|       conditions: [], | ||||
|     }); | ||||
|  | ||||
|     const notExistingExcecutionUUID = Crypto.randomUUID(); | ||||
|  | ||||
|     await request(app) | ||||
|       .get(`/api/v1/executions/${notExistingExcecutionUUID}`) | ||||
|       .set('Authorization', token) | ||||
|       .expect(404); | ||||
|   }); | ||||
|  | ||||
|   it('should return bad request response for invalid UUID', async () => { | ||||
|     await createPermission({ | ||||
|       action: 'read', | ||||
|       subject: 'Execution', | ||||
|       roleId: currentUserRole.id, | ||||
|       conditions: [], | ||||
|     }); | ||||
|  | ||||
|     await request(app) | ||||
|       .get('/api/v1/executions/invalidExecutionUUID') | ||||
|       .set('Authorization', token) | ||||
|       .expect(400); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,26 @@ | ||||
| import { renderObject } from '../../../../helpers/renderer.js'; | ||||
| import paginateRest from '../../../../helpers/pagination-rest.js'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   const executionsQuery = request.currentUser.authorizedExecutions | ||||
|     .withSoftDeleted() | ||||
|     .orderBy('created_at', 'desc') | ||||
|     .withGraphFetched({ | ||||
|       flow: { | ||||
|         steps: true, | ||||
|       }, | ||||
|     }); | ||||
|  | ||||
|   const executions = await paginateRest(executionsQuery, request.query.page); | ||||
|  | ||||
|   for (const execution of executions.records) { | ||||
|     const executionSteps = await execution.$relatedQuery('executionSteps'); | ||||
|     const status = executionSteps.some((step) => step.status === 'failure') | ||||
|       ? 'failure' | ||||
|       : 'success'; | ||||
|  | ||||
|     execution.status = status; | ||||
|   } | ||||
|  | ||||
|   renderObject(response, executions); | ||||
| }; | ||||
| @@ -0,0 +1,113 @@ | ||||
| import { describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import app from '../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id'; | ||||
| import { createUser } from '../../../../../test/factories/user'; | ||||
| import { createFlow } from '../../../../../test/factories/flow.js'; | ||||
| import { createStep } from '../../../../../test/factories/step.js'; | ||||
| import { createExecution } from '../../../../../test/factories/execution.js'; | ||||
| import { createPermission } from '../../../../../test/factories/permission'; | ||||
| import getExecutionsMock from '../../../../../test/mocks/rest/api/v1/executions/get-executions'; | ||||
|  | ||||
| describe('GET /api/v1/executions', () => { | ||||
|   let currentUser, currentUserRole, anotherUser, token; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     currentUser = await createUser(); | ||||
|     currentUserRole = await currentUser.$relatedQuery('role'); | ||||
|  | ||||
|     anotherUser = await createUser(); | ||||
|  | ||||
|     token = createAuthTokenByUserId(currentUser.id); | ||||
|   }); | ||||
|  | ||||
|   it('should return the executions of current user', async () => { | ||||
|     const currentUserFlow = await createFlow({ | ||||
|       userId: currentUser.id, | ||||
|     }); | ||||
|  | ||||
|     const stepOne = await createStep({ | ||||
|       flowId: currentUserFlow.id, | ||||
|       type: 'trigger', | ||||
|     }); | ||||
|  | ||||
|     const stepTwo = await createStep({ | ||||
|       flowId: currentUserFlow.id, | ||||
|       type: 'action', | ||||
|     }); | ||||
|  | ||||
|     const currentUserExecutionOne = await createExecution({ | ||||
|       flowId: currentUserFlow.id, | ||||
|     }); | ||||
|  | ||||
|     const currentUserExecutionTwo = await createExecution({ | ||||
|       flowId: currentUserFlow.id, | ||||
|       deletedAt: new Date().toISOString(), | ||||
|     }); | ||||
|  | ||||
|     await createPermission({ | ||||
|       action: 'read', | ||||
|       subject: 'Execution', | ||||
|       roleId: currentUserRole.id, | ||||
|       conditions: ['isCreator'], | ||||
|     }); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get('/api/v1/executions') | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = await getExecutionsMock( | ||||
|       [currentUserExecutionTwo, currentUserExecutionOne], | ||||
|       currentUserFlow, | ||||
|       [stepOne, stepTwo] | ||||
|     ); | ||||
|  | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
|  | ||||
|   it('should return the executions of another user', async () => { | ||||
|     const anotherUserFlow = await createFlow({ | ||||
|       userId: anotherUser.id, | ||||
|     }); | ||||
|  | ||||
|     const stepOne = await createStep({ | ||||
|       flowId: anotherUserFlow.id, | ||||
|       type: 'trigger', | ||||
|     }); | ||||
|  | ||||
|     const stepTwo = await createStep({ | ||||
|       flowId: anotherUserFlow.id, | ||||
|       type: 'action', | ||||
|     }); | ||||
|  | ||||
|     const anotherUserExecutionOne = await createExecution({ | ||||
|       flowId: anotherUserFlow.id, | ||||
|     }); | ||||
|  | ||||
|     const anotherUserExecutionTwo = await createExecution({ | ||||
|       flowId: anotherUserFlow.id, | ||||
|       deletedAt: new Date().toISOString(), | ||||
|     }); | ||||
|  | ||||
|     await createPermission({ | ||||
|       action: 'read', | ||||
|       subject: 'Execution', | ||||
|       roleId: currentUserRole.id, | ||||
|       conditions: [], | ||||
|     }); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get('/api/v1/executions') | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = await getExecutionsMock( | ||||
|       [anotherUserExecutionTwo, anotherUserExecutionOne], | ||||
|       anotherUserFlow, | ||||
|       [stepOne, stepTwo] | ||||
|     ); | ||||
|  | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										11
									
								
								packages/backend/src/controllers/api/v1/flows/get-flow.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/backend/src/controllers/api/v1/flows/get-flow.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| import { renderObject } from '../../../../helpers/renderer.js'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   const flow = await request.currentUser.authorizedFlows | ||||
|     .withGraphJoined({ steps: true }) | ||||
|     .orderBy('steps.position', 'asc') | ||||
|     .findOne({ 'flows.id': request.params.flowId }) | ||||
|     .throwIfNotFound(); | ||||
|  | ||||
|   renderObject(response, flow); | ||||
| }; | ||||
							
								
								
									
										102
									
								
								packages/backend/src/controllers/api/v1/flows/get-flow.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								packages/backend/src/controllers/api/v1/flows/get-flow.test.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | ||||
| import { describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import Crypto from 'crypto'; | ||||
| import app from '../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id'; | ||||
| import { createUser } from '../../../../../test/factories/user'; | ||||
| import { createFlow } from '../../../../../test/factories/flow'; | ||||
| import { createStep } from '../../../../../test/factories/step'; | ||||
| import { createPermission } from '../../../../../test/factories/permission'; | ||||
| import getFlowMock from '../../../../../test/mocks/rest/api/v1/flows/get-flow'; | ||||
|  | ||||
| describe('GET /api/v1/flows/:flowId', () => { | ||||
|   let currentUser, currentUserRole, token; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     currentUser = await createUser(); | ||||
|     currentUserRole = await currentUser.$relatedQuery('role'); | ||||
|  | ||||
|     token = createAuthTokenByUserId(currentUser.id); | ||||
|   }); | ||||
|  | ||||
|   it('should return the flow data of current user', async () => { | ||||
|     const currentUserflow = await createFlow({ userId: currentUser.id }); | ||||
|     const triggerStep = await createStep({ flowId: currentUserflow.id }); | ||||
|     const actionStep = await createStep({ flowId: currentUserflow.id }); | ||||
|  | ||||
|     await createPermission({ | ||||
|       action: 'read', | ||||
|       subject: 'Flow', | ||||
|       roleId: currentUserRole.id, | ||||
|       conditions: ['isCreator'], | ||||
|     }); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get(`/api/v1/flows/${currentUserflow.id}`) | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = await getFlowMock(currentUserflow, [ | ||||
|       triggerStep, | ||||
|       actionStep, | ||||
|     ]); | ||||
|  | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
|  | ||||
|   it('should return the flow data of another user', async () => { | ||||
|     const anotherUser = await createUser(); | ||||
|     const anotherUserFlow = await createFlow({ userId: anotherUser.id }); | ||||
|     const triggerStep = await createStep({ flowId: anotherUserFlow.id }); | ||||
|     const actionStep = await createStep({ flowId: anotherUserFlow.id }); | ||||
|  | ||||
|     await createPermission({ | ||||
|       action: 'read', | ||||
|       subject: 'Flow', | ||||
|       roleId: currentUserRole.id, | ||||
|       conditions: [], | ||||
|     }); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get(`/api/v1/flows/${anotherUserFlow.id}`) | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = await getFlowMock(anotherUserFlow, [ | ||||
|       triggerStep, | ||||
|       actionStep, | ||||
|     ]); | ||||
|  | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
|  | ||||
|   it('should return not found response for not existing flow UUID', async () => { | ||||
|     await createPermission({ | ||||
|       action: 'read', | ||||
|       subject: 'Flow', | ||||
|       roleId: currentUserRole.id, | ||||
|       conditions: [], | ||||
|     }); | ||||
|  | ||||
|     const notExistingFlowUUID = Crypto.randomUUID(); | ||||
|  | ||||
|     await request(app) | ||||
|       .get(`/api/v1/flows/${notExistingFlowUUID}`) | ||||
|       .set('Authorization', token) | ||||
|       .expect(404); | ||||
|   }); | ||||
|  | ||||
|   it('should return bad request response for invalid UUID', async () => { | ||||
|     await createPermission({ | ||||
|       action: 'read', | ||||
|       subject: 'Flow', | ||||
|       roleId: currentUserRole.id, | ||||
|       conditions: [], | ||||
|     }); | ||||
|  | ||||
|     await request(app) | ||||
|       .get('/api/v1/flows/invalidFlowUUID') | ||||
|       .set('Authorization', token) | ||||
|       .expect(400); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,8 @@ | ||||
| import { renderObject } from '../../../../helpers/renderer.js'; | ||||
| import Billing from '../../../../helpers/billing/index.ee.js'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   const paddleInfo = Billing.paddleInfo; | ||||
|  | ||||
|   renderObject(response, paddleInfo); | ||||
| }; | ||||
| @@ -0,0 +1,33 @@ | ||||
| import { vi, describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import app from '../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js'; | ||||
| import { createUser } from '../../../../../test/factories/user.js'; | ||||
| import getPaddleInfoMock from '../../../../../test/mocks/rest/api/v1/payment/get-paddle-info.js'; | ||||
| import appConfig from '../../../../config/app.js'; | ||||
| import billing from '../../../../helpers/billing/index.ee.js'; | ||||
|  | ||||
| describe('GET /api/v1/payment/paddle-info', () => { | ||||
|   let user, token; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     user = await createUser(); | ||||
|     token = createAuthTokenByUserId(user.id); | ||||
|  | ||||
|     vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true); | ||||
|     vi.spyOn(billing.paddleInfo, 'vendorId', 'get').mockReturnValue( | ||||
|       'sampleVendorId' | ||||
|     ); | ||||
|   }); | ||||
|  | ||||
|   it('should return payment plans', async () => { | ||||
|     const response = await request(app) | ||||
|       .get('/api/v1/payment/paddle-info') | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedResponsePayload = await getPaddleInfoMock(); | ||||
|  | ||||
|     expect(response.body).toEqual(expectedResponsePayload); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,8 @@ | ||||
| import { renderObject } from '../../../../helpers/renderer.js'; | ||||
| import Billing from '../../../../helpers/billing/index.ee.js'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   const paymentPlans = Billing.paddlePlans; | ||||
|  | ||||
|   renderObject(response, paymentPlans); | ||||
| }; | ||||
| @@ -0,0 +1,29 @@ | ||||
| import { vi, describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import app from '../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js'; | ||||
| import { createUser } from '../../../../../test/factories/user.js'; | ||||
| import getPaymentPlansMock from '../../../../../test/mocks/rest/api/v1/payment/get-plans.js'; | ||||
| import appConfig from '../../../../config/app.js'; | ||||
|  | ||||
| describe('GET /api/v1/payment/plans', () => { | ||||
|   let user, token; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     user = await createUser(); | ||||
|     token = createAuthTokenByUserId(user.id); | ||||
|  | ||||
|     vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true); | ||||
|   }); | ||||
|  | ||||
|   it('should return payment plans', async () => { | ||||
|     const response = await request(app) | ||||
|       .get('/api/v1/payment/plans') | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedResponsePayload = await getPaymentPlansMock(); | ||||
|  | ||||
|     expect(response.body).toEqual(expectedResponsePayload); | ||||
|   }); | ||||
| }); | ||||
| @@ -2,15 +2,29 @@ import { describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import app from '../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id'; | ||||
| import { createPermission } from '../../../../../test/factories/permission'; | ||||
| import { createRole } from '../../../../../test/factories/role'; | ||||
| import { createUser } from '../../../../../test/factories/user'; | ||||
| import getCurrentUserMock from '../../../../../test/mocks/rest/api/v1/users/get-current-user'; | ||||
|  | ||||
| describe('GET /api/v1/users/me', () => { | ||||
|   let role, currentUser, token; | ||||
|   let role, permissionOne, permissionTwo, currentUser, token; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     currentUser = await createUser(); | ||||
|     role = await currentUser.$relatedQuery('role'); | ||||
|     role = await createRole(); | ||||
|  | ||||
|     permissionOne = await createPermission({ | ||||
|       roleId: role.id, | ||||
|     }); | ||||
|  | ||||
|     permissionTwo = await createPermission({ | ||||
|       roleId: role.id, | ||||
|     }); | ||||
|  | ||||
|     currentUser = await createUser({ | ||||
|       roleId: role.id, | ||||
|     }); | ||||
|  | ||||
|     token = createAuthTokenByUserId(currentUser.id); | ||||
|   }); | ||||
|  | ||||
| @@ -20,7 +34,11 @@ describe('GET /api/v1/users/me', () => { | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = getCurrentUserMock(currentUser, role); | ||||
|     const expectedPayload = getCurrentUserMock(currentUser, role, [ | ||||
|       permissionOne, | ||||
|       permissionTwo, | ||||
|     ]); | ||||
|  | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
| }); | ||||
|   | ||||
| @@ -0,0 +1,7 @@ | ||||
| import { renderObject } from '../../../../helpers/renderer.js'; | ||||
|  | ||||
| export default async (request, response) => { | ||||
|   const invoices = await request.currentUser.getInvoices(); | ||||
|  | ||||
|   renderObject(response, invoices); | ||||
| }; | ||||
| @@ -0,0 +1,34 @@ | ||||
| import { describe, it, expect, beforeEach, vi } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import app from '../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id'; | ||||
| import { createUser } from '../../../../../test/factories/user'; | ||||
| import User from '../../../../models/user'; | ||||
| import getInvoicesMock from '../../../../../test/mocks/rest/api/v1/users/get-invoices.ee'; | ||||
|  | ||||
| describe('GET /api/v1/user/invoices', () => { | ||||
|   let currentUser, token; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     currentUser = await createUser(); | ||||
|     token = createAuthTokenByUserId(currentUser.id); | ||||
|   }); | ||||
|  | ||||
|   it('should return current user invoices', async () => { | ||||
|     const invoices = [ | ||||
|       { id: 1, amount: 100, description: 'Invoice 1' }, | ||||
|       { id: 2, amount: 200, description: 'Invoice 2' }, | ||||
|     ]; | ||||
|  | ||||
|     vi.spyOn(User.prototype, 'getInvoices').mockResolvedValue(invoices); | ||||
|  | ||||
|     const response = await request(app) | ||||
|       .get('/api/v1/users/invoices') | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = await getInvoicesMock(invoices); | ||||
|  | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
| }); | ||||
| @@ -1,36 +0,0 @@ | ||||
| import { describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import app from '../../../../app.js'; | ||||
| import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id'; | ||||
| import { createUser } from '../../../../../test/factories/user'; | ||||
| import { createPermission } from '../../../../../test/factories/permission'; | ||||
| import getUserMock from '../../../../../test/mocks/rest/api/v1/users/get-user'; | ||||
|  | ||||
| describe('GET /api/v1/users/:userId', () => { | ||||
|   let currentUser, currentUserRole, anotherUser, anotherUserRole, token; | ||||
|  | ||||
|   beforeEach(async () => { | ||||
|     currentUser = await createUser(); | ||||
|     anotherUser = await createUser(); | ||||
|     currentUserRole = await currentUser.$relatedQuery('role'); | ||||
|     anotherUserRole = await anotherUser.$relatedQuery('role'); | ||||
|  | ||||
|     await createPermission({ | ||||
|       roleId: currentUserRole.id, | ||||
|       action: 'read', | ||||
|       subject: 'User', | ||||
|     }); | ||||
|  | ||||
|     token = createAuthTokenByUserId(currentUser.id); | ||||
|   }); | ||||
|  | ||||
|   it('should return specified user info', async () => { | ||||
|     const response = await request(app) | ||||
|       .get(`/api/v1/users/${anotherUser.id}`) | ||||
|       .set('Authorization', token) | ||||
|       .expect(200); | ||||
|  | ||||
|     const expectedPayload = getUserMock(anotherUser, anotherUserRole); | ||||
|     expect(response.body).toEqual(expectedPayload); | ||||
|   }); | ||||
| }); | ||||
| @@ -0,0 +1,16 @@ | ||||
| export async function up(knex) { | ||||
|   return knex.schema.createTable('datastore', (table) => { | ||||
|     table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()')); | ||||
|     table.string('key').notNullable(); | ||||
|     table.string('value'); | ||||
|     table.string('scope').notNullable(); | ||||
|     table.uuid('scope_id').notNullable(); | ||||
|     table.index(['key', 'scope', 'scope_id']); | ||||
|  | ||||
|     table.timestamps(true, true); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| export async function down(knex) { | ||||
|   return knex.schema.dropTable('datastore'); | ||||
| } | ||||
| @@ -1,8 +1,13 @@ | ||||
| const deleteStep = async (_parent, params, context) => { | ||||
|   context.currentUser.can('update', 'Flow'); | ||||
| import Step from '../../models/flow.js'; | ||||
|  | ||||
|   const step = await context.currentUser | ||||
|     .$relatedQuery('steps') | ||||
| const deleteStep = async (_parent, params, context) => { | ||||
|   const conditions = context.currentUser.can('update', 'Flow'); | ||||
|   const isCreator = conditions.isCreator; | ||||
|   const allSteps = Step.query(); | ||||
|   const userSteps = context.currentUser.$relatedQuery('steps'); | ||||
|   const baseQuery = isCreator ? userSteps : allSteps; | ||||
|  | ||||
|   const step = await baseQuery | ||||
|     .withGraphFetched('flow') | ||||
|     .findOne({ | ||||
|       'steps.id': params.input.id, | ||||
|   | ||||
| @@ -1,21 +0,0 @@ | ||||
| import appConfig from '../../config/app.js'; | ||||
| import { getLicense } from '../../helpers/license.ee.js'; | ||||
|  | ||||
| const getAutomatischInfo = async () => { | ||||
|   const license = await getLicense(); | ||||
|  | ||||
|   const computedLicense = { | ||||
|     id: license ? license.id : null, | ||||
|     name: license ? license.name : null, | ||||
|     expireAt: license ? license.expireAt : null, | ||||
|     verified: license ? true : false, | ||||
|   }; | ||||
|  | ||||
|   return { | ||||
|     isCloud: appConfig.isCloud, | ||||
|     isMation: appConfig.isMation, | ||||
|     license: computedLicense, | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export default getAutomatischInfo; | ||||
| @@ -1,190 +0,0 @@ | ||||
| import { vi, describe, it, expect, beforeEach } from 'vitest'; | ||||
| import request from 'supertest'; | ||||
| import app from '../../app'; | ||||
| import * as license from '../../helpers/license.ee'; | ||||
| import appConfig from '../../config/app'; | ||||
|  | ||||
| describe('graphQL getAutomatischInfo query', () => { | ||||
|   const query = ` | ||||
|     query { | ||||
|       getAutomatischInfo { | ||||
|         isCloud | ||||
|         isMation | ||||
|         license { | ||||
|           id | ||||
|           name | ||||
|           expireAt | ||||
|           verified | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   `; | ||||
|  | ||||
|   describe('and without valid license', () => { | ||||
|     beforeEach(async () => { | ||||
|       vi.spyOn(license, 'getLicense').mockResolvedValue(false); | ||||
|  | ||||
|       vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false); | ||||
|       vi.spyOn(appConfig, 'isMation', 'get').mockReturnValue(false); | ||||
|     }); | ||||
|  | ||||
|     it('should return empty license data', async () => { | ||||
|       const response = await request(app) | ||||
|         .post('/graphql') | ||||
|         .send({ query }) | ||||
|         .expect(200); | ||||
|  | ||||
|       const expectedResponsePayload = { | ||||
|         data: { | ||||
|           getAutomatischInfo: { | ||||
|             isCloud: false, | ||||
|             isMation: false, | ||||
|             license: { | ||||
|               id: null, | ||||
|               name: null, | ||||
|               expireAt: null, | ||||
|               verified: false, | ||||
|             }, | ||||
|           }, | ||||
|         }, | ||||
|       }; | ||||
|  | ||||
|       expect(response.body).toEqual(expectedResponsePayload); | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   describe('and with valid license', () => { | ||||
|     beforeEach(async () => { | ||||
|       const mockedLicense = { | ||||
|         id: '123123', | ||||
|         name: 'Test License', | ||||
|         expireAt: '2025-08-09T10:56:54.144Z', | ||||
|         verified: true, | ||||
|       }; | ||||
|  | ||||
|       vi.spyOn(license, 'getLicense').mockResolvedValue(mockedLicense); | ||||
|     }); | ||||
|  | ||||
|     describe('and with cloud flag enabled', () => { | ||||
|       beforeEach(async () => { | ||||
|         vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true); | ||||
|       }); | ||||
|  | ||||
|       it('should return all license data', async () => { | ||||
|         const response = await request(app) | ||||
|           .post('/graphql') | ||||
|           .send({ query }) | ||||
|           .expect(200); | ||||
|  | ||||
|         const expectedResponsePayload = { | ||||
|           data: { | ||||
|             getAutomatischInfo: { | ||||
|               isCloud: true, | ||||
|               isMation: false, | ||||
|               license: { | ||||
|                 expireAt: '2025-08-09T10:56:54.144Z', | ||||
|                 id: '123123', | ||||
|                 name: 'Test License', | ||||
|                 verified: true, | ||||
|               }, | ||||
|             }, | ||||
|           }, | ||||
|         }; | ||||
|  | ||||
|         expect(response.body).toEqual(expectedResponsePayload); | ||||
|       }); | ||||
|     }); | ||||
|  | ||||
|     describe('and with cloud flag disabled', () => { | ||||
|       beforeEach(async () => { | ||||
|         vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false); | ||||
|       }); | ||||
|  | ||||
|       it('should return all license data', async () => { | ||||
|         const response = await request(app) | ||||
|           .post('/graphql') | ||||
|           .send({ query }) | ||||
|           .expect(200); | ||||
|  | ||||
|         const expectedResponsePayload = { | ||||
|           data: { | ||||
|             getAutomatischInfo: { | ||||
|               isCloud: false, | ||||
|               isMation: false, | ||||
|               license: { | ||||
|                 expireAt: '2025-08-09T10:56:54.144Z', | ||||
|                 id: '123123', | ||||
|                 name: 'Test License', | ||||
|                 verified: true, | ||||
|               }, | ||||
|             }, | ||||
|           }, | ||||
|         }; | ||||
|  | ||||
|         expect(response.body).toEqual(expectedResponsePayload); | ||||
|       }); | ||||
|     }); | ||||
|  | ||||
|     describe('and with mation flag enabled', () => { | ||||
|       beforeEach(async () => { | ||||
|         vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false); | ||||
|         vi.spyOn(appConfig, 'isMation', 'get').mockReturnValue(true); | ||||
|       }); | ||||
|  | ||||
|       it('should return all license data', async () => { | ||||
|         const response = await request(app) | ||||
|           .post('/graphql') | ||||
|           .send({ query }) | ||||
|           .expect(200); | ||||
|  | ||||
|         const expectedResponsePayload = { | ||||
|           data: { | ||||
|             getAutomatischInfo: { | ||||
|               isCloud: false, | ||||
|               isMation: true, | ||||
|               license: { | ||||
|                 expireAt: '2025-08-09T10:56:54.144Z', | ||||
|                 id: '123123', | ||||
|                 name: 'Test License', | ||||
|                 verified: true, | ||||
|               }, | ||||
|             }, | ||||
|           }, | ||||
|         }; | ||||
|  | ||||
|         expect(response.body).toEqual(expectedResponsePayload); | ||||
|       }); | ||||
|     }); | ||||
|  | ||||
|     describe('and with mation flag disabled', () => { | ||||
|       beforeEach(async () => { | ||||
|         vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false); | ||||
|         vi.spyOn(appConfig, 'isMation', 'get').mockReturnValue(false); | ||||
|       }); | ||||
|  | ||||
|       it('should return all license data', async () => { | ||||
|         const response = await request(app) | ||||
|           .post('/graphql') | ||||
|           .send({ query }) | ||||
|           .expect(200); | ||||
|  | ||||
|         const expectedResponsePayload = { | ||||
|           data: { | ||||
|             getAutomatischInfo: { | ||||
|               isMation: false, | ||||
|               isCloud: false, | ||||
|               license: { | ||||
|                 expireAt: '2025-08-09T10:56:54.144Z', | ||||
|                 id: '123123', | ||||
|                 name: 'Test License', | ||||
|                 verified: true, | ||||
|               }, | ||||
|             }, | ||||
|           }, | ||||
|         }; | ||||
|  | ||||
|         expect(response.body).toEqual(expectedResponsePayload); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
| @@ -3,7 +3,6 @@ import getAppAuthClient from './queries/get-app-auth-client.ee.js'; | ||||
| import getAppAuthClients from './queries/get-app-auth-clients.ee.js'; | ||||
| import getAppConfig from './queries/get-app-config.ee.js'; | ||||
| import getApps from './queries/get-apps.js'; | ||||
| import getAutomatischInfo from './queries/get-automatisch-info.js'; | ||||
| import getBillingAndUsage from './queries/get-billing-and-usage.ee.js'; | ||||
| import getConfig from './queries/get-config.ee.js'; | ||||
| import getConnectedApps from './queries/get-connected-apps.js'; | ||||
| @@ -39,7 +38,6 @@ const queryResolvers = { | ||||
|   getAppAuthClients, | ||||
|   getAppConfig, | ||||
|   getApps, | ||||
|   getAutomatischInfo, | ||||
|   getBillingAndUsage, | ||||
|   getConfig, | ||||
|   getConnectedApps, | ||||
|   | ||||
| @@ -40,7 +40,6 @@ type Query { | ||||
|     key: String! | ||||
|     parameters: JSONObject | ||||
|   ): [SubstepArgument] | ||||
|   getAutomatischInfo: GetAutomatischInfo | ||||
|   getBillingAndUsage: GetBillingAndUsage | ||||
|   getCurrentUser: User | ||||
|   getConfig(keys: [String]): JSONObject | ||||
| @@ -644,12 +643,6 @@ type AppHealth { | ||||
|   version: String | ||||
| } | ||||
|  | ||||
| type GetAutomatischInfo { | ||||
|   isCloud: Boolean | ||||
|   isMation: Boolean | ||||
|   license: License | ||||
| } | ||||
|  | ||||
| type License { | ||||
|   id: String | ||||
|   name: String | ||||
|   | ||||
| @@ -42,7 +42,6 @@ const isAuthenticatedRule = rule()(isAuthenticated); | ||||
| export const authenticationRules = { | ||||
|   Query: { | ||||
|     '*': isAuthenticatedRule, | ||||
|     getAutomatischInfo: allow, | ||||
|     getConfig: allow, | ||||
|     getNotifications: allow, | ||||
|     healthcheck: allow, | ||||
|   | ||||
| @@ -1,16 +1,33 @@ | ||||
| const authorizationList = { | ||||
|   '/api/v1/users/:userId': { | ||||
|   'GET /api/v1/users/:userId': { | ||||
|     action: 'read', | ||||
|     subject: 'User', | ||||
|   }, | ||||
|   '/api/v1/users/': { | ||||
|   'GET /api/v1/users/': { | ||||
|     action: 'read', | ||||
|     subject: 'User', | ||||
|   }, | ||||
|   'GET /api/v1/flows/:flowId': { | ||||
|     action: 'read', | ||||
|     subject: 'Flow', | ||||
|   }, | ||||
|   'GET /api/v1/executions/:executionId': { | ||||
|     action: 'read', | ||||
|     subject: 'Execution', | ||||
|   }, | ||||
|   'GET /api/v1/executions/': { | ||||
|     action: 'read', | ||||
|     subject: 'Execution', | ||||
|   }, | ||||
|   'GET /api/v1/executions/:executionId/execution-steps': { | ||||
|     action: 'read', | ||||
|     subject: 'Execution', | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| 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]; | ||||
|  | ||||
|   try { | ||||
| @@ -20,3 +37,13 @@ export const authorizeUser = async (request, response, next) => { | ||||
|     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(); | ||||
|   } | ||||
| }; | ||||
| @@ -1,14 +1,45 @@ | ||||
| import logger from './logger.js'; | ||||
| import objection from 'objection'; | ||||
| import * as Sentry from './sentry.ee.js'; | ||||
| const { NotFoundError, DataError } = objection; | ||||
|  | ||||
| // Do not remove `next` argument as the function signature will not fit for an error handler middleware | ||||
| // eslint-disable-next-line no-unused-vars | ||||
| const errorHandler = (err, req, res, next) => { | ||||
|   if (err.message === 'Not Found') { | ||||
|     res.status(404).end(); | ||||
|   } else { | ||||
|     logger.error(err.message + '\n' + err.stack); | ||||
|     res.status(err.statusCode || 500).send(err.message); | ||||
| const errorHandler = (error, request, response, next) => { | ||||
|   if (error.message === 'Not Found' || error instanceof NotFoundError) { | ||||
|     response.status(404).end(); | ||||
|   } | ||||
|  | ||||
|   if (notFoundAppError(error)) { | ||||
|     response.status(404).end(); | ||||
|   } | ||||
|  | ||||
|   if (error instanceof DataError) { | ||||
|     response.status(400).end(); | ||||
|   } | ||||
|  | ||||
|   const statusCode = error.statusCode || 500; | ||||
|  | ||||
|   logger.error(request.method + ' ' + request.url + ' ' + statusCode); | ||||
|   logger.error(error.stack); | ||||
|  | ||||
|   Sentry.captureException(error, { | ||||
|     tags: { rest: true }, | ||||
|     extra: { | ||||
|       url: request?.url, | ||||
|       method: request?.method, | ||||
|       params: request?.params, | ||||
|     }, | ||||
|   }); | ||||
|  | ||||
|   response.status(statusCode).end(); | ||||
| }; | ||||
|  | ||||
| const notFoundAppError = (error) => { | ||||
|   return ( | ||||
|     error.message.includes('An application with the') || | ||||
|     error.message.includes("key couldn't be found.") | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export default errorHandler; | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import createHttpClient from './http-client/index.js'; | ||||
| import EarlyExitError from '../errors/early-exit.js'; | ||||
| import AlreadyProcessedError from '../errors/already-processed.js'; | ||||
| import Datastore from '../models/datastore.js'; | ||||
|  | ||||
| const globalVariable = async (options) => { | ||||
|   const { | ||||
| @@ -88,6 +89,43 @@ const globalVariable = async (options) => { | ||||
|     setActionItem: (actionItem) => { | ||||
|       $.actionOutput.data = actionItem; | ||||
|     }, | ||||
|     datastore: { | ||||
|       get: async ({ key }) => { | ||||
|         const datastore = await Datastore.query().findOne({ | ||||
|           key, | ||||
|           scope: 'flow', | ||||
|           scope_id: $.flow.id, | ||||
|         }); | ||||
|  | ||||
|         return { | ||||
|           key: datastore.key, | ||||
|           value: datastore.value, | ||||
|           [datastore.key]: datastore.value, | ||||
|         }; | ||||
|       }, | ||||
|       set: async ({ key, value }) => { | ||||
|         let datastore = await Datastore.query() | ||||
|           .where({ key, scope: 'flow', scope_id: $.flow.id }) | ||||
|           .first(); | ||||
|  | ||||
|         if (datastore) { | ||||
|           await datastore.$query().patchAndFetch({ value: value }); | ||||
|         } else { | ||||
|           datastore = await Datastore.query().insert({ | ||||
|             key, | ||||
|             value, | ||||
|             scope: 'flow', | ||||
|             scopeId: $.flow.id, | ||||
|           }); | ||||
|         } | ||||
|  | ||||
|         return { | ||||
|           key: datastore.key, | ||||
|           value: datastore.value, | ||||
|           [datastore.key]: datastore.value, | ||||
|         }; | ||||
|       }, | ||||
|     }, | ||||
|   }; | ||||
|  | ||||
|   if (request) { | ||||
|   | ||||
| @@ -4,8 +4,8 @@ import appConfig from '../config/app.js'; | ||||
| const levels = { | ||||
|   error: 0, | ||||
|   warn: 1, | ||||
|   info: 2, | ||||
|   http: 3, | ||||
|   http: 2, | ||||
|   info: 3, | ||||
|   debug: 4, | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -11,13 +11,18 @@ const isArray = (object) => | ||||
| const totalCount = (object) => | ||||
|   isPaginated(object) ? object.totalCount : isArray(object) ? object.length : 1; | ||||
|  | ||||
| const renderObject = (response, object) => { | ||||
| const renderObject = (response, object, options) => { | ||||
|   let data = isPaginated(object) ? object.records : object; | ||||
|  | ||||
|   const type = isPaginated(object) | ||||
|     ? object.records[0].constructor.name | ||||
|     : Array.isArray(object) | ||||
|     ? object?.[0]?.constructor?.name || 'Object' | ||||
|     : object.constructor.name; | ||||
|  | ||||
|   const serializer = serializers[type]; | ||||
|   const serializer = options?.serializer | ||||
|     ? serializers[options.serializer] | ||||
|     : serializers[type]; | ||||
|  | ||||
|   if (serializer) { | ||||
|     data = Array.isArray(data) | ||||
|   | ||||
| @@ -3,10 +3,13 @@ import * as Tracing from '@sentry/tracing'; | ||||
|  | ||||
| import appConfig from '../config/app.js'; | ||||
|  | ||||
| const isSentryEnabled = !!appConfig.sentryDsn; | ||||
| const isSentryEnabled = () => { | ||||
|   if (appConfig.isDev || appConfig.isTest) return false; | ||||
|   return !!appConfig.sentryDsn; | ||||
| }; | ||||
|  | ||||
| export function init(app) { | ||||
|   if (!isSentryEnabled) return; | ||||
|   if (!isSentryEnabled()) return; | ||||
|  | ||||
|   return Sentry.init({ | ||||
|     enabled: !!appConfig.sentryDsn, | ||||
| @@ -21,19 +24,19 @@ export function init(app) { | ||||
| } | ||||
|  | ||||
| export function attachRequestHandler(app) { | ||||
|   if (!isSentryEnabled) return; | ||||
|   if (!isSentryEnabled()) return; | ||||
|  | ||||
|   app.use(Sentry.Handlers.requestHandler()); | ||||
| } | ||||
|  | ||||
| export function attachTracingHandler(app) { | ||||
|   if (!isSentryEnabled) return; | ||||
|   if (!isSentryEnabled()) return; | ||||
|  | ||||
|   app.use(Sentry.Handlers.tracingHandler()); | ||||
| } | ||||
|  | ||||
| export function attachErrorHandler(app) { | ||||
|   if (!isSentryEnabled) return; | ||||
|   if (!isSentryEnabled()) return; | ||||
|  | ||||
|   app.use( | ||||
|     Sentry.Handlers.errorHandler({ | ||||
| @@ -46,7 +49,7 @@ export function attachErrorHandler(app) { | ||||
| } | ||||
|  | ||||
| export function captureException(exception, captureContext) { | ||||
|   if (!isSentryEnabled) return; | ||||
|   if (!isSentryEnabled()) return; | ||||
|  | ||||
|   return Sentry.captureException(exception, captureContext); | ||||
| } | ||||
|   | ||||
| @@ -39,6 +39,47 @@ class App { | ||||
|     return appInfoConverter(rawAppData); | ||||
|   } | ||||
|  | ||||
|   static async findAuthByKey(key, stripFuncs = false) { | ||||
|     const rawAppData = await getApp(key, stripFuncs); | ||||
|     const appData = appInfoConverter(rawAppData); | ||||
|  | ||||
|     return appData?.auth || {}; | ||||
|   } | ||||
|  | ||||
|   static async findTriggersByKey(key, stripFuncs = false) { | ||||
|     const rawAppData = await getApp(key, stripFuncs); | ||||
|     const appData = appInfoConverter(rawAppData); | ||||
|  | ||||
|     return appData?.triggers || []; | ||||
|   } | ||||
|  | ||||
|   static async findTriggerSubsteps(appKey, triggerKey, stripFuncs = false) { | ||||
|     const rawAppData = await getApp(appKey, stripFuncs); | ||||
|     const appData = appInfoConverter(rawAppData); | ||||
|  | ||||
|     const trigger = appData?.triggers?.find( | ||||
|       (trigger) => trigger.key === triggerKey | ||||
|     ); | ||||
|  | ||||
|     return trigger?.substeps || []; | ||||
|   } | ||||
|  | ||||
|   static async findActionsByKey(key, stripFuncs = false) { | ||||
|     const rawAppData = await getApp(key, stripFuncs); | ||||
|     const appData = appInfoConverter(rawAppData); | ||||
|  | ||||
|     return appData?.actions || []; | ||||
|   } | ||||
|  | ||||
|   static async findActionSubsteps(appKey, actionKey, stripFuncs = false) { | ||||
|     const rawAppData = await getApp(appKey, stripFuncs); | ||||
|     const appData = appInfoConverter(rawAppData); | ||||
|  | ||||
|     const action = appData?.actions?.find((action) => action.key === actionKey); | ||||
|  | ||||
|     return action?.substeps || []; | ||||
|   } | ||||
|  | ||||
|   static async checkAppAndAction(appKey, actionKey) { | ||||
|     const app = await this.findOneByKey(appKey); | ||||
|  | ||||
|   | ||||
							
								
								
									
										24
									
								
								packages/backend/src/models/datastore.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								packages/backend/src/models/datastore.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| import Base from './base.js'; | ||||
|  | ||||
| class Datastore extends Base { | ||||
|   static tableName = 'datastore'; | ||||
|  | ||||
|   static jsonSchema = { | ||||
|     type: 'object', | ||||
|     required: ['key', 'value', 'scope', 'scopeId'], | ||||
|  | ||||
|     properties: { | ||||
|       id: { type: 'string', format: 'uuid' }, | ||||
|       key: { type: 'string', minLength: 1 }, | ||||
|       value: { type: 'string' }, | ||||
|       scope: { | ||||
|         type: 'string', | ||||
|         enum: ['flow'], | ||||
|         default: 'flow', | ||||
|       }, | ||||
|       scopeId: { type: 'string', format: 'uuid' }, | ||||
|     }, | ||||
|   }; | ||||
| } | ||||
|  | ||||
| export default Datastore; | ||||
| @@ -15,6 +15,7 @@ import Role from './role.js'; | ||||
| import Step from './step.js'; | ||||
| import Subscription from './subscription.ee.js'; | ||||
| import UsageData from './usage-data.ee.js'; | ||||
| import Billing from '../helpers/billing/index.ee.js'; | ||||
|  | ||||
| class User extends Base { | ||||
|   static tableName = 'users'; | ||||
| @@ -143,6 +144,18 @@ class User extends Base { | ||||
|     }, | ||||
|   }); | ||||
|  | ||||
|   get authorizedFlows() { | ||||
|     const conditions = this.can('read', 'Flow'); | ||||
|     return conditions.isCreator ? this.$relatedQuery('flows') : Flow.query(); | ||||
|   } | ||||
|  | ||||
|   get authorizedExecutions() { | ||||
|     const conditions = this.can('read', 'Execution'); | ||||
|     return conditions.isCreator | ||||
|       ? this.$relatedQuery('executions') | ||||
|       : Execution.query(); | ||||
|   } | ||||
|  | ||||
|   login(password) { | ||||
|     return bcrypt.compare(password, this.password); | ||||
|   } | ||||
| @@ -237,6 +250,20 @@ class User extends Base { | ||||
|     return currentUsageData.consumedTaskCount < plan.quota; | ||||
|   } | ||||
|  | ||||
|   async getInvoices() { | ||||
|     const subscription = await this.$relatedQuery('currentSubscription'); | ||||
|  | ||||
|     if (!subscription) { | ||||
|       return []; | ||||
|     } | ||||
|  | ||||
|     const invoices = await Billing.paddleClient.getInvoices( | ||||
|       Number(subscription.paddleSubscriptionId) | ||||
|     ); | ||||
|  | ||||
|     return invoices; | ||||
|   } | ||||
|  | ||||
|   async $beforeInsert(queryContext) { | ||||
|     await super.$beforeInsert(queryContext); | ||||
|  | ||||
|   | ||||
| @@ -15,11 +15,17 @@ process.on('SIGTERM', async () => { | ||||
|   await actionQueue.close(); | ||||
| }); | ||||
|  | ||||
| actionQueue.on('error', (err) => { | ||||
|   if (err.code === CONNECTION_REFUSED) { | ||||
|     logger.error('Make sure you have installed Redis and it is running.', err); | ||||
| actionQueue.on('error', (error) => { | ||||
|   if (error.code === CONNECTION_REFUSED) { | ||||
|     logger.error( | ||||
|       'Make sure you have installed Redis and it is running.', | ||||
|       error | ||||
|     ); | ||||
|  | ||||
|     process.exit(); | ||||
|   } | ||||
|  | ||||
|   logger.error('Error happened in action queue!', error); | ||||
| }); | ||||
|  | ||||
| export default actionQueue; | ||||
|   | ||||
| @@ -15,11 +15,17 @@ process.on('SIGTERM', async () => { | ||||
|   await deleteUserQueue.close(); | ||||
| }); | ||||
|  | ||||
| deleteUserQueue.on('error', (err) => { | ||||
|   if (err.code === CONNECTION_REFUSED) { | ||||
|     logger.error('Make sure you have installed Redis and it is running.', err); | ||||
| deleteUserQueue.on('error', (error) => { | ||||
|   if (error.code === CONNECTION_REFUSED) { | ||||
|     logger.error( | ||||
|       'Make sure you have installed Redis and it is running.', | ||||
|       error | ||||
|     ); | ||||
|  | ||||
|     process.exit(); | ||||
|   } | ||||
|  | ||||
|   logger.error('Error happened in delete user queue!', error); | ||||
| }); | ||||
|  | ||||
| export default deleteUserQueue; | ||||
|   | ||||
| @@ -15,11 +15,17 @@ process.on('SIGTERM', async () => { | ||||
|   await emailQueue.close(); | ||||
| }); | ||||
|  | ||||
| emailQueue.on('error', (err) => { | ||||
|   if (err.code === CONNECTION_REFUSED) { | ||||
|     logger.error('Make sure you have installed Redis and it is running.', err); | ||||
| emailQueue.on('error', (error) => { | ||||
|   if (error.code === CONNECTION_REFUSED) { | ||||
|     logger.error( | ||||
|       'Make sure you have installed Redis and it is running.', | ||||
|       error | ||||
|     ); | ||||
|  | ||||
|     process.exit(); | ||||
|   } | ||||
|  | ||||
|   logger.error('Error happened in email queue!', error); | ||||
| }); | ||||
|  | ||||
| export default emailQueue; | ||||
|   | ||||
| @@ -15,11 +15,17 @@ process.on('SIGTERM', async () => { | ||||
|   await flowQueue.close(); | ||||
| }); | ||||
|  | ||||
| flowQueue.on('error', (err) => { | ||||
|   if (err.code === CONNECTION_REFUSED) { | ||||
|     logger.error('Make sure you have installed Redis and it is running.', err); | ||||
| flowQueue.on('error', (error) => { | ||||
|   if (error.code === CONNECTION_REFUSED) { | ||||
|     logger.error( | ||||
|       'Make sure you have installed Redis and it is running.', | ||||
|       error | ||||
|     ); | ||||
|  | ||||
|     process.exit(); | ||||
|   } | ||||
|  | ||||
|   logger.error('Error happened in flow queue!', error); | ||||
| }); | ||||
|  | ||||
| export default flowQueue; | ||||
|   | ||||
| @@ -18,11 +18,20 @@ process.on('SIGTERM', async () => { | ||||
|   await removeCancelledSubscriptionsQueue.close(); | ||||
| }); | ||||
|  | ||||
| removeCancelledSubscriptionsQueue.on('error', (err) => { | ||||
|   if (err.code === CONNECTION_REFUSED) { | ||||
|     logger.error('Make sure you have installed Redis and it is running.', err); | ||||
| removeCancelledSubscriptionsQueue.on('error', (error) => { | ||||
|   if (error.code === CONNECTION_REFUSED) { | ||||
|     logger.error( | ||||
|       'Make sure you have installed Redis and it is running.', | ||||
|       error | ||||
|     ); | ||||
|  | ||||
|     process.exit(); | ||||
|   } | ||||
|  | ||||
|   logger.error( | ||||
|     'Error happened in remove cancelled subscriptions queue!', | ||||
|     error | ||||
|   ); | ||||
| }); | ||||
|  | ||||
| removeCancelledSubscriptionsQueue.add('remove-cancelled-subscriptions', null, { | ||||
|   | ||||
| @@ -15,11 +15,17 @@ process.on('SIGTERM', async () => { | ||||
|   await triggerQueue.close(); | ||||
| }); | ||||
|  | ||||
| triggerQueue.on('error', (err) => { | ||||
|   if (err.code === CONNECTION_REFUSED) { | ||||
|     logger.error('Make sure you have installed Redis and it is running.', err); | ||||
| triggerQueue.on('error', (error) => { | ||||
|   if (error.code === CONNECTION_REFUSED) { | ||||
|     logger.error( | ||||
|       'Make sure you have installed Redis and it is running.', | ||||
|       error | ||||
|     ); | ||||
|  | ||||
|     process.exit(); | ||||
|   } | ||||
|  | ||||
|   logger.error('Error happened in trigger queue!', error); | ||||
| }); | ||||
|  | ||||
| export default triggerQueue; | ||||
|   | ||||
							
								
								
									
										18
									
								
								packages/backend/src/routes/api/v1/admin/app-auth-clients.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								packages/backend/src/routes/api/v1/admin/app-auth-clients.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| import { Router } from 'express'; | ||||
| import asyncHandler from 'express-async-handler'; | ||||
| import { authenticateUser } from '../../../../helpers/authentication.js'; | ||||
| import { authorizeAdmin } from '../../../../helpers/authorization.js'; | ||||
| import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js'; | ||||
| import getAdminAppAuthClientsAction from '../../../../controllers/api/v1/admin/app-auth-clients/get-app-auth-client.js'; | ||||
|  | ||||
| const router = Router(); | ||||
|  | ||||
| router.get( | ||||
|   '/:appAuthClientId', | ||||
|   authenticateUser, | ||||
|   authorizeAdmin, | ||||
|   checkIsEnterprise, | ||||
|   asyncHandler(getAdminAppAuthClientsAction) | ||||
| ); | ||||
|  | ||||
| export default router; | ||||
							
								
								
									
										18
									
								
								packages/backend/src/routes/api/v1/admin/permissions.ee.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								packages/backend/src/routes/api/v1/admin/permissions.ee.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| import { Router } from 'express'; | ||||
| import asyncHandler from 'express-async-handler'; | ||||
| import { authenticateUser } from '../../../../helpers/authentication.js'; | ||||
| import { authorizeAdmin } from '../../../../helpers/authorization.js'; | ||||
| import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js'; | ||||
| import getPermissionsCatalogAction from '../../../../controllers/api/v1/admin/permissions/get-permissions-catalog.ee.js'; | ||||
|  | ||||
| const router = Router(); | ||||
|  | ||||
| router.get( | ||||
|   '/catalog', | ||||
|   authenticateUser, | ||||
|   authorizeAdmin, | ||||
|   checkIsEnterprise, | ||||
|   asyncHandler(getPermissionsCatalogAction) | ||||
| ); | ||||
|  | ||||
| export default router; | ||||
							
								
								
									
										27
									
								
								packages/backend/src/routes/api/v1/admin/roles.ee.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								packages/backend/src/routes/api/v1/admin/roles.ee.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| import { Router } from 'express'; | ||||
| import asyncHandler from 'express-async-handler'; | ||||
| import { authenticateUser } from '../../../../helpers/authentication.js'; | ||||
| import { authorizeAdmin } from '../../../../helpers/authorization.js'; | ||||
| import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js'; | ||||
| import getRolesAction from '../../../../controllers/api/v1/admin/roles/get-roles.ee.js'; | ||||
| import getRoleAction from '../../../../controllers/api/v1/admin/roles/get-role.ee.js'; | ||||
|  | ||||
| const router = Router(); | ||||
|  | ||||
| router.get( | ||||
|   '/', | ||||
|   authenticateUser, | ||||
|   authorizeAdmin, | ||||
|   checkIsEnterprise, | ||||
|   asyncHandler(getRolesAction) | ||||
| ); | ||||
|  | ||||
| router.get( | ||||
|   '/:roleId', | ||||
|   authenticateUser, | ||||
|   authorizeAdmin, | ||||
|   checkIsEnterprise, | ||||
|   asyncHandler(getRoleAction) | ||||
| ); | ||||
|  | ||||
| export default router; | ||||
| @@ -0,0 +1,27 @@ | ||||
| import { Router } from 'express'; | ||||
| import asyncHandler from 'express-async-handler'; | ||||
| import { authenticateUser } from '../../../../helpers/authentication.js'; | ||||
| import { authorizeAdmin } from '../../../../helpers/authorization.js'; | ||||
| import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js'; | ||||
| import getSamlAuthProvidersAction from '../../../../controllers/api/v1/admin/saml-auth-providers/get-saml-auth-providers.ee.js'; | ||||
| import getSamlAuthProviderAction from '../../../../controllers/api/v1/admin/saml-auth-providers/get-saml-auth-provider.ee.js'; | ||||
|  | ||||
| const router = Router(); | ||||
|  | ||||
| router.get( | ||||
|   '/', | ||||
|   authenticateUser, | ||||
|   authorizeAdmin, | ||||
|   checkIsEnterprise, | ||||
|   asyncHandler(getSamlAuthProvidersAction) | ||||
| ); | ||||
|  | ||||
| router.get( | ||||
|   '/:samlAuthProviderId', | ||||
|   authenticateUser, | ||||
|   authorizeAdmin, | ||||
|   checkIsEnterprise, | ||||
|   asyncHandler(getSamlAuthProviderAction) | ||||
| ); | ||||
|  | ||||
| export default router; | ||||
							
								
								
									
										27
									
								
								packages/backend/src/routes/api/v1/admin/users.ee.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								packages/backend/src/routes/api/v1/admin/users.ee.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| import { Router } from 'express'; | ||||
| import asyncHandler from 'express-async-handler'; | ||||
| import { authenticateUser } from '../../../../helpers/authentication.js'; | ||||
| import { authorizeAdmin } from '../../../../helpers/authorization.js'; | ||||
| import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js'; | ||||
| import getUsersAction from '../../../../controllers/api/v1/admin/users/get-users.ee.js'; | ||||
| import getUserAction from '../../../../controllers/api/v1/admin/users/get-user.ee.js'; | ||||
|  | ||||
| const router = Router(); | ||||
|  | ||||
| router.get( | ||||
|   '/', | ||||
|   authenticateUser, | ||||
|   authorizeAdmin, | ||||
|   checkIsEnterprise, | ||||
|   asyncHandler(getUsersAction) | ||||
| ); | ||||
|  | ||||
| router.get( | ||||
|   '/:userId', | ||||
|   authenticateUser, | ||||
|   authorizeAdmin, | ||||
|   checkIsEnterprise, | ||||
|   asyncHandler(getUserAction) | ||||
| ); | ||||
|  | ||||
| export default router; | ||||
							
								
								
									
										16
									
								
								packages/backend/src/routes/api/v1/app-auth-clients.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								packages/backend/src/routes/api/v1/app-auth-clients.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| import { Router } from 'express'; | ||||
| import asyncHandler from 'express-async-handler'; | ||||
| import { authenticateUser } from '../../../helpers/authentication.js'; | ||||
| import { checkIsEnterprise } from '../../../helpers/check-is-enterprise.js'; | ||||
| import getAppAuthClientAction from '../../../controllers/api/v1/app-auth-clients/get-app-auth-client.js'; | ||||
|  | ||||
| const router = Router(); | ||||
|  | ||||
| router.get( | ||||
|   '/:appAuthClientId', | ||||
|   authenticateUser, | ||||
|   checkIsEnterprise, | ||||
|   asyncHandler(getAppAuthClientAction) | ||||
| ); | ||||
|  | ||||
| export default router; | ||||
							
								
								
									
										42
									
								
								packages/backend/src/routes/api/v1/apps.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								packages/backend/src/routes/api/v1/apps.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| import { Router } from 'express'; | ||||
| import asyncHandler from 'express-async-handler'; | ||||
| import { authenticateUser } from '../../../helpers/authentication.js'; | ||||
| import getAppAction from '../../../controllers/api/v1/apps/get-app.js'; | ||||
| import getAppsAction from '../../../controllers/api/v1/apps/get-apps.js'; | ||||
| import getAuthAction from '../../../controllers/api/v1/apps/get-auth.js'; | ||||
| import getTriggersAction from '../../../controllers/api/v1/apps/get-triggers.js'; | ||||
| import getTriggerSubstepsAction from '../../../controllers/api/v1/apps/get-trigger-substeps.js'; | ||||
| import getActionsAction from '../../../controllers/api/v1/apps/get-actions.js'; | ||||
| import getActionSubstepsAction from '../../../controllers/api/v1/apps/get-action-substeps.js'; | ||||
|  | ||||
| const router = Router(); | ||||
|  | ||||
| router.get('/', authenticateUser, asyncHandler(getAppsAction)); | ||||
| router.get('/:appKey', authenticateUser, asyncHandler(getAppAction)); | ||||
| router.get('/:appKey/auth', authenticateUser, asyncHandler(getAuthAction)); | ||||
|  | ||||
| router.get( | ||||
|   '/:appKey/triggers', | ||||
|   authenticateUser, | ||||
|   asyncHandler(getTriggersAction) | ||||
| ); | ||||
|  | ||||
| router.get( | ||||
|   '/:appKey/triggers/:triggerKey/substeps', | ||||
|   authenticateUser, | ||||
|   asyncHandler(getTriggerSubstepsAction) | ||||
| ); | ||||
|  | ||||
| router.get( | ||||
|   '/:appKey/actions', | ||||
|   authenticateUser, | ||||
|   asyncHandler(getActionsAction) | ||||
| ); | ||||
|  | ||||
| router.get( | ||||
|   '/:appKey/actions/:actionKey/substeps', | ||||
|   authenticateUser, | ||||
|   asyncHandler(getActionSubstepsAction) | ||||
| ); | ||||
|  | ||||
| export default router; | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user