Compare commits
	
		
			425 Commits
		
	
	
		
			dependabot
			...
			AUT-804
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 53e97729dc | ||
|   | 094c6c0c27 | ||
|   | 1f520ed590 | ||
|   | 686c834748 | ||
|   | d6b9410932 | ||
|   | dff1c5e387 | ||
|   | 9b51e0f746 | ||
|   | e4292815cd | ||
|   | ab37250d5d | ||
|   | e5be8d3ba7 | ||
|   | 96a421fa22 | ||
|   | 12f72401b1 | ||
|   | 7391a9eddc | ||
|   | 30dee27f72 | ||
|   | 51a9939034 | ||
|   | e03c6e0ca4 | ||
|   | bece5c6488 | ||
|   | d49bb4c52d | ||
|   | 73d0eec30c | ||
|   | 5c756b16ca | ||
|   | f482c2422c | ||
|   | 2e564c863f | ||
|   | d9917a81bb | ||
|   | 61dc431f92 | ||
|   | 7d2fb8d9d7 | ||
|   | 608b79b66f | ||
|   | 009754c18b | ||
|   | 5df07c289e | ||
|   | a36d10870b | ||
|   | b549ba3e39 | ||
|   | 897c96361f | ||
|   | e7693d8aa6 | ||
|   | 1fe755f836 | ||
|   | ea1a63f7dd | ||
|   | 85134722a5 | ||
|   | 5c9d3ed134 | ||
|   | 17fb935ea0 | ||
|   | 196642a1cf | ||
|   | 009cf63d8c | ||
|   | da399aacd6 | ||
|   | 3632ee77e5 | ||
|   | 2901f337cc | ||
|   | f0bd2f335b | ||
|   | acdd026448 | ||
|   | fb0a328ab0 | ||
|   | d2a7889fc9 | ||
|   | 88c50e014d | ||
|   | f0ef12f904 | ||
|   | 1827f5413f | ||
|   | 0609f30e25 | ||
|   | d4e4d95b6d | ||
|   | d74af4931e | ||
|   | bee043d10d | ||
|   | a65e48b98a | ||
|   | ee26b54d54 | ||
|   | 855ec53dc2 | ||
|   | 3e3e48110d | ||
|   | fc04a357c8 | ||
|   | c8147370de | ||
|   | 999426be89 | ||
|   | 91458f91ef | ||
|   | 4b9ed29cc0 | ||
|   | e3bcb673fb | ||
|   | bf4776ca4f | ||
|   | 9f7f30a92a | ||
|   | 5c29fff55e | ||
|   | a0160c2573 | ||
|   | 87d3ca287d | ||
|   | 526e093689 | ||
|   | 0930c9d8d6 | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | ec680a713d | ||
|   | 583f90d1e9 | ||
|   | 8ba95381bc | ||
|   | ec6d634b99 | ||
|   | bc082acbe7 | ||
|   | e474ba02cb | ||
|   | ea922aaf10 | ||
|   | 766e6e20d8 | ||
|   | 8e646c244e | ||
|   | 26f31a5899 | ||
|   | 5c79e374dd | ||
|   | 7c1473ea95 | ||
|   | 1fe4cc3258 | ||
|   | 042ad4cea1 | ||
|   | e4c998dbce | ||
|   | 83c8cacdac | ||
|   | f75d5d906e | ||
|   | 85b3856564 | ||
|   | 75cb2569b5 | ||
|   | 0a4ac1cece | ||
|   | a873fd14bd | ||
|   | 85b4cd4998 | ||
|   | e9bc9b1aa8 | ||
|   | e3bf599bf6 | ||
|   | 01ae96840e | ||
|   | 186160ebf4 | ||
|   | 70f5e45c1f | ||
|   | 6dc54ecabc | ||
|   | d21888c047 | ||
|   | 33f7a90042 | ||
|   | a00d3a2c5e | ||
|   | abc64d769c | ||
|   | 88754ac569 | ||
|   | e3ee05d47d | ||
|   | 3b004e7483 | ||
|   | 9aa48c20e4 | ||
|   | 1b5d3beeca | ||
|   | 00115d313e | ||
|   | 190f1a205f | ||
|   | 8ab6f0c3fe | ||
|   | 13eea263c0 | ||
|   | b52b40962e | ||
|   | 7d1fa2e40c | ||
|   | 93b2098829 | ||
|   | a2acdc6b12 | ||
|   | 38b2c1e30f | ||
|   | e07f579f3c | ||
|   | df3297b6ca | ||
|   | fc4eeed764 | ||
|   | 3596d13be1 | ||
|   | 104d49ea1c | ||
|   | 7057317446 | ||
|   | 280575df88 | ||
|   | d2cb434b7b | ||
|   | 2ecb802a2e | ||
|   | 46e706c415 | ||
|   | 3a57349d8a | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 565db852e0 | ||
|   | 754c3269ec | ||
|   | a079842408 | ||
|   | 7664b58553 | ||
|   | de77488f7e | ||
|   | d808afd21b | ||
|   | b68aff76a1 | ||
|   | 6da7fe158f | ||
|   | 4dbc7fdc7d | ||
|   | ad1e1f7eca | ||
|   | 9c3f7a3823 | ||
|   | 86f4cb7701 | ||
|   | 359a90245d | ||
|   | d8d7d86359 | ||
|   | 7189b629c0 | ||
|   | 55c9b5566c | ||
|   | ab671ccbf7 | ||
|   | 316bda8c3f | ||
|   | 76f77e8a4c | ||
|   | 4a99d5eab7 | ||
|   | 473d287c6d | ||
|   | bddd9896e4 | ||
|   | 95eb115965 | ||
|   | 9a63b213b0 | ||
|   | 90b00d88f1 | ||
|   | ec87c7f21c | ||
|   | 452f45cac6 | ||
|   | c644b3d384 | ||
|   | 68160c20e8 | ||
|   | ad144206dd | ||
|   | f3d20ab769 | ||
|   | 9767ca7116 | ||
|   | 73a5b8553f | ||
| ![dependabot[bot]](/assets/img/avatar_default.png)  | 5c684cd499 | ||
|   | 479f3e3172 | ||
|   | 6a1350fd00 | ||
|   | 563784da1c | ||
|   | 347f0ed3a5 | ||
|   | 1db9f5b2c2 | ||
|   | f2a3e26188 | ||
|   | 1c0897bfb6 | ||
|   | f0793992a6 | ||
|   | 393205ba2f | ||
|   | ab49535b6c | ||
|   | 8191b48548 | ||
|   | 925dd06432 | ||
|   | 0d525e056a | ||
|   | 3aa86eebf2 | ||
|   | d7a93abec0 | ||
|   | 6448d28a18 | ||
|   | 449976483c | ||
|   | e468b762ef | ||
|   | b578e73cc4 | ||
|   | e381f95b95 | ||
|   | 5685afae63 | ||
|   | 53a473422b | ||
|   | 3f9f17f584 | ||
|   | 2c410bf318 | ||
|   | 40934a2c77 | ||
|   | ecc9379d7e | ||
|   | f8d27342dc | ||
|   | 17bd2bf2ba | ||
|   | d984a3f275 | ||
|   | 64049bd546 | ||
|   | 9218091c33 | ||
|   | 75df7d6413 | ||
|   | 29341f81e1 | ||
|   | 68c5a3dca7 | ||
|   | b1e2e370c8 | ||
|   | ba9d3afc88 | ||
|   | 9a0434be32 | ||
|   | d6923a2ff0 | ||
|   | 8f7f6dc19e | ||
|   | 70b8817643 | ||
|   | 87c25cbbfe | ||
|   | 082e905014 | ||
|   | e3e598b208 | ||
|   | 6cf92d4ea6 | ||
|   | 89ad685f3a | ||
|   | 1e868dc802 | ||
|   | 58fcfd9a34 | ||
|   | c849afbc11 | ||
|   | 2ebe71ddd0 | ||
|   | 7a8e8c1f3e | ||
|   | 5e897ad1c2 | ||
|   | ce07907f85 | ||
|   | 1e38aa7b53 | ||
|   | 3bc0c23e5a | ||
|   | f07b6d105a | ||
|   | 46491269e3 | ||
|   | 8d9c43af6a | ||
|   | c3568354aa | ||
|   | e68696ccd4 | ||
|   | 35d8b2e790 | ||
|   | 1f83573206 | ||
|   | 2887e76514 | ||
|   | 63b9943203 | ||
|   | bd5aedd83f | ||
|   | c9ff6d7bb9 | ||
|   | 5835def5d0 | ||
|   | 6a2694ce3b | ||
|   | 65ae7bce79 | ||
|   | 57ce8da0ee | ||
|   | 7484bf7403 | ||
|   | f6b2312c49 | ||
|   | 6027cb7cb0 | ||
|   | c1740aae6c | ||
|   | 22ce29e86c | ||
|   | 251d1b5b2e | ||
|   | ea64708c69 | ||
|   | 209ec27a29 | ||
|   | ceee495525 | ||
|   | 751a2347aa | ||
|   | efd96d5fdf | ||
|   | 2ee5af8bfb | ||
|   | a4c0edf493 | ||
|   | 28c8be97b6 | ||
|   | f320a44d45 | ||
|   | c0cc6cc176 | ||
|   | be62c09d06 | ||
|   | 5fe3546d2a | ||
|   | c4b2ea125c | ||
|   | 3301b038fe | ||
|   | 6a58d1e3da | ||
|   | e5670d820d | ||
|   | 1870aead73 | ||
|   | 95db6cca2c | ||
|   | 79a792ac62 | ||
|   | 6406f9eb86 | ||
|   | 8f8ec496f8 | ||
|   | 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 | ||
|   | bd1ad5fa56 | ||
|   | f2e22e7445 | ||
|   | 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 | rm -rf .env | ||||||
| echo " | echo " | ||||||
| PORT=$WEB_PORT | PORT=$WEB_PORT | ||||||
| REACT_APP_GRAPHQL_URL=http://localhost:$BACKEND_PORT/graphql | REACT_APP_BACKEND_URL=http://localhost:$BACKEND_PORT | ||||||
| " >> .env | " >> .env | ||||||
| cd $CURRENT_DIR | cd $CURRENT_DIR | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
|       "version": "latest" |       "version": "latest" | ||||||
|     }, |     }, | ||||||
|     "ghcr.io/devcontainers/features/node:1": { |     "ghcr.io/devcontainers/features/node:1": { | ||||||
|       "version": 20 |       "version": 18 | ||||||
|     }, |     }, | ||||||
|     "ghcr.io/devcontainers/features/common-utils:1": { |     "ghcr.io/devcontainers/features/common-utils:1": { | ||||||
|       "username": "vscode", |       "username": "vscode", | ||||||
|   | |||||||
							
								
								
									
										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 ${{ github.repository }} repository has been cloned to the runner." | ||||||
|       - run: echo "🖥️ The workflow is now ready to test your code on the runner." |       - run: echo "🖥️ The workflow is now ready to test your code on the runner." | ||||||
|       - run: yarn --frozen-lockfile |       - run: yarn --frozen-lockfile | ||||||
|       - run: yarn lint |       - run: cd packages/backend && yarn lint | ||||||
|       - run: echo "🍏 This job's status is ${{ job.status }}." |       - run: echo "🍏 This job's status is ${{ job.status }}." | ||||||
|   start-backend-server: |   start-backend-server: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|   | |||||||
							
								
								
									
										32
									
								
								.github/workflows/docs-change.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								.github/workflows/docs-change.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | name: Automatisch Docs Change | ||||||
|  | on: | ||||||
|  |   pull_request: | ||||||
|  |     paths: | ||||||
|  |       - 'packages/docs/**' | ||||||
|  | jobs: | ||||||
|  |   label: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - name: Checkout repository | ||||||
|  |         uses: actions/checkout@v3 | ||||||
|  |       - name: Label PR | ||||||
|  |         uses: actions/github-script@v6 | ||||||
|  |         with: | ||||||
|  |           script: | | ||||||
|  |             const { pull_request } = context.payload; | ||||||
|  |  | ||||||
|  |             const label = 'documentation-change'; | ||||||
|  |             const hasLabel = pull_request.labels.some(({ name }) => name === label); | ||||||
|  |  | ||||||
|  |             if (!hasLabel) { | ||||||
|  |               await github.rest.issues.addLabels({ | ||||||
|  |                 owner: context.repo.owner, | ||||||
|  |                 repo: context.repo.repo, | ||||||
|  |                 issue_number: pull_request.number, | ||||||
|  |                 labels: [label], | ||||||
|  |               }); | ||||||
|  |  | ||||||
|  |               console.log(`Label "${label}" added to PR #${pull_request.number}`); | ||||||
|  |             } else { | ||||||
|  |               console.log(`Label "${label}" already exists on PR #${pull_request.number}`); | ||||||
|  |             } | ||||||
							
								
								
									
										5
									
								
								.github/workflows/playwright.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/workflows/playwright.yml
									
									
									
									
										vendored
									
									
								
							| @@ -62,8 +62,9 @@ jobs: | |||||||
|         run: yarn && yarn lerna bootstrap |         run: yarn && yarn lerna bootstrap | ||||||
|       - name: Install Playwright Browsers |       - name: Install Playwright Browsers | ||||||
|         run: yarn playwright install --with-deps |         run: yarn playwright install --with-deps | ||||||
|       - name: Build Automatisch |       - name: Build Automatisch web | ||||||
|         run: yarn lerna run --scope=@*/{web,cli} build |         working-directory: ./packages/web | ||||||
|  |         run: yarn build | ||||||
|         env: |         env: | ||||||
|           # Keep this until we clean up warnings in build processes |           # Keep this until we clean up warnings in build processes | ||||||
|           CI: false |           CI: false | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ | |||||||
|     "start": "lerna run --stream --parallel --scope=@*/{web,backend} dev", |     "start": "lerna run --stream --parallel --scope=@*/{web,backend} dev", | ||||||
|     "start:web": "lerna run --stream --scope=@*/web dev", |     "start:web": "lerna run --stream --scope=@*/web dev", | ||||||
|     "start:backend": "lerna run --stream --scope=@*/backend 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" |     "build:docs": "cd ./packages/docs && yarn install && yarn build" | ||||||
|   }, |   }, | ||||||
|   "workspaces": { |   "workspaces": { | ||||||
| @@ -21,8 +20,6 @@ | |||||||
|     ] |     ] | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@typescript-eslint/eslint-plugin": "^5.9.1", |  | ||||||
|     "@typescript-eslint/parser": "^5.9.1", |  | ||||||
|     "eslint": "^8.13.0", |     "eslint": "^8.13.0", | ||||||
|     "eslint-config-prettier": "^8.3.0", |     "eslint-config-prettier": "^8.3.0", | ||||||
|     "eslint-plugin-prettier": "^4.0.0", |     "eslint-plugin-prettier": "^4.0.0", | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ | |||||||
|     "start:worker": "node src/worker.js", |     "start:worker": "node src/worker.js", | ||||||
|     "pretest": "APP_ENV=test node ./test/setup/prepare-test-env.js", |     "pretest": "APP_ENV=test node ./test/setup/prepare-test-env.js", | ||||||
|     "test": "APP_ENV=test vitest run", |     "test": "APP_ENV=test vitest run", | ||||||
|     "lint": "eslint . --ignore-path ../../.eslintignore", |     "lint": "eslint .", | ||||||
|     "db:create": "node ./bin/database/create.js", |     "db:create": "node ./bin/database/create.js", | ||||||
|     "db:seed:user": "node ./bin/database/seed-user.js", |     "db:seed:user": "node ./bin/database/seed-user.js", | ||||||
|     "db:drop": "node ./bin/database/drop.js", |     "db:drop": "node ./bin/database/drop.js", | ||||||
| @@ -38,6 +38,7 @@ | |||||||
|     "debug": "~2.6.9", |     "debug": "~2.6.9", | ||||||
|     "dotenv": "^10.0.0", |     "dotenv": "^10.0.0", | ||||||
|     "express": "~4.18.2", |     "express": "~4.18.2", | ||||||
|  |     "express-async-handler": "^1.2.0", | ||||||
|     "express-basic-auth": "^1.2.1", |     "express-basic-auth": "^1.2.1", | ||||||
|     "express-graphql": "^0.12.0", |     "express-graphql": "^0.12.0", | ||||||
|     "fast-xml-parser": "^4.0.11", |     "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, | ||||||
|  | }); | ||||||
| @@ -0,0 +1,116 @@ | |||||||
|  | import defineAction from '../../../../helpers/define-action.js'; | ||||||
|  |  | ||||||
|  | export default defineAction({ | ||||||
|  |   name: 'Create Cloud Firestore document', | ||||||
|  |   key: 'createCloudFirestoreDocument', | ||||||
|  |   description: 'Creates a new document within a Cloud Firestore collection.', | ||||||
|  |   arguments: [ | ||||||
|  |     { | ||||||
|  |       label: 'Collection', | ||||||
|  |       key: 'collectionId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: true, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listFirestoreCollections', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Convert Numerics', | ||||||
|  |       key: 'convertNumerics', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       description: | ||||||
|  |         "If any value represents a valid numerical value, whether it's an integer or a floating-point number, this field directs the database to store it as a numeric data type instead of a string.", | ||||||
|  |       variables: true, | ||||||
|  |       options: [ | ||||||
|  |         { label: 'Yes', value: true }, | ||||||
|  |         { label: 'No', value: false }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Document ID', | ||||||
|  |       key: 'documentId', | ||||||
|  |       type: 'string', | ||||||
|  |       required: false, | ||||||
|  |       description: | ||||||
|  |         'The document ID to use for this document. If not specified, an ID will be assigned.', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Document Data', | ||||||
|  |       key: 'documentData', | ||||||
|  |       type: 'dynamic', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       fields: [ | ||||||
|  |         { | ||||||
|  |           label: 'Key', | ||||||
|  |           key: 'key', | ||||||
|  |           type: 'string', | ||||||
|  |           required: false, | ||||||
|  |           variables: true, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           label: 'Value', | ||||||
|  |           key: 'value', | ||||||
|  |           type: 'string', | ||||||
|  |           required: false, | ||||||
|  |           variables: true, | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const projectId = $.auth.data.projectId; | ||||||
|  |     const { collectionId, documentId, documentData, convertNumerics } = | ||||||
|  |       $.step.parameters; | ||||||
|  |  | ||||||
|  |     const documentDataObject = documentData.reduce((result, entry) => { | ||||||
|  |       const key = entry.key?.toLowerCase(); | ||||||
|  |       const value = entry.value; | ||||||
|  |       const isNumber = !isNaN(parseFloat(value)); | ||||||
|  |  | ||||||
|  |       if (key && value) { | ||||||
|  |         const formattedValue = | ||||||
|  |           convertNumerics && isNumber | ||||||
|  |             ? { integerValue: parseFloat(value) } | ||||||
|  |             : { stringValue: value }; | ||||||
|  |  | ||||||
|  |         return { | ||||||
|  |           ...result, | ||||||
|  |           [key]: formattedValue, | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       return result; | ||||||
|  |     }, {}); | ||||||
|  |  | ||||||
|  |     const body = { | ||||||
|  |       fields: documentDataObject, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.post( | ||||||
|  |       `/v1/projects/${projectId}/databases/(default)/documents/${collectionId}?documentId=${documentId}`, | ||||||
|  |       body, | ||||||
|  |       { | ||||||
|  |         additionalProperties: { | ||||||
|  |           setFirestoreBaseUrl: true, | ||||||
|  |         }, | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     $.setActionItem({ | ||||||
|  |       raw: data, | ||||||
|  |     }); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
| @@ -0,0 +1,100 @@ | |||||||
|  | import defineAction from '../../../../helpers/define-action.js'; | ||||||
|  |  | ||||||
|  | export default defineAction({ | ||||||
|  |   name: 'Create Firebase Realtime Database Record', | ||||||
|  |   key: 'createFirebaseRealtimeDatabaseRecord', | ||||||
|  |   description: 'Creates a child object within your Firebase Realtime Database.', | ||||||
|  |   arguments: [ | ||||||
|  |     { | ||||||
|  |       label: 'Path', | ||||||
|  |       key: 'path', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       description: | ||||||
|  |         "Indicate the path to the key of the object where the child objects to be queried are located, for example, 'foo/bar/here'.", | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Convert Numerics', | ||||||
|  |       key: 'convertNumerics', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       description: | ||||||
|  |         "If any value represents a valid numerical value, whether it's an integer or a floating-point number, this field directs the database to store it as a numeric data type instead of a string.", | ||||||
|  |       variables: true, | ||||||
|  |       options: [ | ||||||
|  |         { label: 'Yes', value: true }, | ||||||
|  |         { label: 'No', value: false }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'New ID', | ||||||
|  |       key: 'newId', | ||||||
|  |       type: 'string', | ||||||
|  |       required: false, | ||||||
|  |       description: | ||||||
|  |         'The key to use for this object, or leave it blank for Firebase to create one automatically.', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Data', | ||||||
|  |       key: 'childData', | ||||||
|  |       type: 'dynamic', | ||||||
|  |       required: false, | ||||||
|  |       description: '', | ||||||
|  |       fields: [ | ||||||
|  |         { | ||||||
|  |           label: 'Key', | ||||||
|  |           key: 'key', | ||||||
|  |           type: 'string', | ||||||
|  |           required: false, | ||||||
|  |           variables: true, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           label: 'Value', | ||||||
|  |           key: 'value', | ||||||
|  |           type: 'string', | ||||||
|  |           required: false, | ||||||
|  |           variables: true, | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     let path = $.step.parameters.path; | ||||||
|  |     const { convertNumerics, newId, childData } = $.step.parameters; | ||||||
|  |  | ||||||
|  |     if (newId) { | ||||||
|  |       path = `${path}/${newId}.json`; | ||||||
|  |     } else { | ||||||
|  |       path = `${path}.json`; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const formattedChildObjectData = childData.reduce((result, entry) => { | ||||||
|  |       const key = entry?.key; | ||||||
|  |       const value = entry?.value; | ||||||
|  |       const isNumber = !isNaN(parseFloat(value)); | ||||||
|  |  | ||||||
|  |       if (isNumber && convertNumerics) { | ||||||
|  |         result[key] = parseFloat(value); | ||||||
|  |       } else { | ||||||
|  |         result[key] = value; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       return result; | ||||||
|  |     }, {}); | ||||||
|  |  | ||||||
|  |     const body = formattedChildObjectData; | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.post(path, body, { | ||||||
|  |       additionalProperties: { | ||||||
|  |         setFirestoreBaseUrl: false, | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     $.setActionItem({ | ||||||
|  |       raw: data, | ||||||
|  |     }); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
| @@ -0,0 +1,53 @@ | |||||||
|  | import defineAction from '../../../../helpers/define-action.js'; | ||||||
|  |  | ||||||
|  | export default defineAction({ | ||||||
|  |   name: 'Find Cloud Firestore document', | ||||||
|  |   key: 'findCloudFirestoreDocument', | ||||||
|  |   description: 'Finds a document within a collection.', | ||||||
|  |   arguments: [ | ||||||
|  |     { | ||||||
|  |       label: 'Collection', | ||||||
|  |       key: 'collectionId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: true, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listFirestoreCollections', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Document ID', | ||||||
|  |       key: 'documentId', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const projectId = $.auth.data.projectId; | ||||||
|  |     const { collectionId, documentId } = $.step.parameters; | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.get( | ||||||
|  |       `/v1/projects/${projectId}/databases/(default)/documents/${collectionId}/${documentId}`, | ||||||
|  |       { | ||||||
|  |         additionalProperties: { | ||||||
|  |           setFirestoreBaseUrl: true, | ||||||
|  |         }, | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     $.setActionItem({ | ||||||
|  |       raw: data, | ||||||
|  |     }); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
| @@ -0,0 +1,32 @@ | |||||||
|  | import defineAction from '../../../../helpers/define-action.js'; | ||||||
|  |  | ||||||
|  | export default defineAction({ | ||||||
|  |   name: 'Find Firebase Realtime Database Record', | ||||||
|  |   key: 'findFirebaseRealtimeDatabaseRecord', | ||||||
|  |   description: 'Finds a child object in Firebase Realtime Database.', | ||||||
|  |   arguments: [ | ||||||
|  |     { | ||||||
|  |       label: 'Path', | ||||||
|  |       key: 'path', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       description: | ||||||
|  |         "Indicate the path to the key of the object where the child objects to be queried are located, for example, 'foo/bar/here'.", | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const { path } = $.step.parameters; | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.get(`${path}.json`, { | ||||||
|  |       additionalProperties: { | ||||||
|  |         setFirestoreBaseUrl: false, | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     $.setActionItem({ | ||||||
|  |       raw: data, | ||||||
|  |     }); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
							
								
								
									
										11
									
								
								packages/backend/src/apps/firebase/actions/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/backend/src/apps/firebase/actions/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | import createCloudFirestoreDocument from './create-cloud-firestore-document/index.js'; | ||||||
|  | import createFirebaseRealtimeDatabaseRecord from './create-firebase-realtime-database-record/index.js'; | ||||||
|  | import findCloudFirestoreDocument from './find-cloud-firestore-document/index.js'; | ||||||
|  | import findFirebaseRealtimeDatabaseRecord from './find-firebase-realtime-database-record/index.js'; | ||||||
|  |  | ||||||
|  | export default [ | ||||||
|  |   createCloudFirestoreDocument, | ||||||
|  |   createFirebaseRealtimeDatabaseRecord, | ||||||
|  |   findCloudFirestoreDocument, | ||||||
|  |   findFirebaseRealtimeDatabaseRecord, | ||||||
|  | ]; | ||||||
							
								
								
									
										52
									
								
								packages/backend/src/apps/firebase/assets/favicon.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								packages/backend/src/apps/firebase/assets/favicon.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| After Width: | Height: | Size: 21 KiB | 
							
								
								
									
										23
									
								
								packages/backend/src/apps/firebase/auth/generate-auth-url.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								packages/backend/src/apps/firebase/auth/generate-auth-url.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | import { URLSearchParams } from 'url'; | ||||||
|  | import authScope from '../common/auth-scope.js'; | ||||||
|  |  | ||||||
|  | export default async function generateAuthUrl($) { | ||||||
|  |   const oauthRedirectUrlField = $.app.auth.fields.find( | ||||||
|  |     (field) => field.key == 'oAuthRedirectUrl' | ||||||
|  |   ); | ||||||
|  |   const redirectUri = oauthRedirectUrlField.value; | ||||||
|  |   const searchParams = new URLSearchParams({ | ||||||
|  |     client_id: $.auth.data.clientId, | ||||||
|  |     redirect_uri: redirectUri, | ||||||
|  |     prompt: 'select_account', | ||||||
|  |     scope: authScope.join(' '), | ||||||
|  |     response_type: 'code', | ||||||
|  |     access_type: 'offline', | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   const url = `https://accounts.google.com/o/oauth2/v2/auth?${searchParams.toString()}`; | ||||||
|  |  | ||||||
|  |   await $.auth.set({ | ||||||
|  |     url, | ||||||
|  |   }); | ||||||
|  | } | ||||||
							
								
								
									
										71
									
								
								packages/backend/src/apps/firebase/auth/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								packages/backend/src/apps/firebase/auth/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | import generateAuthUrl from './generate-auth-url.js'; | ||||||
|  | import verifyCredentials from './verify-credentials.js'; | ||||||
|  | import refreshToken from './refresh-token.js'; | ||||||
|  | import isStillVerified from './is-still-verified.js'; | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   fields: [ | ||||||
|  |     { | ||||||
|  |       key: 'oAuthRedirectUrl', | ||||||
|  |       label: 'OAuth Redirect URL', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       readOnly: true, | ||||||
|  |       value: '{WEB_APP_URL}/app/firebase/connections/add', | ||||||
|  |       placeholder: null, | ||||||
|  |       description: | ||||||
|  |         'When asked to input a redirect URL in Google Cloud, enter the URL above.', | ||||||
|  |       clickToCopy: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       key: 'clientId', | ||||||
|  |       label: 'Client ID', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       readOnly: false, | ||||||
|  |       value: null, | ||||||
|  |       placeholder: null, | ||||||
|  |       description: null, | ||||||
|  |       clickToCopy: false, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       key: 'clientSecret', | ||||||
|  |       label: 'Client Secret', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       readOnly: false, | ||||||
|  |       value: null, | ||||||
|  |       placeholder: null, | ||||||
|  |       description: null, | ||||||
|  |       clickToCopy: false, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       key: 'projectId', | ||||||
|  |       label: 'Project ID', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       readOnly: false, | ||||||
|  |       value: null, | ||||||
|  |       placeholder: null, | ||||||
|  |       description: 'The project id of your Firebase project', | ||||||
|  |       clickToCopy: false, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       key: 'realtimeDatabaseId', | ||||||
|  |       label: 'Realtime Database Domain', | ||||||
|  |       type: 'string', | ||||||
|  |       required: false, | ||||||
|  |       readOnly: false, | ||||||
|  |       value: null, | ||||||
|  |       placeholder: null, | ||||||
|  |       description: | ||||||
|  |         'If you want to use Realtime Database, please provide the domain of your Realtime Database (https://{{domain}}.firebaseio.com)', | ||||||
|  |       clickToCopy: false, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   generateAuthUrl, | ||||||
|  |   verifyCredentials, | ||||||
|  |   isStillVerified, | ||||||
|  |   refreshToken, | ||||||
|  | }; | ||||||
| @@ -0,0 +1,8 @@ | |||||||
|  | import getCurrentUser from '../common/get-current-user.js'; | ||||||
|  |  | ||||||
|  | const isStillVerified = async ($) => { | ||||||
|  |   const currentUser = await getCurrentUser($); | ||||||
|  |   return !!currentUser.resourceName; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default isStillVerified; | ||||||
							
								
								
									
										31
									
								
								packages/backend/src/apps/firebase/auth/refresh-token.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								packages/backend/src/apps/firebase/auth/refresh-token.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | import { URLSearchParams } from 'node:url'; | ||||||
|  |  | ||||||
|  | import authScope from '../common/auth-scope.js'; | ||||||
|  |  | ||||||
|  | const refreshToken = async ($) => { | ||||||
|  |   const params = new URLSearchParams({ | ||||||
|  |     client_id: $.auth.data.clientId, | ||||||
|  |     client_secret: $.auth.data.clientSecret, | ||||||
|  |     grant_type: 'refresh_token', | ||||||
|  |     refresh_token: $.auth.data.refreshToken, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   const { data } = await $.http.post( | ||||||
|  |     'https://oauth2.googleapis.com/token', | ||||||
|  |     params.toString(), | ||||||
|  |     { | ||||||
|  |       additionalProperties: { | ||||||
|  |         skipAddingAuthHeader: true, | ||||||
|  |       }, | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   await $.auth.set({ | ||||||
|  |     accessToken: data.access_token, | ||||||
|  |     expiresIn: data.expires_in, | ||||||
|  |     scope: authScope.join(' '), | ||||||
|  |     tokenType: data.token_type, | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default refreshToken; | ||||||
| @@ -0,0 +1,50 @@ | |||||||
|  | import getCurrentUser from '../common/get-current-user.js'; | ||||||
|  |  | ||||||
|  | const verifyCredentials = async ($) => { | ||||||
|  |   const oauthRedirectUrlField = $.app.auth.fields.find( | ||||||
|  |     (field) => field.key == 'oAuthRedirectUrl' | ||||||
|  |   ); | ||||||
|  |   const redirectUri = oauthRedirectUrlField.value; | ||||||
|  |   const { data } = await $.http.post( | ||||||
|  |     `https://oauth2.googleapis.com/token`, | ||||||
|  |     { | ||||||
|  |       client_id: $.auth.data.clientId, | ||||||
|  |       client_secret: $.auth.data.clientSecret, | ||||||
|  |       code: $.auth.data.code, | ||||||
|  |       grant_type: 'authorization_code', | ||||||
|  |       redirect_uri: redirectUri, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       additionalProperties: { | ||||||
|  |         skipAddingAuthHeader: true, | ||||||
|  |       }, | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   await $.auth.set({ | ||||||
|  |     accessToken: data.access_token, | ||||||
|  |     tokenType: data.token_type, | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   const currentUser = await getCurrentUser($); | ||||||
|  |  | ||||||
|  |   const { displayName } = currentUser.names.find( | ||||||
|  |     (name) => name.metadata.primary | ||||||
|  |   ); | ||||||
|  |   const { value: email } = currentUser.emailAddresses.find( | ||||||
|  |     (emailAddress) => emailAddress.metadata.primary | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   await $.auth.set({ | ||||||
|  |     clientId: $.auth.data.clientId, | ||||||
|  |     clientSecret: $.auth.data.clientSecret, | ||||||
|  |     scope: $.auth.data.scope, | ||||||
|  |     idToken: data.id_token, | ||||||
|  |     expiresIn: data.expires_in, | ||||||
|  |     refreshToken: data.refresh_token, | ||||||
|  |     resourceName: currentUser.resourceName, | ||||||
|  |     screenName: `${displayName} - ${email}`, | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default verifyCredentials; | ||||||
| @@ -0,0 +1,9 @@ | |||||||
|  | const addAuthHeader = ($, requestConfig) => { | ||||||
|  |   if ($.auth.data?.accessToken) { | ||||||
|  |     requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return requestConfig; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default addAuthHeader; | ||||||
							
								
								
									
										9
									
								
								packages/backend/src/apps/firebase/common/auth-scope.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/backend/src/apps/firebase/common/auth-scope.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | const authScope = [ | ||||||
|  |   'https://www.googleapis.com/auth/datastore', | ||||||
|  |   'https://www.googleapis.com/auth/firebase.database', | ||||||
|  |   'https://www.googleapis.com/auth/datastore', | ||||||
|  |   'https://www.googleapis.com/auth/userinfo.email', | ||||||
|  |   'https://www.googleapis.com/auth/userinfo.profile', | ||||||
|  | ]; | ||||||
|  |  | ||||||
|  | export default authScope; | ||||||
| @@ -0,0 +1,13 @@ | |||||||
|  | const getCurrentUser = async ($) => { | ||||||
|  |   const { data: currentUser } = await $.http.get( | ||||||
|  |     'https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses', | ||||||
|  |     { | ||||||
|  |       additionalProperties: { | ||||||
|  |         skipAddingAuthHeader: true, | ||||||
|  |       }, | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |   return currentUser; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default getCurrentUser; | ||||||
							
								
								
									
										16
									
								
								packages/backend/src/apps/firebase/common/set-base-url.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								packages/backend/src/apps/firebase/common/set-base-url.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | const setBaseUrl = ($, requestConfig) => { | ||||||
|  |   const realtimeDatabaseId = $.auth.data.realtimeDatabaseId; | ||||||
|  |  | ||||||
|  |   if (requestConfig.additionalProperties?.skipAddingAuthHeader) | ||||||
|  |     return requestConfig; | ||||||
|  |  | ||||||
|  |   if (requestConfig.additionalProperties?.setFirestoreBaseUrl) { | ||||||
|  |     requestConfig.baseURL = 'https://firestore.googleapis.com'; | ||||||
|  |   } else { | ||||||
|  |     requestConfig.baseURL = `https://${realtimeDatabaseId}.firebaseio.com`; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return requestConfig; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default setBaseUrl; | ||||||
							
								
								
									
										3
									
								
								packages/backend/src/apps/firebase/dynamic-data/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								packages/backend/src/apps/firebase/dynamic-data/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | import listFirestoreCollections from './list-firestore-collections/index.js'; | ||||||
|  |  | ||||||
|  | export default [listFirestoreCollections]; | ||||||
| @@ -0,0 +1,32 @@ | |||||||
|  | export default { | ||||||
|  |   name: 'List firestore collections', | ||||||
|  |   key: 'listFirestoreCollections', | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const firestoreCollections = { | ||||||
|  |       data: [], | ||||||
|  |     }; | ||||||
|  |     const projectId = $.auth.data.projectId; | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.post( | ||||||
|  |       `/v1/projects/${projectId}/databases/(default)/documents:listCollectionIds`, | ||||||
|  |       null, | ||||||
|  |       { | ||||||
|  |         additionalProperties: { | ||||||
|  |           setFirestoreBaseUrl: true, | ||||||
|  |         }, | ||||||
|  |       } | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     if (data.collectionIds?.length) { | ||||||
|  |       for (const collectionId of data.collectionIds) { | ||||||
|  |         firestoreCollections.data.push({ | ||||||
|  |           value: collectionId, | ||||||
|  |           name: collectionId, | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return firestoreCollections; | ||||||
|  |   }, | ||||||
|  | }; | ||||||
							
								
								
									
										23
									
								
								packages/backend/src/apps/firebase/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								packages/backend/src/apps/firebase/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | import defineApp from '../../helpers/define-app.js'; | ||||||
|  | import addAuthHeader from './common/add-auth-header.js'; | ||||||
|  | import auth from './auth/index.js'; | ||||||
|  | import setBaseUrl from './common/set-base-url.js'; | ||||||
|  | import triggers from './triggers/index.js'; | ||||||
|  | import dynamicData from './dynamic-data/index.js'; | ||||||
|  | import actions from './actions/index.js'; | ||||||
|  |  | ||||||
|  | export default defineApp({ | ||||||
|  |   name: 'Firebase', | ||||||
|  |   key: 'firebase', | ||||||
|  |   baseUrl: 'https://firebase.google.com', | ||||||
|  |   apiBaseUrl: '', | ||||||
|  |   iconUrl: '{BASE_URL}/apps/firebase/assets/favicon.svg', | ||||||
|  |   authDocUrl: 'https://automatisch.io/docs/apps/firebase/connection', | ||||||
|  |   primaryColor: 'FFA000', | ||||||
|  |   supportsConnections: true, | ||||||
|  |   beforeRequest: [setBaseUrl, addAuthHeader], | ||||||
|  |   auth, | ||||||
|  |   triggers, | ||||||
|  |   dynamicData, | ||||||
|  |   actions, | ||||||
|  | }); | ||||||
							
								
								
									
										7
									
								
								packages/backend/src/apps/firebase/triggers/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/backend/src/apps/firebase/triggers/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | import newChildObjectInFirebaseRealtimeDatabase from './new-child-object-in-a-firebase-realtime-database/index.js'; | ||||||
|  | import newDocumentsWithinFirestoreCollection from './new-documents-within-firestore-collection/index.js'; | ||||||
|  |  | ||||||
|  | export default [ | ||||||
|  |   newChildObjectInFirebaseRealtimeDatabase, | ||||||
|  |   newDocumentsWithinFirestoreCollection, | ||||||
|  | ]; | ||||||
| @@ -0,0 +1,75 @@ | |||||||
|  | import Crypto from 'crypto'; | ||||||
|  | import defineTrigger from '../../../../helpers/define-trigger.js'; | ||||||
|  |  | ||||||
|  | export default defineTrigger({ | ||||||
|  |   name: 'New child object in a firebase realtime database', | ||||||
|  |   key: 'newChildObjectInFirebaseRealtimeDatabase', | ||||||
|  |   pollInterval: 15, | ||||||
|  |   description: | ||||||
|  |     'Triggers when a new child object is generated within a specific path.', | ||||||
|  |   arguments: [ | ||||||
|  |     { | ||||||
|  |       label: 'Path', | ||||||
|  |       key: 'path', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       description: | ||||||
|  |         "Indicate the path to the key of the object where the child objects to be queried are located, for example, 'foo/bar/here.json'.", | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Order', | ||||||
|  |       key: 'order', | ||||||
|  |       type: 'string', | ||||||
|  |       required: false, | ||||||
|  |       description: | ||||||
|  |         'The key or path of the child that should be utilized for comparing objects/records. If unspecified, the order of $key is used.', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Location of newest objects', | ||||||
|  |       key: 'locationOfNewestObjects', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: false, | ||||||
|  |       description: | ||||||
|  |         'Specifies whether the new 100 records are positioned at the "top" or the "bottom" of the ordering. If left unspecified, the assumption is that the bottom/last result represents the "newest objects" (limitToLast).', | ||||||
|  |       variables: true, | ||||||
|  |       options: [ | ||||||
|  |         { label: 'Top of results', value: 'limitToFirst' }, | ||||||
|  |         { label: 'Bottom of results', value: 'limitToLast' }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const { path, order, locationOfNewestObjects } = $.step.parameters; | ||||||
|  |  | ||||||
|  |     const params = {}; | ||||||
|  |  | ||||||
|  |     if (order) { | ||||||
|  |       params.orderBy = `"${order}"`; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (locationOfNewestObjects) { | ||||||
|  |       params[`${locationOfNewestObjects}`] = 100; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.get(path, { | ||||||
|  |       params, | ||||||
|  |       additionalProperties: { | ||||||
|  |         setFirestoreBaseUrl: false, | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     if (!data) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $.pushTriggerItem({ | ||||||
|  |       raw: data, | ||||||
|  |       meta: { | ||||||
|  |         internalId: Crypto.randomUUID(), | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
| @@ -0,0 +1,66 @@ | |||||||
|  | import defineTrigger from '../../../../helpers/define-trigger.js'; | ||||||
|  |  | ||||||
|  | export default defineTrigger({ | ||||||
|  |   name: 'New documents within a firestore collection', | ||||||
|  |   key: 'newDocumentsWithinFirestoreCollection', | ||||||
|  |   pollInterval: 15, | ||||||
|  |   description: | ||||||
|  |     'Triggers when a new document is added within a Cloud Firestore collection.', | ||||||
|  |   arguments: [ | ||||||
|  |     { | ||||||
|  |       label: 'Collection', | ||||||
|  |       key: 'collectionId', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: true, | ||||||
|  |       description: '', | ||||||
|  |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|  |           { | ||||||
|  |             name: 'key', | ||||||
|  |             value: 'listFirestoreCollections', | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const projectId = $.auth.data.projectId; | ||||||
|  |     const collectionId = $.step.parameters.collectionId; | ||||||
|  |     const params = { | ||||||
|  |       pageSize: 100, | ||||||
|  |       pageToken: undefined, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     do { | ||||||
|  |       const { data } = await $.http.get( | ||||||
|  |         `/v1/projects/${projectId}/databases/(default)/documents/${collectionId}`, | ||||||
|  |         { | ||||||
|  |           params, | ||||||
|  |           additionalProperties: { | ||||||
|  |             setFirestoreBaseUrl: true, | ||||||
|  |           }, | ||||||
|  |         } | ||||||
|  |       ); | ||||||
|  |       params.pageToken = data.nextPageToken; | ||||||
|  |  | ||||||
|  |       if (!data?.documents?.length) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       for (const document of data.documents) { | ||||||
|  |         const nameParts = document.name.split('/'); | ||||||
|  |         const id = nameParts[nameParts.length - 1]; | ||||||
|  |         $.pushTriggerItem({ | ||||||
|  |           raw: document, | ||||||
|  |           meta: { | ||||||
|  |             internalId: id, | ||||||
|  |           }, | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } while (params.pageToken); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
| @@ -1,4 +1,3 @@ | |||||||
| import FormData from 'form-data'; |  | ||||||
| import defineAction from '../../../../helpers/define-action.js'; | import defineAction from '../../../../helpers/define-action.js'; | ||||||
|  |  | ||||||
| export default defineAction({ | export default defineAction({ | ||||||
| @@ -6,45 +5,51 @@ export default defineAction({ | |||||||
|   key: 'newChat', |   key: 'newChat', | ||||||
|   description: 'Create a new chat session for Helix AI.', |   description: 'Create a new chat session for Helix AI.', | ||||||
|   arguments: [ |   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', |       label: 'Input', | ||||||
|       key: 'input', |       key: 'input', | ||||||
|       type: 'string', |       type: 'string', | ||||||
|       required: true, |       required: true, | ||||||
|       description: 'Prompt to start the chat with.', |       description: 'User input to start the chat with.', | ||||||
|       variables: true, |       variables: true, | ||||||
|     }, |     }, | ||||||
|   ], |   ], | ||||||
|  |  | ||||||
|   async run($) { |   async run($) { | ||||||
|     const formData = new FormData(); |     const response = await $.http.post('/api/v1/sessions/chat', { | ||||||
|     formData.append('input', $.step.parameters.input); |       session_id: $.step.parameters.sessionId, | ||||||
|     formData.append('mode', 'inference'); |       system: $.step.parameters.systemPrompt, | ||||||
|     formData.append('type', 'text'); |       messages: [ | ||||||
|  |         { | ||||||
|     const sessionResponse = await $.http.post('/api/v1/sessions', formData, { |           role: 'user', | ||||||
|       headers: { |           content: { | ||||||
|         ...formData.getHeaders(), |             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({ |     $.setActionItem({ | ||||||
|           raw: message, |       raw: response.data, | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|         chatGenerated = true; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   }, |   }, | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -90,7 +90,7 @@ export default defineAction({ | |||||||
|  |  | ||||||
|   async run($) { |   async run($) { | ||||||
|     const method = $.step.parameters.method; |     const method = $.step.parameters.method; | ||||||
|     const data = $.step.parameters.data; |     const data = $.step.parameters.data || null; | ||||||
|     const url = $.step.parameters.url; |     const url = $.step.parameters.url; | ||||||
|     const headers = $.step.parameters.headers; |     const headers = $.step.parameters.headers; | ||||||
|  |  | ||||||
| @@ -108,14 +108,17 @@ export default defineAction({ | |||||||
|       return result; |       return result; | ||||||
|     }, {}); |     }, {}); | ||||||
|  |  | ||||||
|     let contentType = headersObject['content-type']; |     let expectedResponseContentType = headersObject.accept; | ||||||
|  |  | ||||||
|     // in case HEAD request is not supported by the URL |     // in case HEAD request is not supported by the URL | ||||||
|     try { |     try { | ||||||
|       const metadataResponse = await $.http.head(url, { |       const metadataResponse = await $.http.head(url, { | ||||||
|         headers: headersObject, |         headers: headersObject, | ||||||
|       }); |       }); | ||||||
|       contentType = metadataResponse.headers['content-type']; |  | ||||||
|  |       if (!expectedResponseContentType) { | ||||||
|  |         expectedResponseContentType = metadataResponse.headers['content-type']; | ||||||
|  |       } | ||||||
|  |  | ||||||
|       throwIfFileSizeExceedsLimit(metadataResponse.headers['content-length']); |       throwIfFileSizeExceedsLimit(metadataResponse.headers['content-length']); | ||||||
|       // eslint-disable-next-line no-empty |       // eslint-disable-next-line no-empty | ||||||
| @@ -128,7 +131,7 @@ export default defineAction({ | |||||||
|       headers: headersObject, |       headers: headersObject, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     if (!isPossiblyTextBased(contentType)) { |     if (!isPossiblyTextBased(expectedResponseContentType)) { | ||||||
|       requestData.responseType = 'arraybuffer'; |       requestData.responseType = 'arraybuffer'; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -138,7 +141,7 @@ export default defineAction({ | |||||||
|  |  | ||||||
|     let responseData = response.data; |     let responseData = response.data; | ||||||
|  |  | ||||||
|     if (!isPossiblyTextBased(contentType)) { |     if (!isPossiblyTextBased(expectedResponseContentType)) { | ||||||
|       responseData = Buffer.from(responseData).toString('base64'); |       responseData = Buffer.from(responseData).toString('base64'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -64,33 +64,18 @@ export default defineAction({ | |||||||
|       value: '1', |       value: '1', | ||||||
|       description: |       description: | ||||||
|         'The ID of the stage this deal will be added to. If omitted, the deal will be placed in the first stage of the default pipeline.', |         'The ID of the stage this deal will be added to. If omitted, the deal will be placed in the first stage of the default pipeline.', | ||||||
|       options: [ |       variables: true, | ||||||
|  |       source: { | ||||||
|  |         type: 'query', | ||||||
|  |         name: 'getDynamicData', | ||||||
|  |         arguments: [ | ||||||
|           { |           { | ||||||
|           label: 'Qualified (Pipeline)', |             name: 'key', | ||||||
|           value: 1, |             value: 'listStages', | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           label: 'Contact Made (Pipeline)', |  | ||||||
|           value: 2, |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           label: 'Prospect Qualified (Pipeline)', |  | ||||||
|           value: 3, |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           label: 'Needs Defined (Pipeline)', |  | ||||||
|           value: 4, |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           label: 'Proposal Made (Pipeline)', |  | ||||||
|           value: 5, |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           label: 'Negotiations Started (Pipeline)', |  | ||||||
|           value: 6, |  | ||||||
|           }, |           }, | ||||||
|         ], |         ], | ||||||
|       }, |       }, | ||||||
|  |     }, | ||||||
|     { |     { | ||||||
|       label: 'Owner', |       label: 'Owner', | ||||||
|       key: 'userId', |       key: 'userId', | ||||||
|   | |||||||
| @@ -1,23 +1,25 @@ | |||||||
| import listActivityTypes from './list-activity-types/index.js'; | import listActivityTypes from './list-activity-types/index.js'; | ||||||
| import listCurrencies from './list-currencies/index.js'; | import listCurrencies from './list-currencies/index.js'; | ||||||
| import listDeals from './list-deals/index.js'; | import listDeals from './list-deals/index.js'; | ||||||
| import listLeads from './list-leads/index.js'; |  | ||||||
| import listLeadLabels from './list-lead-labels/index.js'; | import listLeadLabels from './list-lead-labels/index.js'; | ||||||
| import listOrganizations from './list-organizations/index.js'; | import listLeads from './list-leads/index.js'; | ||||||
| import listOrganizationLabelField from './list-organization-label-field/index.js'; | import listOrganizationLabelField from './list-organization-label-field/index.js'; | ||||||
|  | import listOrganizations from './list-organizations/index.js'; | ||||||
| import listPersonLabelField from './list-person-label-field/index.js'; | import listPersonLabelField from './list-person-label-field/index.js'; | ||||||
| import listPersons from './list-persons/index.js'; | import listPersons from './list-persons/index.js'; | ||||||
|  | import listStages from './list-stages/index.js'; | ||||||
| import listUsers from './list-users/index.js'; | import listUsers from './list-users/index.js'; | ||||||
|  |  | ||||||
| export default [ | export default [ | ||||||
|   listActivityTypes, |   listActivityTypes, | ||||||
|   listCurrencies, |   listCurrencies, | ||||||
|   listDeals, |   listDeals, | ||||||
|   listLeads, |  | ||||||
|   listLeadLabels, |   listLeadLabels, | ||||||
|   listOrganizations, |   listLeads, | ||||||
|   listOrganizationLabelField, |   listOrganizationLabelField, | ||||||
|  |   listOrganizations, | ||||||
|   listPersonLabelField, |   listPersonLabelField, | ||||||
|   listPersons, |   listPersons, | ||||||
|  |   listStages, | ||||||
|   listUsers, |   listUsers, | ||||||
| ]; | ]; | ||||||
|   | |||||||
| @@ -0,0 +1,23 @@ | |||||||
|  | export default { | ||||||
|  |   name: 'List stages', | ||||||
|  |   key: 'listStages', | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const stages = { | ||||||
|  |       data: [], | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     const { data } = await $.http.get('/api/v1/stages'); | ||||||
|  |  | ||||||
|  |     if (data.data?.length) { | ||||||
|  |       for (const stage of data.data) { | ||||||
|  |         stages.data.push({ | ||||||
|  |           value: stage.id, | ||||||
|  |           name: stage.name, | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return stages; | ||||||
|  |   }, | ||||||
|  | }; | ||||||
							
								
								
									
										3
									
								
								packages/backend/src/apps/webhook/actions/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								packages/backend/src/apps/webhook/actions/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | import respondWith from './respond-with/index.js'; | ||||||
|  |  | ||||||
|  | export default [respondWith]; | ||||||
| @@ -0,0 +1,69 @@ | |||||||
|  | import defineAction from '../../../../helpers/define-action.js'; | ||||||
|  |  | ||||||
|  | export default defineAction({ | ||||||
|  |   name: 'Respond with', | ||||||
|  |   key: 'respondWith', | ||||||
|  |   description: 'Respond with defined JSON body.', | ||||||
|  |   arguments: [ | ||||||
|  |     { | ||||||
|  |       label: 'Status code', | ||||||
|  |       key: 'statusCode', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       variables: true, | ||||||
|  |       value: '200', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Headers', | ||||||
|  |       key: 'headers', | ||||||
|  |       type: 'dynamic', | ||||||
|  |       required: false, | ||||||
|  |       description: 'Add or remove headers as needed', | ||||||
|  |       fields: [ | ||||||
|  |         { | ||||||
|  |           label: 'Key', | ||||||
|  |           key: 'key', | ||||||
|  |           type: 'string', | ||||||
|  |           required: true, | ||||||
|  |           description: 'Header key', | ||||||
|  |           variables: true, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           label: 'Value', | ||||||
|  |           key: 'value', | ||||||
|  |           type: 'string', | ||||||
|  |           required: true, | ||||||
|  |           description: 'Header value', | ||||||
|  |           variables: true, | ||||||
|  |         }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Body', | ||||||
|  |       key: 'body', | ||||||
|  |       type: 'string', | ||||||
|  |       required: true, | ||||||
|  |       description: 'The content of the response body.', | ||||||
|  |       variables: true, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|  |   async run($) { | ||||||
|  |     const statusCode = parseInt($.step.parameters.statusCode, 10); | ||||||
|  |     const body = $.step.parameters.body; | ||||||
|  |     const headers = $.step.parameters.headers.reduce((result, entry) => { | ||||||
|  |       return { | ||||||
|  |         ...result, | ||||||
|  |         [entry.key]: entry.value, | ||||||
|  |       }; | ||||||
|  |     }, {}); | ||||||
|  |  | ||||||
|  |     $.setActionItem({ | ||||||
|  |       raw: { | ||||||
|  |         headers, | ||||||
|  |         body, | ||||||
|  |         statusCode, | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
| @@ -1,4 +1,5 @@ | |||||||
| import defineApp from '../../helpers/define-app.js'; | import defineApp from '../../helpers/define-app.js'; | ||||||
|  | import actions from './actions/index.js'; | ||||||
| import triggers from './triggers/index.js'; | import triggers from './triggers/index.js'; | ||||||
|  |  | ||||||
| export default defineApp({ | export default defineApp({ | ||||||
| @@ -10,5 +11,6 @@ export default defineApp({ | |||||||
|   baseUrl: '', |   baseUrl: '', | ||||||
|   apiBaseUrl: '', |   apiBaseUrl: '', | ||||||
|   primaryColor: '0059F7', |   primaryColor: '0059F7', | ||||||
|  |   actions, | ||||||
|   triggers, |   triggers, | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -7,7 +7,20 @@ export default defineTrigger({ | |||||||
|   key: 'catchRawWebhook', |   key: 'catchRawWebhook', | ||||||
|   type: 'webhook', |   type: 'webhook', | ||||||
|   showWebhookUrl: true, |   showWebhookUrl: true, | ||||||
|   description: 'Triggers when the webhook receives a request.', |   description: | ||||||
|  |     'Triggers (immediately if configured) when the webhook receives a request.', | ||||||
|  |   arguments: [ | ||||||
|  |     { | ||||||
|  |       label: 'Wait until flow is done', | ||||||
|  |       key: 'workSynchronously', | ||||||
|  |       type: 'dropdown', | ||||||
|  |       required: true, | ||||||
|  |       options: [ | ||||||
|  |         { label: 'Yes', value: true }, | ||||||
|  |         { label: 'No', value: false }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |  | ||||||
|   async run($) { |   async run($) { | ||||||
|     const dataItem = { |     const dataItem = { | ||||||
|   | |||||||
| @@ -0,0 +1,13 @@ | |||||||
|  | import User from '../../../../models/user.js'; | ||||||
|  | import { renderObject, renderError } from '../../../../helpers/renderer.js'; | ||||||
|  |  | ||||||
|  | export default async (request, response) => { | ||||||
|  |   const { email, password } = request.body; | ||||||
|  |   const token = await User.authenticate(email, password); | ||||||
|  |  | ||||||
|  |   if (token) { | ||||||
|  |     return renderObject(response, { token }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   renderError(response, [{ general: ['Incorrect email or password.'] }]); | ||||||
|  | }; | ||||||
| @@ -0,0 +1,39 @@ | |||||||
|  | import { describe, it, expect, beforeEach } from 'vitest'; | ||||||
|  | import request from 'supertest'; | ||||||
|  | import app from '../../../../app.js'; | ||||||
|  | import { createUser } from '../../../../../test/factories/user'; | ||||||
|  |  | ||||||
|  | describe('POST /api/v1/access-tokens', () => { | ||||||
|  |   beforeEach(async () => { | ||||||
|  |     await createUser({ | ||||||
|  |       email: 'user@automatisch.io', | ||||||
|  |       password: 'password', | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('should return the token data with correct credentials', async () => { | ||||||
|  |     const response = await request(app) | ||||||
|  |       .post('/api/v1/access-tokens') | ||||||
|  |       .send({ | ||||||
|  |         email: 'user@automatisch.io', | ||||||
|  |         password: 'password', | ||||||
|  |       }) | ||||||
|  |       .expect(200); | ||||||
|  |  | ||||||
|  |     expect(response.body.data.token.length).toBeGreaterThan(0); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('should return error with incorrect credentials', async () => { | ||||||
|  |     const response = await request(app) | ||||||
|  |       .post('/api/v1/access-tokens') | ||||||
|  |       .send({ | ||||||
|  |         email: 'incorrect@email.com', | ||||||
|  |         password: 'incorrectpassword', | ||||||
|  |       }) | ||||||
|  |       .expect(422); | ||||||
|  |  | ||||||
|  |     expect(response.body.errors.general).toEqual([ | ||||||
|  |       'Incorrect email or password.', | ||||||
|  |     ]); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -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({ app_key: request.params.appKey }) | ||||||
|  |     .throwIfNotFound(); | ||||||
|  |  | ||||||
|  |   renderObject(response, appAuthClient); | ||||||
|  | }; | ||||||
| @@ -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.js'; | ||||||
|  | import { createUser } from '../../../../../../test/factories/user.js'; | ||||||
|  | import { createRole } from '../../../../../../test/factories/role.js'; | ||||||
|  | import getAppAuthClientMock from '../../../../../../test/mocks/rest/api/v1/admin/apps/get-auth-client.js'; | ||||||
|  | import { createAppAuthClient } from '../../../../../../test/factories/app-auth-client.js'; | ||||||
|  | import * as license from '../../../../../helpers/license.ee.js'; | ||||||
|  |  | ||||||
|  | describe('GET /api/v1/admin/apps/:appKey/auth-clients/:appAuthClientId', () => { | ||||||
|  |   let currentUser, adminRole, currentAppAuthClient, token; | ||||||
|  |  | ||||||
|  |   beforeEach(async () => { | ||||||
|  |     vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||||
|  |  | ||||||
|  |     adminRole = await createRole({ key: 'admin' }); | ||||||
|  |     currentUser = await createUser({ roleId: adminRole.id }); | ||||||
|  |  | ||||||
|  |     currentAppAuthClient = await createAppAuthClient({ | ||||||
|  |       appKey: 'deepl', | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     token = createAuthTokenByUserId(currentUser.id); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('should return specified app auth client', async () => { | ||||||
|  |     const response = await request(app) | ||||||
|  |       .get(`/api/v1/admin/apps/deepl/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/admin/apps/deepl/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/apps/deepl/auth-clients/invalidAppAuthClientUUID') | ||||||
|  |       .set('Authorization', token) | ||||||
|  |       .expect(400); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -0,0 +1,10 @@ | |||||||
|  | import { renderObject } from '../../../../../helpers/renderer.js'; | ||||||
|  | import AppAuthClient from '../../../../../models/app-auth-client.js'; | ||||||
|  |  | ||||||
|  | export default async (request, response) => { | ||||||
|  |   const appAuthClients = await AppAuthClient.query() | ||||||
|  |     .where({ app_key: request.params.appKey }) | ||||||
|  |     .orderBy('created_at', 'desc'); | ||||||
|  |  | ||||||
|  |   renderObject(response, appAuthClients); | ||||||
|  | }; | ||||||
| @@ -0,0 +1,44 @@ | |||||||
|  | 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 { createRole } from '../../../../../../test/factories/role.js'; | ||||||
|  | import getAuthClientsMock from '../../../../../../test/mocks/rest/api/v1/admin/apps/get-auth-clients.js'; | ||||||
|  | import { createAppAuthClient } from '../../../../../../test/factories/app-auth-client.js'; | ||||||
|  | import * as license from '../../../../../helpers/license.ee.js'; | ||||||
|  |  | ||||||
|  | describe('GET /api/v1/admin/apps/:appKey/auth-clients', () => { | ||||||
|  |   let currentUser, adminRole, token; | ||||||
|  |  | ||||||
|  |   beforeEach(async () => { | ||||||
|  |     vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||||
|  |  | ||||||
|  |     adminRole = await createRole({ key: 'admin' }); | ||||||
|  |     currentUser = await createUser({ roleId: adminRole.id }); | ||||||
|  |  | ||||||
|  |     token = createAuthTokenByUserId(currentUser.id); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('should return specified app auth client info', async () => { | ||||||
|  |     const appAuthClientOne = await createAppAuthClient({ | ||||||
|  |       appKey: 'deepl', | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const appAuthClientTwo = await createAppAuthClient({ | ||||||
|  |       appKey: 'deepl', | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const response = await request(app) | ||||||
|  |       .get('/api/v1/admin/apps/deepl/auth-clients') | ||||||
|  |       .set('Authorization', token) | ||||||
|  |       .expect(200); | ||||||
|  |  | ||||||
|  |     const expectedPayload = getAuthClientsMock([ | ||||||
|  |       appAuthClientTwo, | ||||||
|  |       appAuthClientOne, | ||||||
|  |     ]); | ||||||
|  |  | ||||||
|  |     expect(response.body).toEqual(expectedPayload); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -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,14 @@ | |||||||
|  | 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(); | ||||||
|  |  | ||||||
|  |   const roleMappings = await samlAuthProvider | ||||||
|  |     .$relatedQuery('samlAuthProvidersRoleMappings') | ||||||
|  |     .orderBy('remote_role_name', 'asc'); | ||||||
|  |  | ||||||
|  |   renderObject(response, roleMappings); | ||||||
|  | }; | ||||||
| @@ -0,0 +1,51 @@ | |||||||
|  | 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 { createRoleMapping } from '../../../../../../test/factories/role-mapping.js'; | ||||||
|  | import getRoleMappingsMock from '../../../../../../test/mocks/rest/api/v1/admin/saml-auth-providers/get-role-mappings.ee.js'; | ||||||
|  | import * as license from '../../../../../helpers/license.ee.js'; | ||||||
|  |  | ||||||
|  | describe('GET /api/v1/admin/saml-auth-providers/:samlAuthProviderId/role-mappings', () => { | ||||||
|  |   let roleMappingOne, roleMappingTwo, samlAuthProvider, currentUser, token; | ||||||
|  |  | ||||||
|  |   beforeEach(async () => { | ||||||
|  |     const role = await createRole({ key: 'admin' }); | ||||||
|  |     currentUser = await createUser({ roleId: role.id }); | ||||||
|  |  | ||||||
|  |     samlAuthProvider = await createSamlAuthProvider(); | ||||||
|  |  | ||||||
|  |     roleMappingOne = await createRoleMapping({ | ||||||
|  |       samlAuthProviderId: samlAuthProvider.id, | ||||||
|  |       remoteRoleName: 'Admin', | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     roleMappingTwo = await createRoleMapping({ | ||||||
|  |       samlAuthProviderId: samlAuthProvider.id, | ||||||
|  |       remoteRoleName: 'User', | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     token = createAuthTokenByUserId(currentUser.id); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('should return role mappings', async () => { | ||||||
|  |     vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||||
|  |  | ||||||
|  |     const response = await request(app) | ||||||
|  |       .get( | ||||||
|  |         `/api/v1/admin/saml-auth-providers/${samlAuthProvider.id}/role-mappings` | ||||||
|  |       ) | ||||||
|  |       .set('Authorization', token) | ||||||
|  |       .expect(200); | ||||||
|  |  | ||||||
|  |     const expectedPayload = await getRoleMappingsMock([ | ||||||
|  |       roleMappingOne, | ||||||
|  |       roleMappingTwo, | ||||||
|  |     ]); | ||||||
|  |  | ||||||
|  |     expect(response.body).toEqual(expectedPayload); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -0,0 +1,12 @@ | |||||||
|  | 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, { | ||||||
|  |     serializer: 'AdminSamlAuthProvider', | ||||||
|  |   }); | ||||||
|  | }; | ||||||
| @@ -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,13 @@ | |||||||
|  | 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, { | ||||||
|  |     serializer: 'AdminSamlAuthProvider', | ||||||
|  |   }); | ||||||
|  | }; | ||||||
| @@ -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 { renderObject } from '../../../../../helpers/renderer.js'; | ||||||
| import User from '../../../../models/user.js'; | import User from '../../../../../models/user.js'; | ||||||
| 
 | 
 | ||||||
| export default async (request, response) => { | export default async (request, response) => { | ||||||
|   const user = await User.query() |   const user = await User.query() | ||||||
|     .leftJoinRelated({ |  | ||||||
|       role: true, |  | ||||||
|     }) |  | ||||||
|     .withGraphFetched({ |     .withGraphFetched({ | ||||||
|       role: true, |       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 { renderObject } from '../../../../../helpers/renderer.js'; | ||||||
| import User from '../../../../models/user.js'; | import User from '../../../../../models/user.js'; | ||||||
| import paginateRest from '../../../../helpers/pagination-rest.js'; | import paginateRest from '../../../../../helpers/pagination-rest.js'; | ||||||
| 
 | 
 | ||||||
| export default async (request, response) => { | export default async (request, response) => { | ||||||
|   const usersQuery = User.query() |   const usersQuery = User.query() | ||||||
|     .leftJoinRelated({ |  | ||||||
|       role: true, |  | ||||||
|     }) |  | ||||||
|     .withGraphFetched({ |     .withGraphFetched({ | ||||||
|       role: true, |       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 request from 'supertest'; | ||||||
| import app from '../../../../app'; | import app from '../../../../../app'; | ||||||
| import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id'; | import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id'; | ||||||
| import { createRole } from '../../../../../test/factories/role'; | import { createRole } from '../../../../../../test/factories/role'; | ||||||
| import { createPermission } from '../../../../../test/factories/permission'; | import { createUser } from '../../../../../../test/factories/user'; | ||||||
| import { createUser } from '../../../../../test/factories/user'; | import getUsersMock from '../../../../../../test/mocks/rest/api/v1/admin/users/get-users.js'; | ||||||
| import getUsersMock from '../../../../../test/mocks/rest/api/v1/users/get-users'; | 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; |   let currentUser, currentUserRole, anotherUser, anotherUserRole, token; | ||||||
| 
 | 
 | ||||||
|   beforeEach(async () => { |   beforeEach(async () => { | ||||||
|     currentUserRole = await createRole({ |     currentUserRole = await createRole({ key: 'admin' }); | ||||||
|       key: 'currentUser', |  | ||||||
|       name: 'Current user role', |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     await createPermission({ |  | ||||||
|       action: 'read', |  | ||||||
|       subject: 'User', |  | ||||||
|       roleId: currentUserRole.id, |  | ||||||
|     }); |  | ||||||
| 
 | 
 | ||||||
|     currentUser = await createUser({ |     currentUser = await createUser({ | ||||||
|       roleId: currentUserRole.id, |       roleId: currentUserRole.id, | ||||||
| @@ -41,8 +32,10 @@ describe('GET /api/v1/users', () => { | |||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   it('should return users data', async () => { |   it('should return users data', async () => { | ||||||
|  |     vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||||
|  | 
 | ||||||
|     const response = await request(app) |     const response = await request(app) | ||||||
|       .get('/api/v1/users') |       .get('/api/v1/admin/users') | ||||||
|       .set('Authorization', token) |       .set('Authorization', token) | ||||||
|       .expect(200); |       .expect(200); | ||||||
| 
 | 
 | ||||||
| @@ -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); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -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({ app_key: request.params.appKey, active: true }) | ||||||
|  |     .throwIfNotFound(); | ||||||
|  |  | ||||||
|  |   renderObject(response, appAuthClient); | ||||||
|  | }; | ||||||
| @@ -0,0 +1,50 @@ | |||||||
|  | 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/apps/get-auth-client.js'; | ||||||
|  | import { createAppAuthClient } from '../../../../../test/factories/app-auth-client.js'; | ||||||
|  | import * as license from '../../../../helpers/license.ee.js'; | ||||||
|  |  | ||||||
|  | describe('GET /api/v1/apps/:appKey/auth-clients/:appAuthClientId', () => { | ||||||
|  |   let currentUser, currentAppAuthClient, token; | ||||||
|  |  | ||||||
|  |   beforeEach(async () => { | ||||||
|  |     vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||||
|  |  | ||||||
|  |     currentUser = await createUser(); | ||||||
|  |     currentAppAuthClient = await createAppAuthClient({ | ||||||
|  |       appKey: 'deepl', | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     token = createAuthTokenByUserId(currentUser.id); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('should return specified app auth client', async () => { | ||||||
|  |     const response = await request(app) | ||||||
|  |       .get(`/api/v1/apps/deepl/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/apps/deepl/auth-clients/${notExistingAppAuthClientUUID}`) | ||||||
|  |       .set('Authorization', token) | ||||||
|  |       .expect(404); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('should return bad request response for invalid UUID', async () => { | ||||||
|  |     await request(app) | ||||||
|  |       .get('/api/v1/apps/deepl/auth-clients/invalidAppAuthClientUUID') | ||||||
|  |       .set('Authorization', token) | ||||||
|  |       .expect(400); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -0,0 +1,10 @@ | |||||||
|  | import { renderObject } from '../../../../helpers/renderer.js'; | ||||||
|  | import AppAuthClient from '../../../../models/app-auth-client.js'; | ||||||
|  |  | ||||||
|  | export default async (request, response) => { | ||||||
|  |   const appAuthClients = await AppAuthClient.query() | ||||||
|  |     .where({ app_key: request.params.appKey, active: true }) | ||||||
|  |     .orderBy('created_at', 'desc'); | ||||||
|  |  | ||||||
|  |   renderObject(response, appAuthClients); | ||||||
|  | }; | ||||||
| @@ -0,0 +1,42 @@ | |||||||
|  | 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 getAuthClientsMock from '../../../../../test/mocks/rest/api/v1/apps/get-auth-clients.js'; | ||||||
|  | import { createAppAuthClient } from '../../../../../test/factories/app-auth-client.js'; | ||||||
|  | import * as license from '../../../../helpers/license.ee.js'; | ||||||
|  |  | ||||||
|  | describe('GET /api/v1/apps/:appKey/auth-clients', () => { | ||||||
|  |   let currentUser, token; | ||||||
|  |  | ||||||
|  |   beforeEach(async () => { | ||||||
|  |     vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||||
|  |  | ||||||
|  |     currentUser = await createUser(); | ||||||
|  |  | ||||||
|  |     token = createAuthTokenByUserId(currentUser.id); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('should return specified app auth client info', async () => { | ||||||
|  |     const appAuthClientOne = await createAppAuthClient({ | ||||||
|  |       appKey: 'deepl', | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const appAuthClientTwo = await createAppAuthClient({ | ||||||
|  |       appKey: 'deepl', | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const response = await request(app) | ||||||
|  |       .get('/api/v1/apps/deepl/auth-clients') | ||||||
|  |       .set('Authorization', token) | ||||||
|  |       .expect(200); | ||||||
|  |  | ||||||
|  |     const expectedPayload = getAuthClientsMock([ | ||||||
|  |       appAuthClientTwo, | ||||||
|  |       appAuthClientOne, | ||||||
|  |     ]); | ||||||
|  |  | ||||||
|  |     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,15 @@ | |||||||
|  | import { renderObject } from '../../../../helpers/renderer.js'; | ||||||
|  | import AppConfig from '../../../../models/app-config.js'; | ||||||
|  |  | ||||||
|  | export default async (request, response) => { | ||||||
|  |   const appConfig = await AppConfig.query() | ||||||
|  |     .withGraphFetched({ | ||||||
|  |       appAuthClients: true, | ||||||
|  |     }) | ||||||
|  |     .findOne({ | ||||||
|  |       key: request.params.appKey, | ||||||
|  |     }) | ||||||
|  |     .throwIfNotFound(); | ||||||
|  |  | ||||||
|  |   renderObject(response, appConfig); | ||||||
|  | }; | ||||||
| @@ -0,0 +1,44 @@ | |||||||
|  | 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 getAppConfigMock from '../../../../../test/mocks/rest/api/v1/apps/get-config.js'; | ||||||
|  | import { createAppConfig } from '../../../../../test/factories/app-config.js'; | ||||||
|  | import * as license from '../../../../helpers/license.ee.js'; | ||||||
|  |  | ||||||
|  | describe('GET /api/v1/apps/:appKey/config', () => { | ||||||
|  |   let currentUser, appConfig, token; | ||||||
|  |  | ||||||
|  |   beforeEach(async () => { | ||||||
|  |     vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||||
|  |  | ||||||
|  |     currentUser = await createUser(); | ||||||
|  |  | ||||||
|  |     appConfig = await createAppConfig({ | ||||||
|  |       key: 'deepl', | ||||||
|  |       allowCustomConnection: true, | ||||||
|  |       shared: true, | ||||||
|  |       disabled: false, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     token = createAuthTokenByUserId(currentUser.id); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('should return specified app config info', async () => { | ||||||
|  |     const response = await request(app) | ||||||
|  |       .get(`/api/v1/apps/${appConfig.key}/config`) | ||||||
|  |       .set('Authorization', token) | ||||||
|  |       .expect(200); | ||||||
|  |  | ||||||
|  |     const expectedPayload = getAppConfigMock(appConfig); | ||||||
|  |     expect(response.body).toEqual(expectedPayload); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('should return not found response for not existing app key', async () => { | ||||||
|  |     await request(app) | ||||||
|  |       .get('/api/v1/apps/not-existing-app-key/config') | ||||||
|  |       .set('Authorization', token) | ||||||
|  |       .expect(404); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | import { renderObject } from '../../../../helpers/renderer.js'; | ||||||
|  | import App from '../../../../models/app.js'; | ||||||
|  |  | ||||||
|  | export default async (request, response) => { | ||||||
|  |   const app = await App.findOneByKey(request.params.appKey); | ||||||
|  |  | ||||||
|  |   const connections = await request.currentUser.authorizedConnections | ||||||
|  |     .clone() | ||||||
|  |     .select('connections.*') | ||||||
|  |     .withGraphFetched({ | ||||||
|  |       appConfig: true, | ||||||
|  |       appAuthClient: true, | ||||||
|  |     }) | ||||||
|  |     .fullOuterJoinRelated('steps') | ||||||
|  |     .where({ | ||||||
|  |       'connections.key': app.key, | ||||||
|  |       'connections.draft': false, | ||||||
|  |     }) | ||||||
|  |     .countDistinct('steps.flow_id as flowCount') | ||||||
|  |     .groupBy('connections.id') | ||||||
|  |     .orderBy('created_at', 'desc'); | ||||||
|  |  | ||||||
|  |   renderObject(response, connections); | ||||||
|  | }; | ||||||
| @@ -0,0 +1,101 @@ | |||||||
|  | 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.js'; | ||||||
|  | import { createUser } from '../../../../../test/factories/user.js'; | ||||||
|  | import { createConnection } from '../../../../../test/factories/connection.js'; | ||||||
|  | import { createPermission } from '../../../../../test/factories/permission.js'; | ||||||
|  | import getConnectionsMock from '../../../../../test/mocks/rest/api/v1/apps/get-connections.js'; | ||||||
|  |  | ||||||
|  | describe('GET /api/v1/apps/:appKey/connections', () => { | ||||||
|  |   let currentUser, currentUserRole, token; | ||||||
|  |  | ||||||
|  |   beforeEach(async () => { | ||||||
|  |     currentUser = await createUser(); | ||||||
|  |     currentUserRole = await currentUser.$relatedQuery('role'); | ||||||
|  |  | ||||||
|  |     token = createAuthTokenByUserId(currentUser.id); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('should return the connections data of specified app for current user', async () => { | ||||||
|  |     const currentUserConnectionOne = await createConnection({ | ||||||
|  |       userId: currentUser.id, | ||||||
|  |       key: 'deepl', | ||||||
|  |       draft: false, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const currentUserConnectionTwo = await createConnection({ | ||||||
|  |       userId: currentUser.id, | ||||||
|  |       key: 'deepl', | ||||||
|  |       draft: false, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     await createPermission({ | ||||||
|  |       action: 'read', | ||||||
|  |       subject: 'Connection', | ||||||
|  |       roleId: currentUserRole.id, | ||||||
|  |       conditions: ['isCreator'], | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const response = await request(app) | ||||||
|  |       .get('/api/v1/apps/deepl/connections') | ||||||
|  |       .set('Authorization', token) | ||||||
|  |       .expect(200); | ||||||
|  |  | ||||||
|  |     const expectedPayload = await getConnectionsMock([ | ||||||
|  |       currentUserConnectionTwo, | ||||||
|  |       currentUserConnectionOne, | ||||||
|  |     ]); | ||||||
|  |  | ||||||
|  |     expect(response.body).toEqual(expectedPayload); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('should return the connections data of specified app for another user', async () => { | ||||||
|  |     const anotherUser = await createUser(); | ||||||
|  |  | ||||||
|  |     const anotherUserConnectionOne = await createConnection({ | ||||||
|  |       userId: anotherUser.id, | ||||||
|  |       key: 'deepl', | ||||||
|  |       draft: false, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const anotherUserConnectionTwo = await createConnection({ | ||||||
|  |       userId: anotherUser.id, | ||||||
|  |       key: 'deepl', | ||||||
|  |       draft: false, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     await createPermission({ | ||||||
|  |       action: 'read', | ||||||
|  |       subject: 'Connection', | ||||||
|  |       roleId: currentUserRole.id, | ||||||
|  |       conditions: [], | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const response = await request(app) | ||||||
|  |       .get('/api/v1/apps/deepl/connections') | ||||||
|  |       .set('Authorization', token) | ||||||
|  |       .expect(200); | ||||||
|  |  | ||||||
|  |     const expectedPayload = await getConnectionsMock([ | ||||||
|  |       anotherUserConnectionTwo, | ||||||
|  |       anotherUserConnectionOne, | ||||||
|  |     ]); | ||||||
|  |  | ||||||
|  |     expect(response.body).toEqual(expectedPayload); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('should return not found response for invalid connection UUID', async () => { | ||||||
|  |     await createPermission({ | ||||||
|  |       action: 'update', | ||||||
|  |       subject: 'Connection', | ||||||
|  |       roleId: currentUserRole.id, | ||||||
|  |       conditions: ['isCreator'], | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     await request(app) | ||||||
|  |       .get('/api/v1/connections/invalid-connection-id/connections') | ||||||
|  |       .set('Authorization', token) | ||||||
|  |       .expect(404); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
							
								
								
									
										23
									
								
								packages/backend/src/controllers/api/v1/apps/get-flows.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								packages/backend/src/controllers/api/v1/apps/get-flows.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | import { renderObject } from '../../../../helpers/renderer.js'; | ||||||
|  | import App from '../../../../models/app.js'; | ||||||
|  | import paginateRest from '../../../../helpers/pagination-rest.js'; | ||||||
|  |  | ||||||
|  | export default async (request, response) => { | ||||||
|  |   const app = await App.findOneByKey(request.params.appKey); | ||||||
|  |  | ||||||
|  |   const flowsQuery = request.currentUser.authorizedFlows | ||||||
|  |     .clone() | ||||||
|  |     .joinRelated({ | ||||||
|  |       steps: true, | ||||||
|  |     }) | ||||||
|  |     .withGraphFetched({ | ||||||
|  |       steps: true, | ||||||
|  |     }) | ||||||
|  |     .where('steps.app_key', app.key) | ||||||
|  |     .orderBy('active', 'desc') | ||||||
|  |     .orderBy('updated_at', 'desc'); | ||||||
|  |  | ||||||
|  |   const flows = await paginateRest(flowsQuery, request.query.page); | ||||||
|  |  | ||||||
|  |   renderObject(response, flows); | ||||||
|  | }; | ||||||
							
								
								
									
										129
									
								
								packages/backend/src/controllers/api/v1/apps/get-flows.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								packages/backend/src/controllers/api/v1/apps/get-flows.test.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | |||||||
|  | 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.js'; | ||||||
|  | import { createUser } from '../../../../../test/factories/user.js'; | ||||||
|  | import { createFlow } from '../../../../../test/factories/flow.js'; | ||||||
|  | import { createStep } from '../../../../../test/factories/step.js'; | ||||||
|  | import { createPermission } from '../../../../../test/factories/permission.js'; | ||||||
|  | import getFlowsMock from '../../../../../test/mocks/rest/api/v1/flows/get-flows.js'; | ||||||
|  |  | ||||||
|  | describe('GET /api/v1/apps/:appKey/flows', () => { | ||||||
|  |   let currentUser, currentUserRole, token; | ||||||
|  |  | ||||||
|  |   beforeEach(async () => { | ||||||
|  |     currentUser = await createUser(); | ||||||
|  |     currentUserRole = await currentUser.$relatedQuery('role'); | ||||||
|  |  | ||||||
|  |     token = createAuthTokenByUserId(currentUser.id); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('should return the flows data of specified app for current user', async () => { | ||||||
|  |     const currentUserFlowOne = await createFlow({ userId: currentUser.id }); | ||||||
|  |  | ||||||
|  |     const triggerStepFlowOne = await createStep({ | ||||||
|  |       flowId: currentUserFlowOne.id, | ||||||
|  |       type: 'trigger', | ||||||
|  |       appKey: 'webhook', | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const actionStepFlowOne = await createStep({ | ||||||
|  |       flowId: currentUserFlowOne.id, | ||||||
|  |       type: 'action', | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const currentUserFlowTwo = await createFlow({ userId: currentUser.id }); | ||||||
|  |  | ||||||
|  |     await createStep({ | ||||||
|  |       flowId: currentUserFlowTwo.id, | ||||||
|  |       type: 'trigger', | ||||||
|  |       appKey: 'github', | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     await createStep({ | ||||||
|  |       flowId: currentUserFlowTwo.id, | ||||||
|  |       type: 'action', | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     await createPermission({ | ||||||
|  |       action: 'read', | ||||||
|  |       subject: 'Flow', | ||||||
|  |       roleId: currentUserRole.id, | ||||||
|  |       conditions: ['isCreator'], | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const response = await request(app) | ||||||
|  |       .get('/api/v1/apps/webhook/flows') | ||||||
|  |       .set('Authorization', token) | ||||||
|  |       .expect(200); | ||||||
|  |  | ||||||
|  |     const expectedPayload = await getFlowsMock( | ||||||
|  |       [currentUserFlowOne], | ||||||
|  |       [triggerStepFlowOne, actionStepFlowOne] | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     expect(response.body).toEqual(expectedPayload); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('should return the flows data of specified app for another user', async () => { | ||||||
|  |     const anotherUser = await createUser(); | ||||||
|  |     const anotherUserFlowOne = await createFlow({ userId: anotherUser.id }); | ||||||
|  |  | ||||||
|  |     const triggerStepFlowOne = await createStep({ | ||||||
|  |       flowId: anotherUserFlowOne.id, | ||||||
|  |       type: 'trigger', | ||||||
|  |       appKey: 'webhook', | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const actionStepFlowOne = await createStep({ | ||||||
|  |       flowId: anotherUserFlowOne.id, | ||||||
|  |       type: 'action', | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const anotherUserFlowTwo = await createFlow({ userId: anotherUser.id }); | ||||||
|  |  | ||||||
|  |     await createStep({ | ||||||
|  |       flowId: anotherUserFlowTwo.id, | ||||||
|  |       type: 'trigger', | ||||||
|  |       appKey: 'github', | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     await createStep({ | ||||||
|  |       flowId: anotherUserFlowTwo.id, | ||||||
|  |       type: 'action', | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     await createPermission({ | ||||||
|  |       action: 'read', | ||||||
|  |       subject: 'Flow', | ||||||
|  |       roleId: currentUserRole.id, | ||||||
|  |       conditions: [], | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const response = await request(app) | ||||||
|  |       .get('/api/v1/apps/webhook/flows') | ||||||
|  |       .set('Authorization', token) | ||||||
|  |       .expect(200); | ||||||
|  |  | ||||||
|  |     const expectedPayload = await getFlowsMock( | ||||||
|  |       [anotherUserFlowOne], | ||||||
|  |       [triggerStepFlowOne, actionStepFlowOne] | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     expect(response.body).toEqual(expectedPayload); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('should return not found response for invalid app key', async () => { | ||||||
|  |     await createPermission({ | ||||||
|  |       action: 'read', | ||||||
|  |       subject: 'Flow', | ||||||
|  |       roleId: currentUserRole.id, | ||||||
|  |       conditions: ['isCreator'], | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     await request(app) | ||||||
|  |       .get('/api/v1/apps/invalid-app-key/flows') | ||||||
|  |       .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); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | import appConfig from '../../../../config/app.js'; | ||||||
|  | import Config from '../../../../models/config.js'; | ||||||
|  | import { renderObject } from '../../../../helpers/renderer.js'; | ||||||
|  |  | ||||||
|  | export default async (request, response) => { | ||||||
|  |   const defaultConfig = { | ||||||
|  |     disableNotificationsPage: appConfig.disableNotificationsPage, | ||||||
|  |     disableFavicon: appConfig.disableFavicon, | ||||||
|  |     additionalDrawerLink: appConfig.additionalDrawerLink, | ||||||
|  |     additionalDrawerLinkText: appConfig.additionalDrawerLinkText, | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   let config = await Config.query().orderBy('key', 'asc'); | ||||||
|  |  | ||||||
|  |   config = config.reduce((computedConfig, configEntry) => { | ||||||
|  |     const { key, value } = configEntry; | ||||||
|  |  | ||||||
|  |     computedConfig[key] = value?.data; | ||||||
|  |  | ||||||
|  |     return computedConfig; | ||||||
|  |   }, defaultConfig); | ||||||
|  |  | ||||||
|  |   renderObject(response, config); | ||||||
|  | }; | ||||||
| @@ -0,0 +1,51 @@ | |||||||
|  | import { vi, expect, describe, it } from 'vitest'; | ||||||
|  | import request from 'supertest'; | ||||||
|  | import { createConfig } from '../../../../../test/factories/config.js'; | ||||||
|  | import app from '../../../../app.js'; | ||||||
|  | import configMock from '../../../../../test/mocks/rest/api/v1/automatisch/config.js'; | ||||||
|  | import * as license from '../../../../helpers/license.ee.js'; | ||||||
|  |  | ||||||
|  | describe('GET /api/v1/automatisch/config', () => { | ||||||
|  |   it('should return Automatisch config', async () => { | ||||||
|  |     vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); | ||||||
|  |  | ||||||
|  |     const logoConfig = await createConfig({ | ||||||
|  |       key: 'logo.svgData', | ||||||
|  |       value: { data: '<svg>Sample</svg>' }, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const primaryDarkConfig = await createConfig({ | ||||||
|  |       key: 'palette.primary.dark', | ||||||
|  |       value: { data: '#001F52' }, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const primaryLightConfig = await createConfig({ | ||||||
|  |       key: 'palette.primary.light', | ||||||
|  |       value: { data: '#4286FF' }, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const primaryMainConfig = await createConfig({ | ||||||
|  |       key: 'palette.primary.main', | ||||||
|  |       value: { data: '#0059F7' }, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const titleConfig = await createConfig({ | ||||||
|  |       key: 'title', | ||||||
|  |       value: { data: 'Sample Title' }, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const response = await request(app) | ||||||
|  |       .get('/api/v1/automatisch/config') | ||||||
|  |       .expect(200); | ||||||
|  |  | ||||||
|  |     const expectedPayload = configMock( | ||||||
|  |       logoConfig, | ||||||
|  |       primaryDarkConfig, | ||||||
|  |       primaryLightConfig, | ||||||
|  |       primaryMainConfig, | ||||||
|  |       titleConfig | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     expect(response.body).toEqual(expectedPayload); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
							
								
								
									
										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,14 @@ | |||||||
|  | import { renderObject } from '../../../../helpers/renderer.js'; | ||||||
|  |  | ||||||
|  | export default async (request, response) => { | ||||||
|  |   let connection = await request.currentUser.authorizedConnections | ||||||
|  |     .clone() | ||||||
|  |     .findOne({ | ||||||
|  |       id: request.params.connectionId, | ||||||
|  |     }) | ||||||
|  |     .throwIfNotFound(); | ||||||
|  |  | ||||||
|  |   connection = await connection.testAndUpdateConnection(); | ||||||
|  |  | ||||||
|  |   renderObject(response, connection); | ||||||
|  | }; | ||||||
| @@ -0,0 +1,123 @@ | |||||||
|  | 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.js'; | ||||||
|  | import { createUser } from '../../../../../test/factories/user.js'; | ||||||
|  | import { createConnection } from '../../../../../test/factories/connection.js'; | ||||||
|  | import { createPermission } from '../../../../../test/factories/permission.js'; | ||||||
|  |  | ||||||
|  | describe('POST /api/v1/connections/:connectionId/test', () => { | ||||||
|  |   let currentUser, currentUserRole, token; | ||||||
|  |  | ||||||
|  |   beforeEach(async () => { | ||||||
|  |     currentUser = await createUser(); | ||||||
|  |     currentUserRole = await currentUser.$relatedQuery('role'); | ||||||
|  |  | ||||||
|  |     token = createAuthTokenByUserId(currentUser.id); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('should update the connection as not verified for current user', async () => { | ||||||
|  |     const currentUserConnection = await createConnection({ | ||||||
|  |       userId: currentUser.id, | ||||||
|  |       key: 'deepl', | ||||||
|  |       verified: true, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     await createPermission({ | ||||||
|  |       action: 'read', | ||||||
|  |       subject: 'Connection', | ||||||
|  |       roleId: currentUserRole.id, | ||||||
|  |       conditions: ['isCreator'], | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     await createPermission({ | ||||||
|  |       action: 'update', | ||||||
|  |       subject: 'Connection', | ||||||
|  |       roleId: currentUserRole.id, | ||||||
|  |       conditions: ['isCreator'], | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const response = await request(app) | ||||||
|  |       .post(`/api/v1/connections/${currentUserConnection.id}/test`) | ||||||
|  |       .set('Authorization', token) | ||||||
|  |       .expect(200); | ||||||
|  |  | ||||||
|  |     expect(response.body.data.verified).toEqual(false); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('should update the connection as not verified for another user', async () => { | ||||||
|  |     const anotherUser = await createUser(); | ||||||
|  |  | ||||||
|  |     const anotherUserConnection = await createConnection({ | ||||||
|  |       userId: anotherUser.id, | ||||||
|  |       key: 'deepl', | ||||||
|  |       verified: true, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     await createPermission({ | ||||||
|  |       action: 'read', | ||||||
|  |       subject: 'Connection', | ||||||
|  |       roleId: currentUserRole.id, | ||||||
|  |       conditions: [], | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     await createPermission({ | ||||||
|  |       action: 'update', | ||||||
|  |       subject: 'Connection', | ||||||
|  |       roleId: currentUserRole.id, | ||||||
|  |       conditions: [], | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     const response = await request(app) | ||||||
|  |       .post(`/api/v1/connections/${anotherUserConnection.id}/test`) | ||||||
|  |       .set('Authorization', token) | ||||||
|  |       .expect(200); | ||||||
|  |  | ||||||
|  |     expect(response.body.data.verified).toEqual(false); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('should return not found response for not existing connection UUID', async () => { | ||||||
|  |     const notExistingConnectionUUID = Crypto.randomUUID(); | ||||||
|  |  | ||||||
|  |     await createPermission({ | ||||||
|  |       action: 'read', | ||||||
|  |       subject: 'Connection', | ||||||
|  |       roleId: currentUserRole.id, | ||||||
|  |       conditions: ['isCreator'], | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     await createPermission({ | ||||||
|  |       action: 'update', | ||||||
|  |       subject: 'Connection', | ||||||
|  |       roleId: currentUserRole.id, | ||||||
|  |       conditions: ['isCreator'], | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     await request(app) | ||||||
|  |       .post(`/api/v1/connections/${notExistingConnectionUUID}/test`) | ||||||
|  |       .set('Authorization', token) | ||||||
|  |       .expect(404); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('should return bad request response for invalid UUID', async () => { | ||||||
|  |     await createPermission({ | ||||||
|  |       action: 'read', | ||||||
|  |       subject: 'Connection', | ||||||
|  |       roleId: currentUserRole.id, | ||||||
|  |       conditions: ['isCreator'], | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     await createPermission({ | ||||||
|  |       action: 'update', | ||||||
|  |       subject: 'Connection', | ||||||
|  |       roleId: currentUserRole.id, | ||||||
|  |       conditions: ['isCreator'], | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     await request(app) | ||||||
|  |       .post('/api/v1/connections/invalidConnectionUUID/test') | ||||||
|  |       .set('Authorization', token) | ||||||
|  |       .expect(400); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user