Compare commits
	
		
			275 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| d06f6a4407 | |||
| a50d72f2f5 | |||
| 4723d9738e | |||
| 1a478bd784 | |||
| 284cb0f8b3 | |||
| 6e63c57936 | |||
| 30b61db2c1 | |||
| 8237d5f210 | |||
| 03e0a29096 | |||
| a6afba93e2 | |||
| a41758cd9c | |||
| d6755ed134 | |||
| 599c75fc00 | |||
| bb213f001e | |||
| 5415cd38a7 | |||
| 175ba52ffa | |||
| 5c5000a218 | |||
| d559d04031 | |||
| 2af682d1dd | |||
| 30905e481c | |||
| 752d405bda | |||
| 8fa4ed7c33 | |||
| c4201e9a68 | |||
| 78dcad0857 | |||
| 93e0cdf577 | |||
| 6efcd94726 | |||
| 2e271bcd52 | |||
| ebde8c6ffd | |||
| a3639dd89b | |||
| 0a43f1bb5b | |||
| 8c6fdb2239 | |||
| c0d5af5d7a | |||
| 4008a5ee72 | |||
| 07bf28b144 | |||
| 6764bf80ea | |||
| b3a73b25e8 | |||
| bda1f971d1 | |||
| 765ef84903 | |||
| 296ba8ddab | |||
| 6eff243803 | |||
| 0f4c8b2051 | |||
| d842c14240 | |||
| a54cb287a4 | |||
| 74d334f9b7 | |||
| cd3cd81360 | |||
| cf48c00ddb | |||
| 3192365793 | |||
| 075d484f11 | |||
| 5082b1b8b1 | |||
| 50dd703a1b | |||
| 057a8ee699 | |||
| 8d9418635d | |||
| f2832a2dae | |||
| 0d21596e2b | |||
| 245827e9c6 | |||
| 4608a36df6 | |||
| cb1305aa77 | |||
| 12a9ae2493 | |||
| b9fe9f1c24 | |||
| b25b0db760 | |||
| fe59e3a557 | |||
| 42c23a5883 | |||
| 6ee5328dbc | |||
| 6f39ac42da | |||
| 301f334674 | |||
| fcee3909f4 | |||
| f0e20e4130 | |||
| 80de188565 | |||
| 2f305e127c | |||
| 513d7f6fba | |||
| 244da61892 | |||
| 2a72aea10e | |||
| 71ebce6f8e | |||
| f60025b6de | |||
| 0fa663a341 | |||
| 538622aa18 | |||
| 86a21dbfa4 | |||
| 1e9e24d99d | |||
| 4493c0e3d9 | |||
| f5d48fc638 | |||
| b35a2dd2fa | |||
| a28ffe06e5 | |||
| d873674819 | |||
| 37b2ac974b | |||
| 81aed1de40 | |||
| 0f0c3c7214 | |||
| 3909ed34f7 | |||
| b2ac70e0ae | |||
| 5f17e7f783 | |||
| a5a56a263a | |||
| 2d8f7528d9 | |||
| 9581185b24 | |||
| 2905884c02 | |||
| e9914e317b | |||
| 702070da66 | |||
| cc89ba8afb | |||
| 7c4ff42a3b | |||
| 8007117434 | |||
| 23fa78eb9d | |||
| 3b3e68900b | |||
| 3ff666fd3e | |||
| 4e4435010f | |||
| de9af5a909 | |||
| ac631f0af4 | |||
| 6bbdd5bb04 | |||
| a8fc755840 | |||
| 27e74e824c | |||
| b5c0a288ac | |||
| 85dc3444ac | |||
| d02743984d | |||
| 734c826fac | |||
| 33b25c9743 | |||
| 6275aaa326 | |||
| 2a94bfa622 | |||
| a64f6c9822 | |||
| 93d43b7684 | |||
| 16ce0a8480 | |||
| 9a8d618ae4 | |||
| 38da2d3318 | |||
| 068deb4960 | |||
| 13f093bb61 | |||
| 6289f30740 | |||
| 6ff764bc34 | |||
| ea87cc793b | |||
| 92517e3653 | |||
| ffee887ddf | |||
| 3bac75e7ab | |||
| d05eddcae1 | |||
| d5c689d693 | |||
| 8fedd4ef3b | |||
| e8b2e6f261 | |||
| 39f3b0e01f | |||
| edaf255e8f | |||
| 41c4ed4d0f | |||
| f2bd88aadf | |||
| 67a3661448 | |||
| 0c763a2dfd | |||
| a7297ff933 | |||
| 4cdba8bc77 | |||
| 77c6303014 | |||
| 2b641faa29 | |||
| 9fa8b93c08 | |||
| 4b676bc853 | |||
| 4433ddb1e1 | |||
| 39aa7598b7 | |||
| 19a290c3a9 | |||
| 9bc80aac8a | |||
| e184673963 | |||
| 68cd746a9f | |||
| 69651d9f6c | |||
| 6fd246f43c | |||
| ae14d6c74f | |||
| 2fa56b82d1 | |||
| 9cc66eebdf | |||
| 4c10e20b91 | |||
| 9217421221 | |||
| 4570845b3e | |||
| 0e78951300 | |||
| 6ad56b3126 | |||
| d95c6d3365 | |||
| 1f2c8abb22 | |||
| a6d5693ccd | |||
| 31b258b4ce | |||
| f19f2808d8 | |||
| 3b9cd2e1bb | |||
| 95320ca1bc | |||
| f2d127fc98 | |||
| eb526fb57f | |||
| 348fe52c42 | |||
| eef0fa6952 | |||
| 8a82e059b7 | |||
| 2229cdf20d | |||
| 3220b194d4 | |||
| 278c4a6a41 | |||
| ec50ac31c4 | |||
| a2f0d814fc | |||
| 6468b35708 | |||
| 3558e99090 | |||
| 520608aef0 | |||
| 6df5f634f3 | |||
| da266a8dd6 | |||
| 8ae4b85827 | |||
| 8fe3243693 | |||
| 49b174f29f | |||
| 30c6d3d8db | |||
| 6c14ed9c89 | |||
| 01ed51489e | |||
| 0636616dad | |||
| 34dbaaafe0 | |||
| b4c31ee9b5 | |||
| 99307423c5 | |||
| 71542bc388 | |||
| d64f470b60 | |||
| b8fbb72fa0 | |||
| 0c61ff457d | |||
| 1d82f65b0d | |||
| 610988ec16 | |||
| 6e236ede14 | |||
| b7ad5d3a31 | |||
| a694ad225c | |||
| 5633e85f41 | |||
| 95e1eec313 | |||
| 377d5dadb2 | |||
| 4a294b1e17 | |||
| 720774fcf4 | |||
| dcdbdd15ac | |||
| 132b48cf2a | |||
| 23bd432c5f | |||
| 71b33ab05b | |||
| 87f444c30d | |||
| 4a73eab134 | |||
| f8baca5ab2 | |||
| 10221b9f2e | |||
| 1d8c8c8e9c | |||
| 4603a84f16 | |||
| 2cd8f3f7f3 | |||
| 107eeeae7f | |||
| b8767b8bd4 | |||
| bf686e89e0 | |||
| 6163f0a90b | |||
| 8f0f795a70 | |||
| 22cae39bd3 | |||
| 0b07a53ed2 | |||
| d4a02e7db2 | |||
| b9a7dc84f0 | |||
| 7111068361 | |||
| 63964fbf2c | |||
| cbcb829fbd | |||
| 057ae0d797 | |||
| 257f320ee3 | |||
| 7b15c2d88b | |||
| 988f17a795 | |||
| 4471e57438 | |||
| 51daf969cf | |||
| cb71fcd13b | |||
| a6a526dc5d | |||
| dd6d799c84 | |||
| e89e07d0fc | |||
| c28843c405 | |||
| 4834a6698b | |||
| 69afd4d587 | |||
| 24d152fdc8 | |||
| 4279e43743 | |||
| d837654617 | |||
| 0767943721 | |||
| ca87774767 | |||
| f693f2cde9 | |||
| d70c5b1bbc | |||
| 71e3d0efe2 | |||
| b517dff8a8 | |||
| 114c246ace | |||
| d7703c9e07 | |||
| dc3071f7d2 | |||
| 5fb355f450 | |||
| 33c13de32c | |||
| 1be073a4fa | |||
| b0d8249452 | |||
| 7af883f271 | |||
| f5433076b0 | |||
| 6aafe4a6ae | |||
| bdeeb03645 | |||
| 675c8762e8 | |||
| 89e392473c | |||
| 6c9b91d75a | |||
| 8c00aefd6c | |||
| 3afd785a54 | |||
| 8099999e2c | |||
| a139554e05 | |||
| 0290b0e5f5 | |||
| 0f7fa990d4 | |||
| 2f568c9cb8 | |||
| 1cb2dc9d53 | |||
| 6005b0661f | |||
| 5a36c8dcae | |||
| 58f4d2151f | 
							
								
								
									
										188
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										188
									
								
								.drone.yml
									
									
									
									
									
								
							| @@ -1,188 +0,0 @@ | ||||
| --- | ||||
| kind: secret | ||||
| name: docker_username | ||||
| get: | ||||
|   path: odit-registry-builder | ||||
|   name: username | ||||
|  | ||||
| --- | ||||
| kind: secret | ||||
| name: docker_password | ||||
| get: | ||||
|   path: odit-registry-builder | ||||
|   name: password | ||||
|  | ||||
| --- | ||||
| kind: secret | ||||
| name: git_ssh | ||||
| get: | ||||
|   path: odit-git-bot | ||||
|   name: sshkey | ||||
|  | ||||
| --- | ||||
| kind: pipeline | ||||
| type: kubernetes | ||||
| name: tests:node_latest | ||||
| clone: | ||||
|   disable: true | ||||
| steps: | ||||
|   - name: checkout pr | ||||
|     image: alpine/git | ||||
|     commands: | ||||
|       - git clone $DRONE_REMOTE_URL . | ||||
|       - git checkout $DRONE_SOURCE_BRANCH | ||||
|   - name: run tests | ||||
|     image: node:latest | ||||
|     commands: | ||||
|       - yarn | ||||
|       - yarn test:ci | ||||
| trigger: | ||||
|   event: | ||||
|     - pull_request | ||||
|  | ||||
| --- | ||||
| kind: pipeline | ||||
| type: kubernetes | ||||
| name: build:dev | ||||
| clone: | ||||
|   disable: true | ||||
|  | ||||
| steps: | ||||
|   - name: clone | ||||
|     image: alpine/git | ||||
|     commands: | ||||
|       - git clone $DRONE_REMOTE_URL . | ||||
|       - git checkout dev | ||||
|   - name: build dev | ||||
|     image: plugins/docker | ||||
|     depends_on: [clone] | ||||
|     settings: | ||||
|       username: | ||||
|         from_secret: docker_username | ||||
|       password: | ||||
|         from_secret: docker_password | ||||
|       repo: registry.odit.services/lfk/backend | ||||
|       tags: | ||||
|         - dev | ||||
|       registry: registry.odit.services | ||||
|       mtu: 1000 | ||||
|   - name: run changelog export | ||||
|     depends_on: ["clone"] | ||||
|     image: node:latest | ||||
|     commands: | ||||
|       - npx auto-changelog --commit-limit false -p -u --hide-credit | ||||
|   - name: push new changelog to repo | ||||
|     depends_on: ["run changelog export"] | ||||
|     image: appleboy/drone-git-push | ||||
|     settings: | ||||
|       branch: dev | ||||
|       commit: true | ||||
|       commit_message: 🧾New changelog file version [CI SKIP] [skip ci] | ||||
|       author_email: bot@odit.services | ||||
|       remote: git@git.odit.services:lfk/backend.git | ||||
|       ssh_key: | ||||
|         from_secret: git_ssh | ||||
|   - name: run full license export | ||||
|     depends_on: ["clone"] | ||||
|     image: node:14.15.1-alpine3.12 | ||||
|     commands: | ||||
|       - yarn | ||||
|       - yarn licenses:export | ||||
|   - name: push new licenses file to repo | ||||
|     depends_on: ["run full license export"] | ||||
|     image: appleboy/drone-git-push | ||||
|     settings: | ||||
|       branch: dev | ||||
|       commit: true | ||||
|       commit_message: 📖New license file version [CI SKIP] [skip ci] | ||||
|       author_email: bot@odit.services | ||||
|       remote: git@git.odit.services:lfk/backend.git | ||||
|       skip_verify: true | ||||
|       ssh_key: | ||||
|         from_secret: git_ssh | ||||
|  | ||||
|  | ||||
| trigger: | ||||
|   branch: | ||||
|     - dev | ||||
|   event: | ||||
|     - push | ||||
|  | ||||
| --- | ||||
| kind: pipeline | ||||
| type: kubernetes | ||||
| name: build:latest | ||||
| clone: | ||||
|   disable: true | ||||
|  | ||||
| steps: | ||||
|   - name: clone | ||||
|     image: alpine/git | ||||
|     commands: | ||||
|       - git clone $DRONE_REMOTE_URL . | ||||
|       - git checkout dev | ||||
|       - git merge main | ||||
|       - git checkout main | ||||
|   - name: build latest | ||||
|     depends_on: ["clone"] | ||||
|     image: plugins/docker | ||||
|     settings: | ||||
|       username: | ||||
|         from_secret: docker_username | ||||
|       password: | ||||
|         from_secret: docker_password | ||||
|       repo: registry.odit.services/lfk/backend | ||||
|       tags: | ||||
|         - latest | ||||
|       registry: registry.odit.services | ||||
|       mtu: 1000 | ||||
|   - name: push merge to repo | ||||
|     depends_on: ["clone"] | ||||
|     image: appleboy/drone-git-push | ||||
|     settings: | ||||
|       branch: dev | ||||
|       commit: false | ||||
|       remote: git@git.odit.services:lfk/backend.git | ||||
|       ssh_key: | ||||
|         from_secret: git_ssh | ||||
|  | ||||
| trigger: | ||||
|   branch: | ||||
|     - main | ||||
|   event: | ||||
|     - push | ||||
|  | ||||
| --- | ||||
| kind: pipeline | ||||
| type: kubernetes | ||||
| name: build:tags | ||||
|  | ||||
| steps: | ||||
|   - name: build $DRONE_TAG | ||||
|     image: plugins/docker | ||||
|     depends_on: [clone] | ||||
|     settings: | ||||
|       username: | ||||
|         from_secret: docker_username | ||||
|       password: | ||||
|         from_secret: docker_password | ||||
|       repo: registry.odit.services/lfk/backend | ||||
|       tags: | ||||
|         - '${DRONE_TAG}' | ||||
|       registry: registry.odit.services | ||||
|       mtu: 1000 | ||||
|   - name: trigger node lib build | ||||
|     image: idcooldi/drone-webhook | ||||
|     settings: | ||||
|       urls: https://ci.odit.services/api/repos/lfk/lfk-client-node/builds?SOURCE_TAG=${DRONE_TAG} | ||||
|       bearer: | ||||
|         from_secret: BOT_DRONE_KEY | ||||
|   - name: trigger js lib build | ||||
|     image: idcooldi/drone-webhook | ||||
|     settings: | ||||
|       urls: https://ci.odit.services/api/repos/lfk/lfk-client-js/builds?SOURCE_TAG=${DRONE_TAG} | ||||
|       bearer: | ||||
|         from_secret: BOT_DRONE_KEY | ||||
| trigger: | ||||
|   event: | ||||
|   - tag | ||||
| @@ -7,4 +7,5 @@ DB_PASSWORD=bla | ||||
| DB_NAME=./test.sqlite | ||||
| NODE_ENV=production | ||||
| POSTALCODE_COUNTRYCODE=DE | ||||
| SEED_TEST_DATA=false | ||||
| SEED_TEST_DATA=false | ||||
| SELFSERVICE_URL=bla | ||||
							
								
								
									
										33
									
								
								.gitea/workflows/release.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								.gitea/workflows/release.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| name: Build release images | ||||
| on: | ||||
|   push: | ||||
|     tags: | ||||
|       - "*.*.*" | ||||
|  | ||||
| jobs: | ||||
|   build-container: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v4 | ||||
|       - name: Set up Node.js | ||||
|         uses: actions/setup-node@v4 | ||||
|         with: | ||||
|           node-version: 19 | ||||
|       - run: npm i -g pnpm@10.7 && pnpm i | ||||
|       - run: pnpm licenses:export | ||||
|       - name: Login to registry | ||||
|         uses: docker/login-action@v3 | ||||
|         with: | ||||
|           registry: registry.odit.services | ||||
|           username: ${{ vars.REGISTRY_USERNAME }} | ||||
|           password: ${{ secrets.REGISTRY_PASSWORD }} | ||||
|       - name: Set up Docker Buildx | ||||
|         uses: docker/setup-buildx-action@v3 | ||||
|       - name: Build and push | ||||
|         uses: docker/build-push-action@v6 | ||||
|         with: | ||||
|           push: true | ||||
|           tags: | | ||||
|             ${{ vars.REGISTRY }}/lfk/backend:${{ github.ref_name }} | ||||
|           platforms: linux/amd64,linux/arm64 | ||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -135,4 +135,4 @@ build | ||||
| /docs | ||||
| lib | ||||
| /oss-attribution | ||||
| *.tmp | ||||
| *.tmp | ||||
|   | ||||
							
								
								
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							| @@ -9,8 +9,7 @@ | ||||
|     "[typescript]": { | ||||
|         "editor.defaultFormatter": "vscode.typescript-language-features", | ||||
|         "editor.codeActionsOnSave": { | ||||
|             "source.organizeImports": true, | ||||
|             // "source.fixAll": true | ||||
|             "source.organizeImports": "explicit" | ||||
|         } | ||||
|     }, | ||||
|     "javascript.preferences.quoteStyle": "single", | ||||
|   | ||||
							
								
								
									
										502
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										502
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -2,8 +2,501 @@ | ||||
|  | ||||
| All notable changes to this project will be documented in this file. Dates are displayed in UTC. | ||||
|  | ||||
| #### [1.3.11](https://git.odit.services/lfk/backend/compare/1.3.10...1.3.11) | ||||
|  | ||||
| - feat(RunnerController): add selfservice_links parameter to getRunners method [`a50d72f`](https://git.odit.services/lfk/backend/commit/a50d72f2f5281b8c28ca64a0970161a35a7af95a) | ||||
|  | ||||
| #### [1.3.10](https://git.odit.services/lfk/backend/compare/1.3.9...1.3.10) | ||||
|  | ||||
| > 11 April 2025 | ||||
|  | ||||
| - chore(release): 1.3.10 [`4723d97`](https://git.odit.services/lfk/backend/commit/4723d9738eacd63fb41f23c628fbe4181bd126de) | ||||
| - feat(RunnerController.getAll): debug created_via query param filter [`1a478bd`](https://git.odit.services/lfk/backend/commit/1a478bd784e01b9d5a1c6635d1004a9535c9a0e9) | ||||
|  | ||||
| #### [1.3.9](https://git.odit.services/lfk/backend/compare/1.3.8...1.3.9) | ||||
|  | ||||
| > 9 April 2025 | ||||
|  | ||||
| - feat(RunnerController.getAll): add created_via query param filter [`6e63c57`](https://git.odit.services/lfk/backend/commit/6e63c57936f06a29da5f1a94b1141d51b75df5f0) | ||||
| - chore(release): 1.3.9 [`284cb0f`](https://git.odit.services/lfk/backend/commit/284cb0f8b3955d0d65c2b36d2ec427a39752ffe7) | ||||
|  | ||||
| #### [1.3.8](https://git.odit.services/lfk/backend/compare/1.3.7...1.3.8) | ||||
|  | ||||
| > 9 April 2025 | ||||
|  | ||||
| - feat(RunnerCardController): putByCode [`8237d5f`](https://git.odit.services/lfk/backend/commit/8237d5f21067c0872a7eff7c8d1506edf44ec10c) | ||||
| - chore(release): 1.3.8 [`30b61db`](https://git.odit.services/lfk/backend/commit/30b61db2c160c019bac381f26cefdc6524ea465e) | ||||
|  | ||||
| #### [1.3.7](https://git.odit.services/lfk/backend/compare/1.3.6...1.3.7) | ||||
|  | ||||
| > 8 April 2025 | ||||
|  | ||||
| - feat(stats): Publish runners by kiosk stat [`a6afba9`](https://git.odit.services/lfk/backend/commit/a6afba93e243ca419c282a16cad023d06d864e0e) | ||||
| - chore(release): 1.3.7 [`03e0a29`](https://git.odit.services/lfk/backend/commit/03e0a290965648579956ac1f8e8542c97a667ed8) | ||||
|  | ||||
| #### [1.3.6](https://git.odit.services/lfk/backend/compare/1.3.5...1.3.6) | ||||
|  | ||||
| > 8 April 2025 | ||||
|  | ||||
| - chore(release): 1.3.6 [`a41758c`](https://git.odit.services/lfk/backend/commit/a41758cd9c83105c3a4b407744bafe2f0f6fb48a) | ||||
| - feat(runners): Allow created via being set via api [`d6755ed`](https://git.odit.services/lfk/backend/commit/d6755ed134071df635bc9d5821ceb2396c0f1d22) | ||||
| - fix(participant): Switch to correct type [`599c75f`](https://git.odit.services/lfk/backend/commit/599c75fc00217eaec3cc87c0de50d059bdde685f) | ||||
|  | ||||
| #### [1.3.5](https://git.odit.services/lfk/backend/compare/1.3.4...1.3.5) | ||||
|  | ||||
| > 8 April 2025 | ||||
|  | ||||
| - feat(runners): Generate selfservice urls on runner if requested or create/update/get single [`5415cd3`](https://git.odit.services/lfk/backend/commit/5415cd38a727e76632a01a4d2634a1777df5542c) | ||||
| - chore(release): 1.3.5 [`bb213f0`](https://git.odit.services/lfk/backend/commit/bb213f001eff2157abf8741128f624f9cc991afe) | ||||
|  | ||||
| #### [1.3.4](https://git.odit.services/lfk/backend/compare/1.3.3...1.3.4) | ||||
|  | ||||
| > 28 March 2025 | ||||
|  | ||||
| - feat: add runnersViaSelfservice to statsControllerGet [`5c5000a`](https://git.odit.services/lfk/backend/commit/5c5000a218b47815e6846ac8b857dcd1995bfa6f) | ||||
| - chore(release): 1.3.4 [`175ba52`](https://git.odit.services/lfk/backend/commit/175ba52ffae8e6ba1fdc1603ac2f5eba15602046) | ||||
|  | ||||
| #### [1.3.3](https://git.odit.services/lfk/backend/compare/v1.3.2...1.3.3) | ||||
|  | ||||
| > 28 March 2025 | ||||
|  | ||||
| - chore(release): 1.3.3 [`d559d04`](https://git.odit.services/lfk/backend/commit/d559d0403191c703fd6da0e3f3dab53eec9258c0) | ||||
| - ci: remove "v" prefix from tags [`2af682d`](https://git.odit.services/lfk/backend/commit/2af682d1dd09df496eb9f3a9111c50c0c4117356) | ||||
|  | ||||
| #### [v1.3.2](https://git.odit.services/lfk/backend/compare/v1.3.1...v1.3.2) | ||||
|  | ||||
| > 28 March 2025 | ||||
|  | ||||
| - chore(release): v1.3.2 [`30905e4`](https://git.odit.services/lfk/backend/commit/30905e481c69cfe62b4261544b4277de3a1a43c2) | ||||
| - ci: pnpm@10.7 [`752d405`](https://git.odit.services/lfk/backend/commit/752d405bda9129f3cd288a956d5444cab316c2af) | ||||
|  | ||||
| #### [v1.3.1](https://git.odit.services/lfk/backend/compare/1.3.0...v1.3.1) | ||||
|  | ||||
| > 28 March 2025 | ||||
|  | ||||
| - fix: TypeError: Cannot read properties of undefined (reading 'filter') - when trying to delete a org/team with runners [`#210`](https://git.odit.services/lfk/backend/issues/210) | ||||
| - pnpm@10.7, node@23, argon->@node-rs/argon2 [`78dcad0`](https://git.odit.services/lfk/backend/commit/78dcad085794c93829499dd550a786c38d6186f5) | ||||
| - chore(release): v1.3.1 [`8fa4ed7`](https://git.odit.services/lfk/backend/commit/8fa4ed7c3319c3e56a71701ba266ceda64d2ef69) | ||||
|  | ||||
| #### [1.3.0](https://git.odit.services/lfk/backend/compare/1.2.1...1.3.0) | ||||
|  | ||||
| > 28 March 2025 | ||||
|  | ||||
| - feat: created_via for tracking how runners got into the system [`#212`](https://git.odit.services/lfk/backend/pull/212) | ||||
| - feat: created_via for tracking how runners got into the system (#212) [`#211`](https://git.odit.services/lfk/backend/issues/211) | ||||
| - ci: move to gitea workflows [`ebde8c6`](https://git.odit.services/lfk/backend/commit/ebde8c6ffd8b17c6752da8c4d8eb3095105f6132) | ||||
| - chore(release): v1.3.0 [`93e0cdf`](https://git.odit.services/lfk/backend/commit/93e0cdf577654898b2d63790d91598c458a2db59) | ||||
| - build: docker "AS" casing [`0a43f1b`](https://git.odit.services/lfk/backend/commit/0a43f1bb5b26d3acb0d4d91648473f0dc55e8637) | ||||
| - ci: change release commit message [`6efcd94`](https://git.odit.services/lfk/backend/commit/6efcd94726957b8c527820f1a9b0130151ce22f1) | ||||
| - refactor(RunnerController.remove): only load necessary relations [`8c6fdb2`](https://git.odit.services/lfk/backend/commit/8c6fdb22390218e385780fadb3bdaf32148ac054) | ||||
| - refactor(RunnerTeamController.remove): only load necessary relations [`c0d5af5`](https://git.odit.services/lfk/backend/commit/c0d5af5d7ab44cfdf19014e0d774fb560d08f6d7) | ||||
| - fix: add .created_via to ResponseParticipant constructor [`2e271bc`](https://git.odit.services/lfk/backend/commit/2e271bcd52f02ab7449cd15916b0afc86e8b0a90) | ||||
|  | ||||
| #### [1.2.1](https://git.odit.services/lfk/backend/compare/1.2.0...1.2.1) | ||||
|  | ||||
| > 11 December 2024 | ||||
|  | ||||
| - refactor: allow selfservice link every 30s [`07bf28b`](https://git.odit.services/lfk/backend/commit/07bf28b14458849930748ce041fb65e572759482) | ||||
| - chore(release): 1.2.1 [`4008a5e`](https://git.odit.services/lfk/backend/commit/4008a5ee720b212bac9cba64417058bf4526060b) | ||||
|  | ||||
| #### [1.2.0](https://git.odit.services/lfk/backend/compare/v1.1.4...1.2.0) | ||||
|  | ||||
| > 11 December 2024 | ||||
|  | ||||
| - refactor: move to new mailer [`0f4c8b2`](https://git.odit.services/lfk/backend/commit/0f4c8b2051cae17fbdd7e02017ad5b41c61e210c) | ||||
| - refactor(ci): Switch to new woodpecker [`b3a73b2`](https://git.odit.services/lfk/backend/commit/b3a73b25e80a0466ff83e43481271fc0cd499a0d) | ||||
| - feat: middlename [`6eff243`](https://git.odit.services/lfk/backend/commit/6eff2438035b368eb45931fad9402a6cb942b350) | ||||
| - SELFSERVICE_URL [`765ef84`](https://git.odit.services/lfk/backend/commit/765ef849035ca4f8b2253bb76d15be8e9a3e6763) | ||||
| - FRONTEND_URL env [`296ba8d`](https://git.odit.services/lfk/backend/commit/296ba8ddab1dba46f8201829d9a7e5fc1c88c0f8) | ||||
| - chore: update readme [`d842c14`](https://git.odit.services/lfk/backend/commit/d842c14240fb4a7f70c66143bbe877f8168ef6d4) | ||||
| - chore(release): 1.2.0 [`6764bf8`](https://git.odit.services/lfk/backend/commit/6764bf80eac832d186e688319d8a959543a1495f) | ||||
| - Merge pull request 'refactor: move to new mailer' (#209) from refactor/new-mailer into dev [`bda1f97`](https://git.odit.services/lfk/backend/commit/bda1f971d1a14ea403439533c7ae31280c7df167) | ||||
|  | ||||
| #### [v1.1.4](https://git.odit.services/lfk/backend/compare/v1.1.3...v1.1.4) | ||||
|  | ||||
| > 20 November 2024 | ||||
|  | ||||
| - build: package lock [`50dd703`](https://git.odit.services/lfk/backend/commit/50dd703a1bd276a607cc10a087c7e90fd880847a) | ||||
| - fix(deps): Bump sqlite3 [`cd3cd81`](https://git.odit.services/lfk/backend/commit/cd3cd81360777e8bc4d78e861354e58c8da79cc7) | ||||
| - feat(ci)!: Switch to woodpecker [`3192365`](https://git.odit.services/lfk/backend/commit/3192365793fae59f2b89e3231db298654f0a28e9) | ||||
| - fix(deps): Bumped argon2 to latest version for arm support [`cf48c00`](https://git.odit.services/lfk/backend/commit/cf48c00ddb2ac33263549876928db50ae152c12d) | ||||
| - fix: updated README for pnpm, typos [`5082b1b`](https://git.odit.services/lfk/backend/commit/5082b1b8b1c0ae9e8ffa9c71c4d7923fd9223c87) | ||||
| - 🚀Bumped version to v1.1.4 [`a54cb28`](https://git.odit.services/lfk/backend/commit/a54cb287a4323ac8de77f51711cc6c52ec290859) | ||||
| - ci: drop lfk-client-node [`075d484`](https://git.odit.services/lfk/backend/commit/075d484f1169bfc5c5b68cb9712116b0e270b471) | ||||
| - fix(dependencies): Switch back to previous class-validator version to produce a working build [`74d334f`](https://git.odit.services/lfk/backend/commit/74d334f9b747a77115bd9b97729ef1120822e128) | ||||
|  | ||||
| #### [v1.1.3](https://git.odit.services/lfk/backend/compare/v1.1.2...v1.1.3) | ||||
|  | ||||
| > 10 May 2023 | ||||
|  | ||||
| - 🚀Bumped version to v1.1.3 [`057a8ee`](https://git.odit.services/lfk/backend/commit/057a8ee699d08c0e4a80cb50a8820f819569c9ac) | ||||
| - feat(orgs): Also resolve child-teams' distances and add them to org total [`8d94186`](https://git.odit.services/lfk/backend/commit/8d9418635d3e381c0f55a2521a3334ba497c169a) | ||||
| - fix(orgs): Removed unused log [`f2832a2`](https://git.odit.services/lfk/backend/commit/f2832a2daecc7bc7bbee4d4fceeab8db194730cf) | ||||
|  | ||||
| #### [v1.1.2](https://git.odit.services/lfk/backend/compare/v1.1.1...v1.1.2) | ||||
|  | ||||
| > 10 May 2023 | ||||
|  | ||||
| - 🚀Bumped version to v1.1.2 [`0d21596`](https://git.odit.services/lfk/backend/commit/0d21596e2b64a99258d4925ae2ad627d5cdbd984) | ||||
| - feat(groups): Resolve the total group distance on group get single (aka get org and get team) [`245827e`](https://git.odit.services/lfk/backend/commit/245827e9c659cf76183dc33ab253becc22ddf032) | ||||
| - chore(package): Formatting [`4608a36`](https://git.odit.services/lfk/backend/commit/4608a36df6b187520ca0c331b8dce615205257be) | ||||
|  | ||||
| #### [v1.1.1](https://git.odit.services/lfk/backend/compare/v1.1.0...v1.1.1) | ||||
|  | ||||
| > 19 April 2023 | ||||
|  | ||||
| - feat(donors): Resolve donations with donors via pagination [`12a9ae2`](https://git.odit.services/lfk/backend/commit/12a9ae24933117acb3ff9815a7d72abca5eea7a7) | ||||
| - 🚀Bumped version to v1.1.1 [`cb1305a`](https://git.odit.services/lfk/backend/commit/cb1305aa77c36aa9d7900f09e7413bc6d45f2c89) | ||||
|  | ||||
| #### [v1.1.0](https://git.odit.services/lfk/backend/compare/v1.0.1...v1.1.0) | ||||
|  | ||||
| > 19 April 2023 | ||||
|  | ||||
| - feat(stats): Added donation count and donor count to stats [`6f39ac4`](https://git.odit.services/lfk/backend/commit/6f39ac42dafc2a589bbb2256b0417f3e774ae174) | ||||
| - 🚀Bumped version to v1.1.0 [`b9fe9f1`](https://git.odit.services/lfk/backend/commit/b9fe9f1c24653b91255a6dbbdc32c30b1b411eeb) | ||||
| - Added average donation per distance to stats [`fe59e3a`](https://git.odit.services/lfk/backend/commit/fe59e3a557903cf555d4c50098e935c49ca1fac4) | ||||
| - Added hints [`b25b0db`](https://git.odit.services/lfk/backend/commit/b25b0db76071ef8d50cc60e950a399dc060a2a9f) | ||||
| - Added calls to controller [`6ee5328`](https://git.odit.services/lfk/backend/commit/6ee5328dbc404603d19db3a5173ae4def560a9c9) | ||||
| - Formatting [`42c23a5`](https://git.odit.services/lfk/backend/commit/42c23a5883dacda4e0147842d448b3ad35b197b1) | ||||
|  | ||||
| #### [v1.0.1](https://git.odit.services/lfk/backend/compare/v1.0.0...v1.0.1) | ||||
|  | ||||
| > 18 April 2023 | ||||
|  | ||||
| - fix(pagination) page=0 resulted in false thx JS [`fcee390`](https://git.odit.services/lfk/backend/commit/fcee3909f4c4664115cc7ecb94f30e0dd8e78ce0) | ||||
| - 🚀Bumped version to v1.0.1 [`301f334`](https://git.odit.services/lfk/backend/commit/301f33467489a8533bdac11fbd10efd1b791f5e3) | ||||
|  | ||||
| ### [v1.0.0](https://git.odit.services/lfk/backend/compare/v0.15.4...v1.0.0) | ||||
|  | ||||
| > 18 April 2023 | ||||
|  | ||||
| - 🚀Bumped version to v1.0.0 [`f0e20e4`](https://git.odit.services/lfk/backend/commit/f0e20e413014fe446c97754d2765cdad92c2cc3b) | ||||
| - Merge pull request 'feature/205-pagination' (#206) from feature/205-pagination into dev [`80de188`](https://git.odit.services/lfk/backend/commit/80de188565523d642407612272432ef07672b890) | ||||
| - Added pagination for runner orgs [`538622a`](https://git.odit.services/lfk/backend/commit/538622aa1841e27256f304e15b4204c2f6d24d76) | ||||
| - RunnerTeam Pagination [`0fa663a`](https://git.odit.services/lfk/backend/commit/0fa663a34104d438dd8fc9ab02458fdf289329f8) | ||||
| - users pagination [`244da61`](https://git.odit.services/lfk/backend/commit/244da618926377f58bb12dbbd89b7bb39d84596e) | ||||
| - Track pagination [`2a72aea`](https://git.odit.services/lfk/backend/commit/2a72aea10ef940fbdd4a9e6137b22933fdec7734) | ||||
| - usergroup pagination [`513d7f6`](https://git.odit.services/lfk/backend/commit/513d7f6fbaebe39beab6ec95e6e42eb10c62296d) | ||||
| - statsclient pagination [`71ebce6`](https://git.odit.services/lfk/backend/commit/71ebce6f8eebf110bb973a53b91dd6a49e1def99) | ||||
| - scanstation pagination [`f60025b`](https://git.odit.services/lfk/backend/commit/f60025b6de79b0f5f89995bf59260194f5de9af0) | ||||
| - Get all pagination for permissions [`86a21db`](https://git.odit.services/lfk/backend/commit/86a21dbfa4b50d8e80c611ea6e3eabfc2b8ae365) | ||||
| - Pagination for group contacts [`1e9e24d`](https://git.odit.services/lfk/backend/commit/1e9e24d99d75ce6dc846ff662e62c886646ea974) | ||||
| - Added pagination for get all donors [`4493c0e`](https://git.odit.services/lfk/backend/commit/4493c0e3d9beebbf7f601b39e1a2579771b4d152) | ||||
| - Added pagination for donations [`f5d48fc`](https://git.odit.services/lfk/backend/commit/f5d48fc638080c9333efe474d86f131794c809af) | ||||
| - Added pagination for runnercards [`b35a2dd`](https://git.odit.services/lfk/backend/commit/b35a2dd2fab708253373b3326f11ab574be18371) | ||||
| - Added pagination for runners [`d873674`](https://git.odit.services/lfk/backend/commit/d873674819e6cb33cf89da4f8fdc30a0b41707e4) | ||||
| - Added pagination for get all scans [`37b2ac9`](https://git.odit.services/lfk/backend/commit/37b2ac974b2276efd13538c127ba5ddda2537fe3) | ||||
| - Updated test for attribute [`2f305e1`](https://git.odit.services/lfk/backend/commit/2f305e127c75e9e6ff8e9fc0cfc10cc3db44759d) | ||||
| - Formatting [`a28ffe0`](https://git.odit.services/lfk/backend/commit/a28ffe06e5f3f69e4af6fdf0c66c9a1dfda10cfa) | ||||
|  | ||||
| #### [v0.15.4](https://git.odit.services/lfk/backend/compare/v0.15.3...v0.15.4) | ||||
|  | ||||
| > 15 April 2023 | ||||
|  | ||||
| - Fixed possible null [`0f0c3c7`](https://git.odit.services/lfk/backend/commit/0f0c3c7214f357d991518aafd015ffc4d387ce59) | ||||
| - 🚀Bumped version to v0.15.4 [`81aed1d`](https://git.odit.services/lfk/backend/commit/81aed1de40166f4cefabdb478d7638017127b25c) | ||||
|  | ||||
| #### [v0.15.3](https://git.odit.services/lfk/backend/compare/v0.15.2...v0.15.3) | ||||
|  | ||||
| > 15 April 2023 | ||||
|  | ||||
| - Faster stats (not including donations) [`b2ac70e`](https://git.odit.services/lfk/backend/commit/b2ac70e0aec1064e54a5043a104e7892984b2338) | ||||
| - 🚀Bumped version to v0.15.3 [`3909ed3`](https://git.odit.services/lfk/backend/commit/3909ed34f739e9fee90828f16757c75da90bab0f) | ||||
|  | ||||
| #### [v0.15.2](https://git.odit.services/lfk/backend/compare/v0.15.1...v0.15.2) | ||||
|  | ||||
| > 15 April 2023 | ||||
|  | ||||
| - 🚀Bumped version to v0.15.2 [`5f17e7f`](https://git.odit.services/lfk/backend/commit/5f17e7f783a7e8e2efc8f7dbbf2c98bcd1d80240) | ||||
| - Don't resolve runner group and parten with get all card requests [`2d8f752`](https://git.odit.services/lfk/backend/commit/2d8f7528d98144832e7609f5aa6fac8de4723c4a) | ||||
| - Resolve groups again for card generation [`a5a56a2`](https://git.odit.services/lfk/backend/commit/a5a56a263a01dbd911a799ab57084166e17b80ac) | ||||
|  | ||||
| #### [v0.15.1](https://git.odit.services/lfk/backend/compare/v0.15.0...v0.15.1) | ||||
|  | ||||
| > 15 April 2023 | ||||
|  | ||||
| - 🚀Bumped version to v0.15.1 [`9581185`](https://git.odit.services/lfk/backend/commit/9581185b24039338e7f238ecdcc3881bb5203759) | ||||
| - Faster trackscan creation by only loading the latest scan [`e9914e3`](https://git.odit.services/lfk/backend/commit/e9914e317b7fd78863cfd8549bad65da9292b7ca) | ||||
| - Log batch time in mass scan script [`2905884`](https://git.odit.services/lfk/backend/commit/2905884c024d7f275b3ad2c2858a2f0911adb95b) | ||||
| - Dont load cards with get all runners request [`702070d`](https://git.odit.services/lfk/backend/commit/702070da669cc605b93e6f5b62d712c28f079dd0) | ||||
|  | ||||
| #### [v0.15.0](https://git.odit.services/lfk/backend/compare/v0.14.6...v0.15.0) | ||||
|  | ||||
| > 15 April 2023 | ||||
|  | ||||
| - Added test script for creating mass scans [`8007117`](https://git.odit.services/lfk/backend/commit/80071174342d87199fcbd981cd8c92300b0a51e4) | ||||
| - 🚀Bumped version to v0.15.0 [`cc89ba8`](https://git.odit.services/lfk/backend/commit/cc89ba8afb3120569613a889baf962555612e95a) | ||||
| - Get all scans speed improvement [`23fa78e`](https://git.odit.services/lfk/backend/commit/23fa78eb9dcc01ecc036347f6703aacc0d163d7d) | ||||
| - More scan request optimizations [`7c4ff42`](https://git.odit.services/lfk/backend/commit/7c4ff42a3b3e7b186e16c85a97d9ecc854a32cb0) | ||||
|  | ||||
| #### [v0.14.6](https://git.odit.services/lfk/backend/compare/v0.14.5...v0.14.6) | ||||
|  | ||||
| > 15 April 2023 | ||||
|  | ||||
| - 🚀Bumped version to v0.14.6 [`3b3e689`](https://git.odit.services/lfk/backend/commit/3b3e68900beca16cfff88dbef22540f77750d29b) | ||||
| - Missing orm file [`3ff666f`](https://git.odit.services/lfk/backend/commit/3ff666fd3e84ac8cf41b30e9e17082b10548d55b) | ||||
|  | ||||
| #### [v0.14.5](https://git.odit.services/lfk/backend/compare/v0.14.4...v0.14.5) | ||||
|  | ||||
| > 15 April 2023 | ||||
|  | ||||
| - 🚀Bumped version to v0.14.5 [`4e44350`](https://git.odit.services/lfk/backend/commit/4e4435010fd7095e3b9742e207cba1b68cd6da3b) | ||||
| - Entrypoint fix [`de9af5a`](https://git.odit.services/lfk/backend/commit/de9af5a90907dcfc9bfb1d5a56420eed8bb59922) | ||||
| - Fixed copy [`ac631f0`](https://git.odit.services/lfk/backend/commit/ac631f0af467446552478873b7b4802a9310f865) | ||||
|  | ||||
| #### [v0.14.4](https://git.odit.services/lfk/backend/compare/v0.14.3...v0.14.4) | ||||
|  | ||||
| > 15 April 2023 | ||||
|  | ||||
| - Switched ci over to pnpm + cache [`6275aaa`](https://git.odit.services/lfk/backend/commit/6275aaa326f1c02c8dd42aa31608978408c44ab7) | ||||
| - 🚀Bumped version to v0.14.4 [`6bbdd5b`](https://git.odit.services/lfk/backend/commit/6bbdd5bb04a1c38e4b3a150db24b76e9c96490dd) | ||||
| - Back to ean13 based codes [`a8fc755`](https://git.odit.services/lfk/backend/commit/a8fc7558408b97da4b2c469ae5e73ab502b4fda0) | ||||
| - install prod in first step [`d027439`](https://git.odit.services/lfk/backend/commit/d02743984dfea8057be3081bd3a32a8f67e610aa) | ||||
| - Switched dockerfile to pnpm 8 with cache [`93d43b7`](https://git.odit.services/lfk/backend/commit/93d43b76843d7cb411f37fd2066c6a5364c05415) | ||||
| - COPY by stage name [`a64f6c9`](https://git.odit.services/lfk/backend/commit/a64f6c9822af2b927e91b0b55f1f50176de30169) | ||||
| - pinned pnpm version [`2a94bfa`](https://git.odit.services/lfk/backend/commit/2a94bfa6227d14f635b5fc2789b59c36d490937e) | ||||
| - custom pnpm cache [`85dc344`](https://git.odit.services/lfk/backend/commit/85dc3444acc677ddd242f9f2543ce477fe427a7c) | ||||
| - added missing ci env [`734c826`](https://git.odit.services/lfk/backend/commit/734c826face58dd5c3bb2607bda6e7f6d051012e) | ||||
| - pinned pnpm to 8 [`27e74e8`](https://git.odit.services/lfk/backend/commit/27e74e824cd1e23d4d53c1a983a1668dd87f5d59) | ||||
| - coherent baseimage [`b5c0a28`](https://git.odit.services/lfk/backend/commit/b5c0a288ac3c020f5d753c558aee160fea0bae14) | ||||
| - bumped final pnpm version [`33b25c9`](https://git.odit.services/lfk/backend/commit/33b25c9743abb7cefb3538f08cc2f78a646905c8) | ||||
|  | ||||
| #### [v0.14.3](https://git.odit.services/lfk/backend/compare/v0.14.2...v0.14.3) | ||||
|  | ||||
| > 18 March 2023 | ||||
|  | ||||
| - 🚀Bumped version to v0.14.3 [`16ce0a8`](https://git.odit.services/lfk/backend/commit/16ce0a848050b74c4b6dd93f17e5a6e9024cdb7d) | ||||
| - Adjusted modulo for new fixed card length [`9a8d618`](https://git.odit.services/lfk/backend/commit/9a8d618ae4584640e8be1ce9fe4bddd2ef7a92ae) | ||||
|  | ||||
| #### [v0.14.2](https://git.odit.services/lfk/backend/compare/v0.14.1...v0.14.2) | ||||
|  | ||||
| > 18 March 2023 | ||||
|  | ||||
| - 🚀Bumped version to v0.14.2 [`38da2d3`](https://git.odit.services/lfk/backend/commit/38da2d33187f4b24eef878642e153663ecd95de1) | ||||
| - Back to modulo [`068deb4`](https://git.odit.services/lfk/backend/commit/068deb4960bd16decf99887ffbda7a7d3dd9ff0b) | ||||
|  | ||||
| #### [v0.14.1](https://git.odit.services/lfk/backend/compare/v0.14.0...v0.14.1) | ||||
|  | ||||
| > 18 March 2023 | ||||
|  | ||||
| - 🚀Bumped version to v0.14.1 [`13f093b`](https://git.odit.services/lfk/backend/commit/13f093bb6138a498f93a05ef6dd812ae92f2676a) | ||||
| - Switched from card prefix replacement via modulo to regex [`6289f30`](https://git.odit.services/lfk/backend/commit/6289f307400aacaa9cfe03f3024c1e0d5554d4f2) | ||||
|  | ||||
| #### [v0.14.0](https://git.odit.services/lfk/backend/compare/v0.13.3...v0.14.0) | ||||
|  | ||||
| > 15 March 2023 | ||||
|  | ||||
| - 🚀Bumped version to v0.14.0 [`6ff764b`](https://git.odit.services/lfk/backend/commit/6ff764bc340ca25b3bdd62c6892259e228723973) | ||||
| - Updated default length [`ea87cc7`](https://git.odit.services/lfk/backend/commit/ea87cc793b163bf0d4405a25bbe83fbc8e31c206) | ||||
| - breaking(runnercards): shorter runnercard codes (padding to 12 was a bit tooo ambitious) [`ffee887`](https://git.odit.services/lfk/backend/commit/ffee887ddf6a71102ee39533d7cd504d1fd6698f) | ||||
| - Removed sqlite journal [`92517e3`](https://git.odit.services/lfk/backend/commit/92517e365393f4baac3814f5668874b5752dc7c8) | ||||
|  | ||||
| #### [v0.13.3](https://git.odit.services/lfk/backend/compare/v0.13.2...v0.13.3) | ||||
|  | ||||
| > 15 February 2023 | ||||
|  | ||||
| - 🚀Bumped version to v0.13.3 [`3bac75e`](https://git.odit.services/lfk/backend/commit/3bac75e7ab9f16ecab1fbfa9915a7edb923883f6) | ||||
| - Merge pull request 'feature/201-no_citizen-deletion' (#202) from feature/201-no_citizen-deletion into dev [`d05eddc`](https://git.odit.services/lfk/backend/commit/d05eddcae198427ce9a334096563b3aadcff2b56) | ||||
| - Updated tests [`d5c689d`](https://git.odit.services/lfk/backend/commit/d5c689d6937288df7dca14ce26fbbd4f46a8752a) | ||||
| - Added delete check for citizen org [`8fedd4e`](https://git.odit.services/lfk/backend/commit/8fedd4ef3bdd48dc42abc1d53006eefc145175e3) | ||||
|  | ||||
| #### [v0.13.2](https://git.odit.services/lfk/backend/compare/v0.13.1...v0.13.2) | ||||
|  | ||||
| > 3 February 2023 | ||||
|  | ||||
| - 🚀Bumped version to v0.13.2 [`e8b2e6f`](https://git.odit.services/lfk/backend/commit/e8b2e6f26140a18c06b017e4461742d7e7942f08) | ||||
| - Merge pull request 'move selfservice magic link endpoint to 15min rate limit' (#200) from feature/runner-selfservice-login-link-rate-limit into dev [`39f3b0e`](https://git.odit.services/lfk/backend/commit/39f3b0e01f03bfbcfcb0ea08d697268ce068e63d) | ||||
| - move to 15min limit [`edaf255`](https://git.odit.services/lfk/backend/commit/edaf255e8f609185dcd6c2c0cd2e8b007b785e0c) | ||||
| - Merge pull request 'Releases 0.12.0 and 0.13.0' (#199) from dev into main [`41c4ed4`](https://git.odit.services/lfk/backend/commit/41c4ed4d0faaed382801bbe480f31dafa6f3912d) | ||||
|  | ||||
| #### [v0.13.1](https://git.odit.services/lfk/backend/compare/v0.13.0...v0.13.1) | ||||
|  | ||||
| > 2 February 2023 | ||||
|  | ||||
| - 🚀Bumped version to v0.13.1 [`f2bd88a`](https://git.odit.services/lfk/backend/commit/f2bd88aadfcb6ffa0485ea6afac8c7664a37f5f4) | ||||
| - Updated description [`67a3661`](https://git.odit.services/lfk/backend/commit/67a36614485b2ea83c2de41e0684708b95a05b32) | ||||
|  | ||||
| #### [v0.13.0](https://git.odit.services/lfk/backend/compare/v0.12.0...v0.13.0) | ||||
|  | ||||
| > 2 February 2023 | ||||
|  | ||||
| - Added faker for testing [`e184673`](https://git.odit.services/lfk/backend/commit/e1846739638905aab6ba7e059fd2cbf8ff467bf3) | ||||
| - 📖New license file version [CI SKIP] [skip ci] [`2b641fa`](https://git.odit.services/lfk/backend/commit/2b641faa29c47d95f69983770dc4ab37e674604f) | ||||
| - 🚀Bumped version to v0.13.0 [`0c763a2`](https://git.odit.services/lfk/backend/commit/0c763a2dfd39607b480d9aff7d3c883791f41700) | ||||
| - Updated selfservice tests to prevent email duplication [`9bc80aa`](https://git.odit.services/lfk/backend/commit/9bc80aac8aab9b4dedc26c9bc3ce705d7fe9c0bf) | ||||
| - Moved license and changelog export to releaseit hooks [`77c6303`](https://git.odit.services/lfk/backend/commit/77c6303014578edbbadeeaa790f7974bde2a9764) | ||||
| - Updated readme [`4cdba8b`](https://git.odit.services/lfk/backend/commit/4cdba8bc77ce543f6fb636711b8728bce794eac7) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`ae14d6c`](https://git.odit.services/lfk/backend/commit/ae14d6c74f9205440b41ca5fdbd052ca449148fc) | ||||
| - Added selfservice runner create check to prevent duplicate email [`68cd746`](https://git.odit.services/lfk/backend/commit/68cd746a9f3360b3630a9ba570213d2aa62497b4) | ||||
| - Updated tests for new login in selfservice [`39aa759`](https://git.odit.services/lfk/backend/commit/39aa7598b7cd0ecb0f077f50ebdd31c6e205f06d) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`9fa8b93`](https://git.odit.services/lfk/backend/commit/9fa8b93c08ee52335b18e743f9d205b19e6095c6) | ||||
| - Moved changelog generation to package script [`a7297ff`](https://git.odit.services/lfk/backend/commit/a7297ff933ae1372a9d508cdae1a54d2ebbcc647) | ||||
| - Merge pull request 'feature/197-duplicate_runner_mail' (#198) from feature/197-duplicate_runner_mail into dev [`4b676bc`](https://git.odit.services/lfk/backend/commit/4b676bc85336c2d494e9e74823d38deec5cc0400) | ||||
| - Updated logo url [`4433ddb`](https://git.odit.services/lfk/backend/commit/4433ddb1e15a35481728670e22049200644bf337) | ||||
| - depends_on: ["clone"] [`9cc66ee`](https://git.odit.services/lfk/backend/commit/9cc66eebdfe8e7a2888bbc97197d1756ff44de30) | ||||
| - Fixed typo [`19a290c`](https://git.odit.services/lfk/backend/commit/19a290c3a931ead0d9ae9ebb0985bfbaac54df59) | ||||
| - Rename selfservice forgot to login [`69651d9`](https://git.odit.services/lfk/backend/commit/69651d9f6cd826b6d4720f164897a2a72a57c851) | ||||
| - 📖New license file version [CI SKIP] [skip ci] [`6fd246f`](https://git.odit.services/lfk/backend/commit/6fd246f43cb3f4d0ccb6e017ee699889ba17daac) | ||||
| - Add git for changelog fun [`2fa56b8`](https://git.odit.services/lfk/backend/commit/2fa56b82d1e082a1deae943e5fca5101f24e3ef5) | ||||
|  | ||||
| #### [v0.12.0](https://git.odit.services/lfk/backend/compare/v0.11.1...v0.12.0) | ||||
|  | ||||
| > 2 February 2023 | ||||
|  | ||||
| - Pinned versions [`a6d5693`](https://git.odit.services/lfk/backend/commit/a6d5693ccdeb25b15a09af8f7438142114268807) | ||||
| - Drone -> Kaniko based builds [`0e78951`](https://git.odit.services/lfk/backend/commit/0e789513008085d0db94fc3b2dd9e74a5e583049) | ||||
| - Drone images to odit registry [`6ad56b3`](https://git.odit.services/lfk/backend/commit/6ad56b31269bf19a740c1b6b1a303a8a9d7d59d0) | ||||
| - Bumped container base images [`d95c6d3`](https://git.odit.services/lfk/backend/commit/d95c6d33657f6aa977a8ebfefad7e199bb1cc9c3) | ||||
| - Enabled tag via release script [`9217421`](https://git.odit.services/lfk/backend/commit/92174212213f874e41c9472a927bcf87b963ac94) | ||||
| - Pinned pnpm for builds [`4570845`](https://git.odit.services/lfk/backend/commit/4570845b3e1bd00c228fe1b09b658c24e20aba7f) | ||||
| - 🚀Bumped version to v0.12.0 [`4c10e20`](https://git.odit.services/lfk/backend/commit/4c10e20b91a8101ee37b230373ceb3e024582b41) | ||||
| - Ignore pnpm lock [`1f2c8ab`](https://git.odit.services/lfk/backend/commit/1f2c8abb22f3ff1e61b7350b517bd699c3e315f6) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`31b258b`](https://git.odit.services/lfk/backend/commit/31b258b4ce82213144160a4233b7fd127e456776) | ||||
|  | ||||
| #### [v0.11.1](https://git.odit.services/lfk/backend/compare/v0.11.0...v0.11.1) | ||||
|  | ||||
| > 22 April 2021 | ||||
|  | ||||
| - Merge pull request 'Release 0.11.1' (#196) from dev into main [`f19f280`](https://git.odit.services/lfk/backend/commit/f19f2808d88414f1877c01f10996dac68b6f9617) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`2229cdf`](https://git.odit.services/lfk/backend/commit/2229cdf20db1a98f9f76a99fa9d3f463cdf6d804) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`348fe52`](https://git.odit.services/lfk/backend/commit/348fe52c42cfa32239b703041820f725e147154e) | ||||
| - Now prefixing runnercards with 2 [`8a82e05`](https://git.odit.services/lfk/backend/commit/8a82e059b74ceabf43c9cbfe9c9b89ef6ce15a28) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`3b9cd2e`](https://git.odit.services/lfk/backend/commit/3b9cd2e1bbbe8e69c3883233a98f286d768c2b79) | ||||
| - Added fix for the appended 2 [`eb526fb`](https://git.odit.services/lfk/backend/commit/eb526fb57faf631fd6e84af99af738ab1b3481c7) | ||||
| - 🚀Bumped version to v0.11.1 [`95320ca`](https://git.odit.services/lfk/backend/commit/95320ca1bccc2886553accea6a428aadffda0a27) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`f2d127f`](https://git.odit.services/lfk/backend/commit/f2d127fc98d75ce658424624abd382c087737ca0) | ||||
|  | ||||
| #### [v0.11.0](https://git.odit.services/lfk/backend/compare/v0.10.2...v0.11.0) | ||||
|  | ||||
| > 14 April 2021 | ||||
|  | ||||
| - Merge pull request 'Release 0.11.0' (#195) from dev into main [`3220b19`](https://git.odit.services/lfk/backend/commit/3220b194d4c704835d6d106ec4d9d54a17a38b62) | ||||
| - Fixed spelling [`da266a8`](https://git.odit.services/lfk/backend/commit/da266a8dd68dbb575997ae343624982b690486ec) | ||||
| - Updated tests [`01ed514`](https://git.odit.services/lfk/backend/commit/01ed51489eb92fff907d46a930ecf0b0eb5cad2b) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`520608a`](https://git.odit.services/lfk/backend/commit/520608aef05b21f4daadf55cfc8caddba06b8f01) | ||||
| - Added payedDonationAmount to donor and responsedonor [`8ae4b85`](https://git.odit.services/lfk/backend/commit/8ae4b8582749332f4fb081eee0c520293347001f) | ||||
| - Responses now contain the donation status [`34dbaaa`](https://git.odit.services/lfk/backend/commit/34dbaaafe0422234848eabe3f52b26879c9e5a49) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`278c4a6`](https://git.odit.services/lfk/backend/commit/278c4a6a415434487a92ff66f8114bb2547aac48) | ||||
| - Marked payedAmount as optional during creation and/or update [`0636616`](https://git.odit.services/lfk/backend/commit/0636616dad5afb41ffe47a857d91ac75b4f2f20a) | ||||
| - Added payed amount fileld to donation class [`b8fbb72`](https://git.odit.services/lfk/backend/commit/b8fbb72fa0b659c9acc406c72a8a59c2174351b4) | ||||
| - Added status to tests [`30c6d3d`](https://git.odit.services/lfk/backend/commit/30c6d3d8db9fe37a51e596a73add8b87e8616e54) | ||||
| - Added payed amount to response class [`d64f470`](https://git.odit.services/lfk/backend/commit/d64f470b608b3f179ec77da0210de51c328ef3f2) | ||||
| - 📖New license file version [CI SKIP] [skip ci] [`a2f0d81`](https://git.odit.services/lfk/backend/commit/a2f0d814fc782ad440500e7d6ec779b6ab7f0ac6) | ||||
| - 🚀Bumped version to v0.11.0 [`3558e99`](https://git.odit.services/lfk/backend/commit/3558e9909088647bd4f1f4334f50c07a5ef00214) | ||||
| - Merge pull request 'Donation payment management feature/193-donation_payments' (#194) from feature/193-donation_payments into dev [`6df5f63`](https://git.odit.services/lfk/backend/commit/6df5f634f3123e04c015889573ccc5674a8bab27) | ||||
| - Added payed amount to crealte classes [`71542bc`](https://git.odit.services/lfk/backend/commit/71542bc3887b97c15436d03280e49f7b3f0fcb06) | ||||
| - Added donation status enum [`b4c31ee`](https://git.odit.services/lfk/backend/commit/b4c31ee9b5b35d6e11b07f50f3d30ca12e0f7728) | ||||
| - Added payed amount to update classes [`9930742`](https://git.odit.services/lfk/backend/commit/99307423c533f8cde847b59a80bffc2ff42c9769) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`0c61ff4`](https://git.odit.services/lfk/backend/commit/0c61ff457d02f750efa457dd75464187683b037a) | ||||
| - Added mssing check to tests [`6c14ed9`](https://git.odit.services/lfk/backend/commit/6c14ed9c89eadc1a10db8c912d8ea2711a518766) | ||||
| - No longer answering with null, but 0 [`49b174f`](https://git.odit.services/lfk/backend/commit/49b174f29f63e963e600d74b6923a20211d832eb) | ||||
| - Saved missing file [`8fe3243`](https://git.odit.services/lfk/backend/commit/8fe32436935d7cd6c17eae1e138383d3b714e1ba) | ||||
|  | ||||
| #### [v0.10.2](https://git.odit.services/lfk/backend/compare/v0.10.1...v0.10.2) | ||||
|  | ||||
| > 7 April 2021 | ||||
|  | ||||
| - Merge pull request 'Release 0.10.2' (#192) from dev into main [`1d82f65`](https://git.odit.services/lfk/backend/commit/1d82f65b0d3a32d10c1a10c991353c18696d58bf) | ||||
| - Added first selfservice test [`057ae0d`](https://git.odit.services/lfk/backend/commit/057ae0d79758cd627d6d128406a0d201b6b7ad9b) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`b7ad5d3`](https://git.odit.services/lfk/backend/commit/b7ad5d3a31b8b4f5960852d3ac38af133719ebcd) | ||||
| - First try of the laptime sort [`4471e57`](https://git.odit.services/lfk/backend/commit/4471e57438582d55ff846fd69c2cfcc26b40df2a) | ||||
| - Potential fix for all remaining errors [`377d5da`](https://git.odit.services/lfk/backend/commit/377d5dadb2a14cb2d70e0b2dc77026f51b3fb51c) | ||||
| - At least one fewer test should fail now [`87f444c`](https://git.odit.services/lfk/backend/commit/87f444c30d69d65a9f918c63631a859a389eeee3) | ||||
| - Tried workaround for no availdable stats [`8f0f795`](https://git.odit.services/lfk/backend/commit/8f0f795a709db216396998b68b8bbd64ff4d44ff) | ||||
| - Reverted temp bugfix [`4603a84`](https://git.odit.services/lfk/backend/commit/4603a84f16fb53a14d1792447100f5b470969dd0) | ||||
| - Fixed sorting algo [`988f17a`](https://git.odit.services/lfk/backend/commit/988f17a795bb2d867e9d1d8e78051dff1a14ec30) | ||||
| - Added runners stats tests [`7111068`](https://git.odit.services/lfk/backend/commit/7111068361e00cc1308664a3ae650a56e28c015c) | ||||
| - Added basic laptime endpoint [`cb71fcd`](https://git.odit.services/lfk/backend/commit/cb71fcd13bc61e6214e2fd7b70e72094749463d3) | ||||
| - Added orgs by donations stats tests [`d4a02e7`](https://git.odit.services/lfk/backend/commit/d4a02e7db2ff4976be21605e31aac2f3c82a49c0) | ||||
| - Added teams stats endpoint tests [`b9a7dc8`](https://git.odit.services/lfk/backend/commit/b9a7dc84f05441445453193974b2a793b5197fa5) | ||||
| - Now resolving all missing relations [`257f320`](https://git.odit.services/lfk/backend/commit/257f320ee3bf6429c4314c64023520366f9f730b) | ||||
| - Added min laptime to StatsRunner [`51daf96`](https://git.odit.services/lfk/backend/commit/51daf969cf74792b2c2f2f16ce4359d9fca47bc8) | ||||
| - Fixed sorting [`7b15c2d`](https://git.odit.services/lfk/backend/commit/7b15c2d88b14e7279aad97b0c950202ddb5acaaa) | ||||
| - Fixed top-ten bein top 9 [`a6a526d`](https://git.odit.services/lfk/backend/commit/a6a526dc5d8b1613ea34e82e477081349e764aec) | ||||
| - added new ci secret [`5633e85`](https://git.odit.services/lfk/backend/commit/5633e85f41cb69b10fd8a86f57f1bd2f50848f7b) | ||||
| - Added temp console log for test [`22cae39`](https://git.odit.services/lfk/backend/commit/22cae39bd351ca285880e50187ea0d46a7a26437) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`610988e`](https://git.odit.services/lfk/backend/commit/610988ec16b8df61cca61cf2252a469d30318d81) | ||||
| - Added temp console log for ci debugging [`4a73eab`](https://git.odit.services/lfk/backend/commit/4a73eab134c3a9f58771be996bc8811b62cf378e) | ||||
| - Temp disabled runners by donations test [`0b07a53`](https://git.odit.services/lfk/backend/commit/0b07a53ed209c6193ead3c4d199545e22333ab32) | ||||
| - Updated default docker-compose [`f8baca5`](https://git.odit.services/lfk/backend/commit/f8baca5ab2c56b906751bc7edd71477456ad91f3) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`dd6d799`](https://git.odit.services/lfk/backend/commit/dd6d799c847fc96aec1be8f2667ad371890076fb) | ||||
| - Resolved missing parentgroup relation [`23bd432`](https://git.odit.services/lfk/backend/commit/23bd432c5f33a0863217120d97e2e4ea52a08baf) | ||||
| - Removed console logs for now working tests [`71b33ab`](https://git.odit.services/lfk/backend/commit/71b33ab05b53b62c8b271bd2995c94b2fc212dfd) | ||||
| - Fixed typo in test [`cbcb829`](https://git.odit.services/lfk/backend/commit/cbcb829fbde3a4a5e7f94de5dcf24d854c5fc257) | ||||
| - Ptotential fix for stats failing [`dcdbdd1`](https://git.odit.services/lfk/backend/commit/dcdbdd15acfe6eef4220b7ed66db60d78107d1f9) | ||||
| - 🚀Bumped version to v0.10.2 [`6e236ed`](https://git.odit.services/lfk/backend/commit/6e236ede145e164ee84543fb62404b4776550973) | ||||
| - Merge pull request 'stats/runners/laptime feature/190-runners_laptime' (#191) from feature/190-runners_laptime into dev [`a694ad2`](https://git.odit.services/lfk/backend/commit/a694ad225c68fa23152402acba871c857433cc70) | ||||
| - Removed all useless console.logs [`95e1eec`](https://git.odit.services/lfk/backend/commit/95e1eec313a79458dd75307a9d0f8319af0d0904) | ||||
| - Pinned testing container tag to prod container tag [`10221b9`](https://git.odit.services/lfk/backend/commit/10221b9f2e4493080f3ff095d9772bcfd0ac50eb) | ||||
| - Now resolving all relations for orgs by distance [`4a294b1`](https://git.odit.services/lfk/backend/commit/4a294b1e17c44294274b06748ec8141812c2d217) | ||||
| - Added temp console log [`720774f`](https://git.odit.services/lfk/backend/commit/720774fcf47c38601ab88d5d74cfcd0e47b21acf) | ||||
| - Removed console log for passing tests [`132b48c`](https://git.odit.services/lfk/backend/commit/132b48cf2a9e990a5e830c744ed8244bd25e8b3a) | ||||
| - Removed console log [`1d8c8c8`](https://git.odit.services/lfk/backend/commit/1d8c8c8e9cefa58449f7abb2481d9396fe37ba20) | ||||
| - Temp test logging workaround [`bf686e8`](https://git.odit.services/lfk/backend/commit/bf686e89e02998ccc80c838ef890c736c252634c) | ||||
| - Temp test logging workaround [`6163f0a`](https://git.odit.services/lfk/backend/commit/6163f0a90b3721d3a1488f89cbb39ddff7152241) | ||||
| - Removed test for content type [`63964fb`](https://git.odit.services/lfk/backend/commit/63964fbf2c41d9b90f995f056e9db65ab07d54a8) | ||||
|  | ||||
| #### [v0.10.1](https://git.odit.services/lfk/backend/compare/v0.10.0...v0.10.1) | ||||
|  | ||||
| > 3 April 2021 | ||||
|  | ||||
| - Merge pull request 'Release 0.10.1' (#189) from dev into main [`e89e07d`](https://git.odit.services/lfk/backend/commit/e89e07d0fc99f14148b01204fb8ed39e2da77e38) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`69afd4d`](https://git.odit.services/lfk/backend/commit/69afd4d5877401eb46df430f43a7feb273abda1e) | ||||
| - 🚀Bumped version to v0.10.1 [`24d152f`](https://git.odit.services/lfk/backend/commit/24d152fdc8fe17fffa2f2a718d7145ba8a91d79c) | ||||
| - New class: ResponseSelfServiceDonor [`d70c5b1`](https://git.odit.services/lfk/backend/commit/d70c5b1bbc9f02782f8755b6929e2d3458e10221) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`4279e43`](https://git.odit.services/lfk/backend/commit/4279e4374304887e8db40eab77763b20bbce91a1) | ||||
| - Removed duplicate openapi statement [`4834a66`](https://git.odit.services/lfk/backend/commit/4834a6698b0958602421c1478a95fec7edda910b) | ||||
| - Switched selfservice donation.donor from string to object [`0767943`](https://git.odit.services/lfk/backend/commit/0767943721b6964d542f580c541e744f86444ac6) | ||||
| - Adjusted runner property names [`ca87774`](https://git.odit.services/lfk/backend/commit/ca87774767807a2c4bc869b0de95cc73832a8405) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`71e3d0e`](https://git.odit.services/lfk/backend/commit/71e3d0efe2cbde47aea0f26cb5a8b5cd3312707d) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`c28843c`](https://git.odit.services/lfk/backend/commit/c28843c405dc4fd06a10f0fb85814acede15a769) | ||||
| - Merge pull request 'Selfservice donations reformatting feature/187-selfservice_donation' (#188) from feature/187-selfservice_donation into dev [`d837654`](https://git.odit.services/lfk/backend/commit/d837654617f7de5d055ffb06c65e2cd52f65c604) | ||||
| - Added new responsetype for new class [`f693f2c`](https://git.odit.services/lfk/backend/commit/f693f2cde9a04147155aea4de5d52e1d19d722ca) | ||||
|  | ||||
| #### [v0.10.0](https://git.odit.services/lfk/backend/compare/v0.9.2...v0.10.0) | ||||
|  | ||||
| > 1 April 2021 | ||||
|  | ||||
| - Merge pull request 'Release 0.10.0' (#186) from dev into main [`b517dff`](https://git.odit.services/lfk/backend/commit/b517dff8a82c960836d9f0be90fd89f3ba2fae7d) | ||||
| - 🚀Bumped version to v0.10.0 [`dc3071f`](https://git.odit.services/lfk/backend/commit/dc3071f7d2be298f0bb02d86ec67ed1125cd3b49) | ||||
| - Added locale to mail related runner endpoints [`7af883f`](https://git.odit.services/lfk/backend/commit/7af883f27198206af542bcaff4686221d3788e87) | ||||
| - Added locale to mail related runner endpoints [`f543307`](https://git.odit.services/lfk/backend/commit/f5433076b01c743ed9af085fccadb8f1edc26419) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`5fb355f`](https://git.odit.services/lfk/backend/commit/5fb355f450f19e96d3671b1a46e94d564495942b) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`114c246`](https://git.odit.services/lfk/backend/commit/114c246aceba566cc0dd6daab51a77b951b031cc) | ||||
| - Merge pull request 'Mail locales feature/184-mail_locales' (#185) from feature/184-mail_locales into dev [`33c13de`](https://git.odit.services/lfk/backend/commit/33c13de32c68a3d9e87e4fd9ad12a815ed8c9fde) | ||||
| - Added locale to mail related user endpoints [`1be073a`](https://git.odit.services/lfk/backend/commit/1be073a4fa39f0332a46f567ee6af10a9137844c) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`6aafe4a`](https://git.odit.services/lfk/backend/commit/6aafe4a6ae7d253ab39220e551c52ae067cc481a) | ||||
|  | ||||
| #### [v0.9.2](https://git.odit.services/lfk/backend/compare/v0.9.1...v0.9.2) | ||||
|  | ||||
| > 29 March 2021 | ||||
|  | ||||
| - Merge pull request 'Release 0.9.2' (#183) from dev into main [`bdeeb03`](https://git.odit.services/lfk/backend/commit/bdeeb036459c2a2131e843d8a5a6b338e0ba46ea) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`675c876`](https://git.odit.services/lfk/backend/commit/675c8762e8e4cf28d2f334d5ab2e1cb6b594e33c) | ||||
| - Fixed bug in return creation [`6c9b91d`](https://git.odit.services/lfk/backend/commit/6c9b91d75a0d08fc4ab0e72c7a09bd0133566368) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`8c00aef`](https://git.odit.services/lfk/backend/commit/8c00aefd6ce3723d9f83d1c94e6491d5d597391f) | ||||
| - 🚀Bumped version to v0.9.2 [`89e3924`](https://git.odit.services/lfk/backend/commit/89e392473c52a3f328545699a0f4df89be33ba89) | ||||
|  | ||||
| #### [v0.9.1](https://git.odit.services/lfk/backend/compare/v0.9.0...v0.9.1) | ||||
|  | ||||
| > 29 March 2021 | ||||
|  | ||||
| - Merge pull request 'Release v0.9.1' (#182) from dev into main [`3afd785`](https://git.odit.services/lfk/backend/commit/3afd785a54fac91c12af789af19b45e6124e0e39) | ||||
| - 🚀Bumped version to v0.9.1 [`a139554`](https://git.odit.services/lfk/backend/commit/a139554e059e9a10acb1733ce1a82b610cc99269) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`8099999`](https://git.odit.services/lfk/backend/commit/8099999e2cdfc8046f9ff4a90681281b671e402d) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`0290b0e`](https://git.odit.services/lfk/backend/commit/0290b0e5f531364d37d8157e639614cf5a6b4189) | ||||
| - Merge pull request 'Return cards generated in bulk feature/180-blank_generation_return' (#181) from feature/180-blank_generation_return into dev [`0f7fa99`](https://git.odit.services/lfk/backend/commit/0f7fa990d473ce2dce032c47c39f79c1d0e8df90) | ||||
| - Added query param to return created runenrcards [`5a36c8d`](https://git.odit.services/lfk/backend/commit/5a36c8dcae3d79b3b05ffb30a7ebb0d31dc8183a) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`58f4d21`](https://git.odit.services/lfk/backend/commit/58f4d2151f459bc72692cc70e02a59b77abfb9f0) | ||||
| - Added test for returnCards=true array length [`1cb2dc9`](https://git.odit.services/lfk/backend/commit/1cb2dc9d53b530435f5798f9cdf7ee866eb7416e) | ||||
| - Added test for single card generation with returnCards=true [`6005b06`](https://git.odit.services/lfk/backend/commit/6005b0661f1d5c461bb102e243cc209a8adc21fa) | ||||
| - Fixed copy-paste oversight [`2f568c9`](https://git.odit.services/lfk/backend/commit/2f568c9cb8ae39ce40ec8df6d9acbaf0d5ae1a26) | ||||
|  | ||||
| #### [v0.9.0](https://git.odit.services/lfk/backend/compare/v0.8.0...v0.9.0) | ||||
|  | ||||
| > 26 March 2021 | ||||
|  | ||||
| - Merge pull request 'Release 0.9.0' (#179) from dev into main [`95135dd`](https://git.odit.services/lfk/backend/commit/95135ddc893dcf64be67b47b0ef2b0d9041253bd) | ||||
| - Reenabled user tests [`4c66650`](https://git.odit.services/lfk/backend/commit/4c6665062fe6717242e43b58e66c1f1d030c018d) | ||||
| - Moved to tmp files to better check for other problems [`7a64f23`](https://git.odit.services/lfk/backend/commit/7a64f2393783f97a9729356bc1dfd831927dd312) | ||||
| - Added user creation invalid tests [`888cab5`](https://git.odit.services/lfk/backend/commit/888cab5898caf9e552c421346934bf90f717a653) | ||||
| @@ -15,16 +508,17 @@ All notable changes to this project will be documented in this file. Dates are d | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`274a146`](https://git.odit.services/lfk/backend/commit/274a146b9bccfe5e1a879ca137ebb4f51eaa5d57) | ||||
| - Fixed test params [`070560e`](https://git.odit.services/lfk/backend/commit/070560e8632e833dd26505c02ccb2474462b63ac) | ||||
| - No longer using createuser in seeding process [`96ba25e`](https://git.odit.services/lfk/backend/commit/96ba25ec6c6c397cd2aa322afa79024395f658fe) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`a7fe1e1`](https://git.odit.services/lfk/backend/commit/a7fe1e175918edd7a98983ece570b47075e85e9a) | ||||
| - 🚀Bumped version to v0.8.0 [`c23b4d9`](https://git.odit.services/lfk/backend/commit/c23b4d907f20ed7af37a6de6ea4c61433e30b29b) | ||||
| - 🚀Bumped version to v0.9.0 [`56a5f41`](https://git.odit.services/lfk/backend/commit/56a5f4168621263daeab5d2fda97b944cdc6ab31) | ||||
| - Merge pull request 'Password security feature/99-password_checks' (#177) from feature/99-password_checks into dev [`5a3fc5b`](https://git.odit.services/lfk/backend/commit/5a3fc5b2bd06b3e26177d017d3503f4f627be3f2) | ||||
| - Added pw errors to user controller [`b24e24f`](https://git.odit.services/lfk/backend/commit/b24e24ff7dd75d972cdab0fd1e2fe6c532ca2b2f) | ||||
| - Now checking password rules on user creation [`5daaa3a`](https://git.odit.services/lfk/backend/commit/5daaa3a73c4eca2817d67e226679d125928a3645) | ||||
| - Now checking password rules on user update [`48a87e8`](https://git.odit.services/lfk/backend/commit/48a87e8936e13c48f4baa3f4b10f781ad2f55a44) | ||||
| - Fixed pw not getting hashed currectly; [`cb3ea9b`](https://git.odit.services/lfk/backend/commit/cb3ea9b1ebb82c650abd83d4be8629cfe29a5b21) | ||||
| - Added pw errors to me controller [`9ce35d8`](https://git.odit.services/lfk/backend/commit/9ce35d8eb78a01f40af8c70e640eca3bcb142304) | ||||
| - 🚀Bumped version to v0.8.0 [`c23b4d9`](https://git.odit.services/lfk/backend/commit/c23b4d907f20ed7af37a6de6ea4c61433e30b29b) | ||||
| - Added password checker dependency [`bd00f4f`](https://git.odit.services/lfk/backend/commit/bd00f4f8d585fb6878874810f7de0b8b9f3950d5) | ||||
| - 🚀Bumped version to v0.9.0 [`56a5f41`](https://git.odit.services/lfk/backend/commit/56a5f4168621263daeab5d2fda97b944cdc6ab31) | ||||
| - Merge pull request 'Password security feature/99-password_checks' (#177) from feature/99-password_checks into dev [`5a3fc5b`](https://git.odit.services/lfk/backend/commit/5a3fc5b2bd06b3e26177d017d3503f4f627be3f2) | ||||
| - Now forceing user deletion in tests [`8154e71`](https://git.odit.services/lfk/backend/commit/8154e715bbf18938bd5d1031656a88d39231fa81) | ||||
| - Added password checker dependency [`bd00f4f`](https://git.odit.services/lfk/backend/commit/bd00f4f8d585fb6878874810f7de0b8b9f3950d5) | ||||
| - Fixed empty object getting called [`5369000`](https://git.odit.services/lfk/backend/commit/536900091afd7366128f21058490d0d4f15c6c89) | ||||
| - 🧾New changelog file version [CI SKIP] [skip ci] [`03d76e6`](https://git.odit.services/lfk/backend/commit/03d76e6d0bc5b4655f7f441232681c9462815526) | ||||
| - Formatting [`b8c28eb`](https://git.odit.services/lfk/backend/commit/b8c28ebb0808395218b5fb9031f477ae1d48e65e) | ||||
|   | ||||
							
								
								
									
										31
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								Dockerfile
									
									
									
									
									
								
							| @@ -1,16 +1,27 @@ | ||||
| # Typescript Build | ||||
| FROM node:14.15.1-alpine3.12 | ||||
| FROM registry.odit.services/hub/library/node:23.10.0-alpine3.21 AS build | ||||
| ARG NPM_REGISTRY_URL=https://registry.npmjs.org | ||||
| WORKDIR /app | ||||
|  | ||||
| COPY package.json ./ | ||||
| RUN npm i -g pnpm | ||||
| RUN pnpm i | ||||
| COPY pnpm-workspace.yaml ./ | ||||
| COPY pnpm-lock.yaml ./ | ||||
| RUN npm config set registry $NPM_REGISTRY_URL && npm i -g pnpm@10.7 | ||||
| RUN mkdir /pnpm && pnpm config set store-dir /pnpm && pnpm i | ||||
|  | ||||
| COPY tsconfig.json ormconfig.js ./ | ||||
| COPY src ./src | ||||
| RUN pnpm run build | ||||
| RUN pnpm run build \ | ||||
|     && rm -rf /app/node_modules \ | ||||
|     && pnpm i --production --prefer-offline | ||||
|  | ||||
| # final image | ||||
| FROM node:14.15.1-alpine3.12 | ||||
| COPY package.json ormconfig.js ./ | ||||
| RUN npm i -g pnpm | ||||
| RUN pnpm i --prod | ||||
| COPY --from=0 /app/dist dist | ||||
| ENTRYPOINT ["node", "dist/app.js"] | ||||
| FROM registry.odit.services/hub/library/node:23.10.0-alpine3.21 AS final | ||||
| WORKDIR /app | ||||
| COPY --from=build /app/package.json /app/package.json | ||||
| COPY --from=build /app/pnpm-lock.yaml /app/pnpm-lock.yaml | ||||
| COPY --from=build /app/pnpm-workspace.yaml /app/pnpm-workspace.yaml | ||||
| COPY --from=build /app/ormconfig.js /app/ormconfig.js | ||||
| COPY --from=build /app/dist /app/dist | ||||
| COPY --from=build /app/node_modules /app/node_modules | ||||
| ENTRYPOINT ["node", "/app/dist/app.js"] | ||||
							
								
								
									
										68
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										68
									
								
								README.md
									
									
									
									
									
								
							| @@ -15,59 +15,53 @@ Backend Server | ||||
|  | ||||
| 1. Rename the .env.example file to .env (you can adjust app port and other settings, if needed) | ||||
| 2. Install Dependencies | ||||
|    ```bash | ||||
|    yarn | ||||
|    ``` | ||||
| ```bash | ||||
| pnpm i | ||||
| ``` | ||||
| 3. Start the server | ||||
|    ```bash | ||||
|    yarn dev | ||||
|    ``` | ||||
| ```bash | ||||
| pnpm dev | ||||
| ``` | ||||
|  | ||||
| ### Run Tests | ||||
| ```bash | ||||
| # Run tests once (server has to run) | ||||
| yarn test | ||||
| pnpm test | ||||
|  | ||||
| # Run test in watch mode (reruns on change) | ||||
| yarn test:watch | ||||
| pnpm test:watch | ||||
|  | ||||
| # Run test in ci mode (automaticly starts the dev server) | ||||
| yarn test:ci | ||||
| pnpm test:ci | ||||
| ``` | ||||
|  | ||||
| ### Use your own mail templates | ||||
| > You use your own mail templates by replacing the default ones we provided (either in-code or by mounting them into the /app/static/mail_templates folder). | ||||
|  | ||||
| The mail templates always come in a .html and a .txt variant to provide compatability with legacy mail clients. | ||||
| Currently the following templates exist: | ||||
| * pw-reset.(html/txt) | ||||
|  | ||||
| ### Generate Docs | ||||
| ```bash | ||||
| yarn docs | ||||
| pnpm docs | ||||
| ``` | ||||
|  | ||||
| ## ENV Vars | ||||
| > You can provide them via .env file or docker env vars. | ||||
| > You can use the `test:ci:generate_env` package script to generate a example env (uses bs data as test server and ignores the errors). | ||||
|  | ||||
| | Name | Type | Default | Description | ||||
| | - | - | - | - | ||||
| | APP_PORT | Number | 4010 | The port the backend server listens on. Is optional. | ||||
| | DB_TYPE | String | N/A | The type of the db u want to use. It has to be supported by typeorm. Possible: `sqlite`, `mysql`, `postgresql` | ||||
| | DB_HOST | String | N/A | The db's host's ip-address/fqdn or file path for sqlite | ||||
| | DB_PORT | String | N/A | The db's port | ||||
| | DB_USER | String | N/A | The user for accessing the db | ||||
| | DB_PASSWORD | String | N/A | The user's password for accessing the db | ||||
| | DB_NAME | String | N/A | The db's name | ||||
| | NODE_ENV | String | dev | The apps env - influences debug info. Also when the env is set to "test", mailing errors get ignored. | ||||
| | POSTALCODE_COUNTRYCODE | String/CountryCode | N/A | The countrycode used to validate address's postal codes | ||||
| | PHONE_COUNTRYCODE | String/CountryCode | null (international) | The countrycode used to validate phone numers | ||||
| | SEED_TEST_DATA | Boolean | False | If you want the app to seed some example data set this to true | ||||
| | MAILER_URL | String(Url) | N/A | The mailer's base url (no trailing slash) | ||||
| | MAILER_KEY | String | N/A | The mailer's api key. | ||||
| | IMPRINT_URL | String(Url) | /imprint | The link to a imprint page for the system (Defaults to the frontend's imprint) | ||||
| | PRIVACY_URL | String(Url) | /privacy | The link to a privacy page for the system (Defaults to the frontend's privacy page) | ||||
| | Name                   | Type               | Default              | Description                                                                                                    | | ||||
| | ---------------------- | ------------------ | -------------------- | -------------------------------------------------------------------------------------------------------------- | | ||||
| | APP_PORT               | Number             | 4010                 | The port the backend server listens on. Is optional.                                                           | | ||||
| | DB_TYPE                | String             | N/A                  | The type of the db u want to use. It has to be supported by typeorm. Possible: `sqlite`, `mysql`, `postgresql` | | ||||
| | DB_HOST                | String             | N/A                  | The db's host's ip-address/fqdn or file path for sqlite                                                        | | ||||
| | DB_PORT                | String             | N/A                  | The db's port                                                                                                  | | ||||
| | DB_USER                | String             | N/A                  | The user for accessing the db                                                                                  | | ||||
| | DB_PASSWORD            | String             | N/A                  | The user's password for accessing the db                                                                       | | ||||
| | DB_NAME                | String             | N/A                  | The db's name                                                                                                  | | ||||
| | NODE_ENV               | String             | dev                  | The apps env - influences debug info. Also when the env is set to "test", mailing errors get ignored.          | | ||||
| | POSTALCODE_COUNTRYCODE | String/CountryCode | N/A                  | The countrycode used to validate address's postal codes                                                        | | ||||
| | PHONE_COUNTRYCODE      | String/CountryCode | null (international) | The countrycode used to validate phone numers                                                                  | | ||||
| | SEED_TEST_DATA         | Boolean            | False                | If you want the app to seed some example data set this to true                                                 | | ||||
| | MAILER_URL             | String(Url)        | N/A                  | The mailer's base url (no trailing slash)                                                                      | | ||||
| | MAILER_KEY             | String             | N/A                  | The mailer's api key.                                                                                          | | ||||
| | SELFSERVICE_URL        | String(Url)        | N/A                  | The link to selfservice (no trailing slash)                                                                    | | ||||
| | IMPRINT_URL            | String(Url)        | /imprint             | The link to a imprint page for the system (Defaults to the frontend's imprint)                                 | | ||||
| | PRIVACY_URL            | String(Url)        | /privacy             | The link to a privacy page for the system (Defaults to the frontend's privacy page)                            | | ||||
|  | ||||
|  | ||||
| ## Recommended Editor | ||||
| @@ -85,10 +79,10 @@ yarn docs | ||||
|    * A new release tag automaticly triggers the release ci pipeline | ||||
| * main: Protected "release" branch | ||||
|    * The latest tag of the docker image get's build from this | ||||
|    * New releases get created as tags from this    | ||||
| * dev: Current dev branch for merging the different feature branches and bugfixes | ||||
|    * New releases get created as tags from this    | ||||
|    * The dev tag of the docker image get's build from this | ||||
|    * Only push minor changes to this branch! | ||||
|    * To merge a feature branch into this please create a pull request | ||||
| * feature/xyz: Feature branches - nameing scheme: `feature/issueid-title` | ||||
| * bugfix/xyz: Branches for bugfixes - nameing scheme:`bugfix/issueid-title` | ||||
| * feature/xyz: Feature branches - naming scheme: `feature/issueid-title` | ||||
| * bugfix/xyz: Branches for bugfixes - naming scheme:`bugfix/issueid-title` | ||||
| @@ -1,4 +1,3 @@ | ||||
| version: "3" | ||||
| services: | ||||
|   backend_server: | ||||
|     build: . | ||||
| @@ -11,8 +10,12 @@ services: | ||||
|       DB_PORT: bla | ||||
|       DB_USER: bla | ||||
|       DB_PASSWORD: bla | ||||
|       DB_NAME: dev.sqlite | ||||
|       DB_NAME: ./db.sqlite | ||||
|       NODE_ENV: production | ||||
|       POSTALCODE_COUNTRYCODE: DE | ||||
|       SEED_TEST_DATA: "true" | ||||
|       MAILER_URL: https://dev.lauf-fuer-kaya.de/mailer | ||||
|       MAILER_KEY: asdasd | ||||
|       # APP_PORT: 4010 | ||||
|       # DB_TYPE: postgres | ||||
|       # DB_HOST: backend_db | ||||
|   | ||||
							
								
								
									
										176
									
								
								licenses.md
									
									
									
									
									
								
							
							
						
						
									
										176
									
								
								licenses.md
									
									
									
									
									
								
							| @@ -1,3 +1,32 @@ | ||||
| # @node-rs/argon2 | ||||
| **Author**: undefined | ||||
| **Repo**: [object Object] | ||||
| **License**: MIT | ||||
| **Description**: RustCrypto: Argon2 binding for Node.js | ||||
| ## License Text | ||||
| MIT License | ||||
|  | ||||
| Copyright (c) 2020-present LongYinan | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
|   | ||||
|  | ||||
| # @odit/class-validator-jsonschema | ||||
| **Author**: Aleksi Pekkala <aleksipekkala@gmail.com> | ||||
| **Repo**: git@github.com:epiphone/class-validator-jsonschema.git | ||||
| @@ -27,36 +56,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
|   | ||||
|  | ||||
| # argon2 | ||||
| **Author**: Ranieri Althoff <ranisalt+argon2@gmail.com> | ||||
| **Repo**: [object Object] | ||||
| **License**: MIT | ||||
| **Description**: An Argon2 library for Node | ||||
| ## License Text | ||||
| The MIT License (MIT) | ||||
|  | ||||
| Copyright (c) 2015 Ranieri Althoff | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
|  | ||||
|   | ||||
|  | ||||
| # axios | ||||
| **Author**: Matt Zabriskie | ||||
| **Repo**: [object Object] | ||||
| @@ -444,6 +443,25 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
| **License**: MIT | ||||
| **Description**: A node.js driver for mysql. It is written in JavaScript, does not require compiling, and is 100% MIT licensed. | ||||
| ## License Text | ||||
| Copyright (c) 2012 Felix Geisendörfer (felix@debuggable.com) and contributors | ||||
|  | ||||
|  Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  of this software and associated documentation files (the "Software"), to deal | ||||
|  in the Software without restriction, including without limitation the rights | ||||
|  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  copies of the Software, and to permit persons to whom the Software is | ||||
|  furnished to do so, subject to the following conditions: | ||||
|  | ||||
|  The above copyright notice and this permission notice shall be included in | ||||
|  all copies or substantial portions of the Software. | ||||
|  | ||||
|  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
|  THE SOFTWARE. | ||||
|   | ||||
|  | ||||
| # pg | ||||
| @@ -696,6 +714,75 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||||
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|   | ||||
|  | ||||
| # @faker-js/faker | ||||
| **Author**: undefined | ||||
| **Repo**: [object Object] | ||||
| **License**: MIT | ||||
| **Description**: Generate massive amounts of fake contextual data | ||||
| ## License Text | ||||
| Faker - Copyright (c) 2022 | ||||
|  | ||||
| This software consists of voluntary contributions made by many individuals. | ||||
| For exact contribution history, see the revision history | ||||
| available at https://github.com/faker-js/faker | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining | ||||
| a copy of this software and associated documentation files (the | ||||
| "Software"), to deal in the Software without restriction, including | ||||
| without limitation the rights to use, copy, modify, merge, publish, | ||||
| distribute, sublicense, and/or sell copies of the Software, and to | ||||
| permit persons to whom the Software is furnished to do so, subject to | ||||
| the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be | ||||
| included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||||
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||||
| LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||||
| OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||||
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
| === | ||||
|  | ||||
| From: https://github.com/faker-js/faker/commit/a9f98046c7d5eeaabe12fc587024c06d683800b8 | ||||
| To: https://github.com/faker-js/faker/commit/29234378807c4141588861f69421bf20b5ac635e | ||||
|  | ||||
| Based on faker.js, copyright Marak Squires and contributor, what follows below is the original license. | ||||
|  | ||||
| === | ||||
|  | ||||
| faker.js - Copyright (c) 2020 | ||||
| Marak Squires | ||||
| http://github.com/marak/faker.js/ | ||||
|  | ||||
| faker.js was inspired by and has used data definitions from: | ||||
|  | ||||
|  * https://github.com/stympy/faker/ - Copyright (c) 2007-2010 Benjamin Curtis | ||||
|  * http://search.cpan.org/~jasonk/Data-Faker-0.07/ - Copyright 2004-2005 by Jason Kohles | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining | ||||
| a copy of this software and associated documentation files (the | ||||
| "Software"), to deal in the Software without restriction, including | ||||
| without limitation the rights to use, copy, modify, merge, publish, | ||||
| distribute, sublicense, and/or sell copies of the Software, and to | ||||
| permit persons to whom the Software is furnished to do so, subject to | ||||
| the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be | ||||
| included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||||
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||||
| LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||||
| OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||||
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|   | ||||
|  | ||||
| # @odit/license-exporter | ||||
| **Author**: ODIT.Services | ||||
| **Repo**: [object Object] | ||||
| @@ -926,6 +1013,35 @@ OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|     SOFTWARE | ||||
|   | ||||
|  | ||||
| # auto-changelog | ||||
| **Author**: Pete Cook <pete@cookpete.com> (https://github.com/cookpete) | ||||
| **Repo**: [object Object] | ||||
| **License**: MIT | ||||
| **Description**: Command line tool for generating a changelog from git tags and commit history | ||||
| ## License Text | ||||
| The MIT License | ||||
|  | ||||
| Copyright (c) 2017 Pete Cook https://cookpete.com | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in | ||||
| all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| THE SOFTWARE. | ||||
|   | ||||
|  | ||||
| # cp-cli | ||||
| **Author**: undefined | ||||
| **Repo**: [object Object] | ||||
|   | ||||
							
								
								
									
										220
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										220
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,106 +1,114 @@ | ||||
| { | ||||
|   "name": "@odit/lfk-backend", | ||||
|   "version": "0.9.0", | ||||
|   "main": "src/app.ts", | ||||
|   "repository": "https://git.odit.services/lfk/backend", | ||||
|   "author": { | ||||
|     "name": "ODIT.Services", | ||||
|     "email": "info@odit.services", | ||||
|     "url": "https://odit.services" | ||||
|   }, | ||||
|   "contributors": [ | ||||
|     { | ||||
|       "name": "Philipp Dormann", | ||||
|       "email": "philipp@philippdormann.de", | ||||
|       "url": "https://philippdormann.de" | ||||
|     }, | ||||
|     { | ||||
|       "name": "Nicolai Ort", | ||||
|       "email": "info@nicolai-ort.com", | ||||
|       "url": "https://nicolai-ort.com" | ||||
|     } | ||||
|   ], | ||||
|   "license": "CC-BY-NC-SA-4.0", | ||||
|   "dependencies": { | ||||
|     "@odit/class-validator-jsonschema": "2.1.1", | ||||
|     "argon2": "^0.27.1", | ||||
|     "axios": "^0.21.1", | ||||
|     "body-parser": "^1.19.0", | ||||
|     "check-password-strength": "^2.0.2", | ||||
|     "class-transformer": "0.3.1", | ||||
|     "class-validator": "^0.13.1", | ||||
|     "consola": "^2.15.0", | ||||
|     "cookie": "^0.4.1", | ||||
|     "cookie-parser": "^1.4.5", | ||||
|     "cors": "^2.8.5", | ||||
|     "csvtojson": "^2.0.10", | ||||
|     "dotenv": "^8.2.0", | ||||
|     "express": "^4.17.1", | ||||
|     "jsonwebtoken": "^8.5.1", | ||||
|     "libphonenumber-js": "^1.9.9", | ||||
|     "mysql": "^2.18.1", | ||||
|     "pg": "^8.5.1", | ||||
|     "reflect-metadata": "^0.1.13", | ||||
|     "routing-controllers": "0.9.0-alpha.6", | ||||
|     "routing-controllers-openapi": "^2.2.0", | ||||
|     "sqlite3": "5.0.0", | ||||
|     "typeorm": "^0.2.30", | ||||
|     "typeorm-routing-controllers-extensions": "^0.2.0", | ||||
|     "typeorm-seeding": "^1.6.1", | ||||
|     "uuid": "^8.3.2", | ||||
|     "validator": "^13.5.2" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@odit/license-exporter": "^0.0.9", | ||||
|     "@types/cors": "^2.8.9", | ||||
|     "@types/csvtojson": "^1.1.5", | ||||
|     "@types/express": "^4.17.11", | ||||
|     "@types/jest": "^26.0.20", | ||||
|     "@types/jsonwebtoken": "^8.5.0", | ||||
|     "@types/node": "^14.14.22", | ||||
|     "@types/uuid": "^8.3.0", | ||||
|     "cp-cli": "^2.0.0", | ||||
|     "jest": "^26.6.3", | ||||
|     "nodemon": "^2.0.7", | ||||
|     "release-it": "^14.2.2", | ||||
|     "rimraf": "^3.0.2", | ||||
|     "start-server-and-test": "^1.11.7", | ||||
|     "ts-jest": "^26.5.0", | ||||
|     "ts-node": "^9.1.1", | ||||
|     "typedoc": "^0.20.19", | ||||
|     "typescript": "^4.1.3" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "dev": "nodemon src/app.ts", | ||||
|     "build": "rimraf ./dist && tsc && cp-cli ./src/static ./dist/static", | ||||
|     "docs": "typedoc --out docs src", | ||||
|     "test": "jest", | ||||
|     "test:watch": "jest --watchAll", | ||||
|     "test:ci:generate_env": "ts-node scripts/create_testenv.ts", | ||||
|     "test:ci:run": "start-server-and-test dev http://localhost:4010/api/docs/openapi.json test", | ||||
|     "test:ci": "npm run test:ci:generate_env && npm run test:ci:run", | ||||
|     "seed": "ts-node ./node_modules/typeorm/cli.js schema:sync && ts-node ./node_modules/typeorm-seeding/dist/cli.js seed", | ||||
|     "openapi:export": "ts-node scripts/openapi_export.ts", | ||||
|     "licenses:export": "license-exporter --markdown", | ||||
|     "release": "release-it --only-version" | ||||
|   }, | ||||
|   "release-it": { | ||||
|     "git": { | ||||
|       "commit": true, | ||||
|       "requireCleanWorkingDir": false, | ||||
|       "commitMessage": "🚀Bumped version to v${version}", | ||||
|       "requireBranch": "dev", | ||||
|       "push": false, | ||||
|       "tag": false | ||||
|     }, | ||||
|     "npm": { | ||||
|       "publish": false | ||||
|     } | ||||
|   }, | ||||
|   "nodemonConfig": { | ||||
|     "ignore": [ | ||||
|       "src/tests/*", | ||||
|       "docs/*" | ||||
|     ] | ||||
|   } | ||||
| } | ||||
| { | ||||
|   "name": "@odit/lfk-backend", | ||||
|   "version": "1.3.11", | ||||
|   "main": "src/app.ts", | ||||
|   "repository": "https://git.odit.services/lfk/backend", | ||||
|   "author": { | ||||
|     "name": "ODIT.Services", | ||||
|     "email": "info@odit.services", | ||||
|     "url": "https://odit.services" | ||||
|   }, | ||||
|   "contributors": [ | ||||
|     { | ||||
|       "name": "Philipp Dormann", | ||||
|       "email": "philipp@philippdormann.de", | ||||
|       "url": "https://philippdormann.de" | ||||
|     }, | ||||
|     { | ||||
|       "name": "Nicolai Ort", | ||||
|       "email": "info@nicolai-ort.com", | ||||
|       "url": "https://nicolai-ort.com" | ||||
|     } | ||||
|   ], | ||||
|   "license": "CC-BY-NC-SA-4.0", | ||||
|   "dependencies": { | ||||
|     "@node-rs/argon2": "^2.0.2", | ||||
|     "@odit/class-validator-jsonschema": "2.1.1", | ||||
|     "axios": "0.21.1", | ||||
|     "body-parser": "1.19.0", | ||||
|     "check-password-strength": "2.0.2", | ||||
|     "class-transformer": "0.3.1", | ||||
|     "class-validator": "0.13.0", | ||||
|     "consola": "2.15.0", | ||||
|     "cookie": "0.4.1", | ||||
|     "cookie-parser": "1.4.5", | ||||
|     "cors": "2.8.5", | ||||
|     "csvtojson": "2.0.10", | ||||
|     "dotenv": "8.2.0", | ||||
|     "express": "4.17.1", | ||||
|     "jsonwebtoken": "8.5.1", | ||||
|     "libphonenumber-js": "1.9.9", | ||||
|     "mysql": "2.18.1", | ||||
|     "pg": "8.5.1", | ||||
|     "reflect-metadata": "0.1.13", | ||||
|     "routing-controllers": "0.9.0-alpha.6", | ||||
|     "routing-controllers-openapi": "2.2.0", | ||||
|     "sqlite3": "5.1.7", | ||||
|     "typeorm": "0.2.30", | ||||
|     "typeorm-routing-controllers-extensions": "0.2.0", | ||||
|     "typeorm-seeding": "1.6.1", | ||||
|     "uuid": "8.3.2", | ||||
|     "validator": "13.5.2" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@faker-js/faker": "7.6.0", | ||||
|     "@odit/license-exporter": "0.0.9", | ||||
|     "@types/cors": "2.8.9", | ||||
|     "@types/csvtojson": "1.1.5", | ||||
|     "@types/express": "4.17.11", | ||||
|     "@types/jest": "26.0.20", | ||||
|     "@types/jsonwebtoken": "8.5.0", | ||||
|     "@types/node": "14.14.22", | ||||
|     "@types/uuid": "8.3.0", | ||||
|     "auto-changelog": "2.4.0", | ||||
|     "cp-cli": "2.0.0", | ||||
|     "jest": "26.6.3", | ||||
|     "nodemon": "2.0.7", | ||||
|     "release-it": "14.2.2", | ||||
|     "rimraf": "3.0.2", | ||||
|     "start-server-and-test": "1.11.7", | ||||
|     "ts-jest": "26.5.0", | ||||
|     "ts-node": "9.1.1", | ||||
|     "typedoc": "0.20.19", | ||||
|     "typescript": "4.1.3" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "dev": "nodemon src/app.ts", | ||||
|     "build": "rimraf ./dist && tsc && cp-cli ./src/static ./dist/static", | ||||
|     "docs": "typedoc --out docs src", | ||||
|     "test": "jest", | ||||
|     "test:watch": "jest --watchAll", | ||||
|     "test:ci:generate_env": "ts-node scripts/create_testenv.ts", | ||||
|     "test:ci:run": "start-server-and-test dev http://localhost:4010/api/docs/openapi.json test", | ||||
|     "test:ci": "npm run test:ci:generate_env && npm run test:ci:run", | ||||
|     "seed": "ts-node ./node_modules/typeorm/cli.js schema:sync && ts-node ./node_modules/typeorm-seeding/dist/cli.js seed", | ||||
|     "openapi:export": "ts-node scripts/openapi_export.ts", | ||||
|     "licenses:export": "license-exporter --markdown", | ||||
|     "changelog:export": "auto-changelog --commit-limit false -p -u --hide-credit", | ||||
|     "release": "release-it --only-version" | ||||
|   }, | ||||
|   "release-it": { | ||||
|     "git": { | ||||
|       "commit": true, | ||||
|       "requireCleanWorkingDir": false, | ||||
|       "commitMessage": "chore(release): ${version}", | ||||
|       "requireBranch": "dev", | ||||
|       "push": true, | ||||
|       "tag": true, | ||||
|       "tagName": "${version}", | ||||
|       "tagAnnotation": "${version}" | ||||
|     }, | ||||
|     "npm": { | ||||
|       "publish": false | ||||
|     }, | ||||
|     "hooks": { | ||||
|       "after:bump": "npm run changelog:export && npm run licenses:export && git add CHANGELOG.md && git add licenses.md" | ||||
|     } | ||||
|   }, | ||||
|   "nodemonConfig": { | ||||
|     "ignore": [ | ||||
|       "src/tests/*", | ||||
|       "docs/*" | ||||
|     ] | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										9134
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										9134
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										2
									
								
								pnpm-workspace.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								pnpm-workspace.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| onlyBuiltDependencies: | ||||
|   - sqlite3 | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { Repository, getConnectionManager } from 'typeorm'; | ||||
| import { DonationIdsNotMatchingError, DonationNotFoundError } from '../errors/DonationErrors'; | ||||
| import { DonorNotFoundError } from '../errors/DonorErrors'; | ||||
| import { RunnerNotFoundError } from '../errors/RunnerErrors'; | ||||
| @@ -36,9 +36,16 @@ export class DonationController { | ||||
| 	@ResponseSchema(ResponseDonation, { isArray: true }) | ||||
| 	@ResponseSchema(ResponseDistanceDonation, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all donations (fixed or distance based) from all donors. <br> This includes the donations\'s runner\'s distance ran(if distance donation).' }) | ||||
| 	async getAll() { | ||||
| 	async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { | ||||
| 		let responseDonations: ResponseDonation[] = new Array<ResponseDonation>(); | ||||
| 		const donations = await this.donationRepository.find({ relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] }); | ||||
| 		let donations: Array<Donation>; | ||||
|  | ||||
| 		if (page != undefined) { | ||||
| 			donations = await this.donationRepository.find({ relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'], skip: page * page_size, take: page_size }); | ||||
| 		} else { | ||||
| 			donations = await this.donationRepository.find({ relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] }); | ||||
| 		} | ||||
|  | ||||
| 		donations.forEach(donation => { | ||||
| 			responseDonations.push(donation.toResponse()); | ||||
| 		}); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { Repository, getConnectionManager } from 'typeorm'; | ||||
| import { DonorHasDonationsError, DonorIdsNotMatchingError, DonorNotFoundError } from '../errors/DonorErrors'; | ||||
| import { CreateDonor } from '../models/actions/create/CreateDonor'; | ||||
| import { UpdateDonor } from '../models/actions/update/UpdateDonor'; | ||||
| @@ -25,9 +25,16 @@ export class DonorController { | ||||
| 	@Authorized("DONOR:GET") | ||||
| 	@ResponseSchema(ResponseDonor, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all donor. <br> This includes the donor\'s current donation amount.' }) | ||||
| 	async getAll() { | ||||
| 	async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { | ||||
| 		let responseDonors: ResponseDonor[] = new Array<ResponseDonor>(); | ||||
| 		const donors = await this.donorRepository.find({ relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] }); | ||||
| 		let donors: Array<Donor>; | ||||
|  | ||||
| 		if (page != undefined) { | ||||
| 			donors = await this.donorRepository.find({ relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'], skip: page * page_size, take: page_size }); | ||||
| 		} else { | ||||
| 			donors = await this.donorRepository.find({ relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] }); | ||||
| 		} | ||||
|  | ||||
| 		donors.forEach(donor => { | ||||
| 			responseDonors.push(new ResponseDonor(donor)); | ||||
| 		}); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnection, getConnectionManager, Repository } from 'typeorm'; | ||||
| import { Repository, getConnection, getConnectionManager } from 'typeorm'; | ||||
| import { GroupContactIdsNotMatchingError, GroupContactNotFoundError } from '../errors/GroupContactErrors'; | ||||
| import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors'; | ||||
| import { CreateGroupContact } from '../models/actions/create/CreateGroupContact'; | ||||
| @@ -26,9 +26,16 @@ export class GroupContactController { | ||||
| 	@Authorized("CONTACT:GET") | ||||
| 	@ResponseSchema(ResponseGroupContact, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all contacts. <br> This includes the contact\'s associated groups.' }) | ||||
| 	async getAll() { | ||||
| 	async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { | ||||
| 		let responseContacts: ResponseGroupContact[] = new Array<ResponseGroupContact>(); | ||||
| 		const contacts = await this.contactRepository.find({ relations: ['groups', 'groups.parentGroup'] }); | ||||
| 		let contacts: Array<GroupContact>; | ||||
|  | ||||
| 		if (page != undefined) { | ||||
| 			contacts = await this.contactRepository.find({ relations: ['groups', 'groups.parentGroup'], skip: page * page_size, take: page_size }); | ||||
| 		} else { | ||||
| 			contacts = await this.contactRepository.find({ relations: ['groups', 'groups.parentGroup'] }); | ||||
| 		} | ||||
|  | ||||
| 		contacts.forEach(contact => { | ||||
| 			responseContacts.push(contact.toResponse()); | ||||
| 		}); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { Repository, getConnectionManager } from 'typeorm'; | ||||
| import { PermissionIdsNotMatchingError, PermissionNeedsPrincipalError, PermissionNotFoundError } from '../errors/PermissionErrors'; | ||||
| import { PrincipalNotFoundError } from '../errors/PrincipalErrors'; | ||||
| import { CreatePermission } from '../models/actions/create/CreatePermission'; | ||||
| @@ -27,9 +27,16 @@ export class PermissionController { | ||||
|     @Authorized("PERMISSION:GET") | ||||
|     @ResponseSchema(ResponsePermission, { isArray: true }) | ||||
|     @OpenAPI({ description: 'Lists all permissions for all users and groups.' }) | ||||
|     async getAll() { | ||||
|     async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { | ||||
|         let responsePermissions: ResponsePermission[] = new Array<ResponsePermission>(); | ||||
|         const permissions = await this.permissionRepository.find({ relations: ['principal'] }); | ||||
|         let permissions: Array<Permission>; | ||||
|  | ||||
|         if (page != undefined) { | ||||
|             permissions = await this.permissionRepository.find({ relations: ['principal'], skip: page * page_size, take: page_size }); | ||||
|         } else { | ||||
|             permissions = await this.permissionRepository.find({ relations: ['principal'] }); | ||||
|         } | ||||
|  | ||||
|         permissions.forEach(permission => { | ||||
|             responsePermissions.push(new ResponsePermission(permission)); | ||||
|         }); | ||||
|   | ||||
| @@ -1,121 +1,161 @@ | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { RunnerCardHasScansError, RunnerCardIdsNotMatchingError, RunnerCardNotFoundError } from '../errors/RunnerCardErrors'; | ||||
| import { RunnerNotFoundError } from '../errors/RunnerErrors'; | ||||
| import { CreateRunnerCard } from '../models/actions/create/CreateRunnerCard'; | ||||
| import { UpdateRunnerCard } from '../models/actions/update/UpdateRunnerCard'; | ||||
| import { RunnerCard } from '../models/entities/RunnerCard'; | ||||
| import { ResponseEmpty } from '../models/responses/ResponseEmpty'; | ||||
| import { ResponseRunnerCard } from '../models/responses/ResponseRunnerCard'; | ||||
| import { ScanController } from './ScanController'; | ||||
|  | ||||
| @JsonController('/cards') | ||||
| @OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
| export class RunnerCardController { | ||||
| 	private cardRepository: Repository<RunnerCard>; | ||||
|  | ||||
| 	/** | ||||
| 	 * Gets the repository of this controller's model/entity. | ||||
| 	 */ | ||||
| 	constructor() { | ||||
| 		this.cardRepository = getConnectionManager().get().getRepository(RunnerCard); | ||||
| 	} | ||||
|  | ||||
| 	@Get() | ||||
| 	@Authorized("CARD:GET") | ||||
| 	@ResponseSchema(ResponseRunnerCard, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all card.' }) | ||||
| 	async getAll() { | ||||
| 		let responseCards: ResponseRunnerCard[] = new Array<ResponseRunnerCard>(); | ||||
| 		const cards = await this.cardRepository.find({ relations: ['runner', 'runner.group', 'runner.group.parentGroup'] }); | ||||
| 		cards.forEach(card => { | ||||
| 			responseCards.push(new ResponseRunnerCard(card)); | ||||
| 		}); | ||||
| 		return responseCards; | ||||
| 	} | ||||
|  | ||||
| 	@Get('/:id') | ||||
| 	@Authorized("CARD:GET") | ||||
| 	@ResponseSchema(ResponseRunnerCard) | ||||
| 	@ResponseSchema(RunnerCardNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(RunnerCardNotFoundError) | ||||
| 	@OpenAPI({ description: "Lists all information about the card whose id got provided." }) | ||||
| 	async getOne(@Param('id') id: number) { | ||||
| 		let card = await this.cardRepository.findOne({ id: id }, { relations: ['runner', 'runner.group', 'runner.group.parentGroup'] }); | ||||
| 		if (!card) { throw new RunnerCardNotFoundError(); } | ||||
| 		return card.toResponse(); | ||||
| 	} | ||||
|  | ||||
| 	@Post('/bulk') | ||||
| 	@Authorized("CARD:CREATE") | ||||
| 	@ResponseSchema(ResponseEmpty, { statusCode: 200 }) | ||||
| 	@OpenAPI({ description: "Create blank cards in bulk. <br> Just provide the count as a query param and wait for the 200 response." }) | ||||
| 	async postBlancoBulk(@QueryParam("count") count: number) { | ||||
| 		let createPromises = new Array<any>(); | ||||
| 		for (let index = 0; index < count; index++) { | ||||
| 			createPromises.push(this.cardRepository.save({ runner: null, enabled: true })) | ||||
| 		} | ||||
| 		await Promise.all(createPromises); | ||||
| 		let response = new ResponseEmpty(); | ||||
| 		response.response = `Created ${count} new blanco cards.` | ||||
| 		return response; | ||||
| 	} | ||||
|  | ||||
| 	@Post() | ||||
| 	@Authorized("CARD:CREATE") | ||||
| 	@ResponseSchema(ResponseRunnerCard) | ||||
| 	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) | ||||
| 	@OpenAPI({ description: "Create a new card. <br> You can provide a associated runner by id but you don't have to." }) | ||||
| 	async post(@Body({ validate: true }) createCard: CreateRunnerCard) { | ||||
| 		let card = await createCard.toEntity(); | ||||
| 		card = await this.cardRepository.save(card); | ||||
| 		return (await this.cardRepository.findOne({ id: card.id }, { relations: ['runner', 'runner.group', 'runner.group.parentGroup'] })).toResponse(); | ||||
| 	} | ||||
|  | ||||
| 	@Put('/:id') | ||||
| 	@Authorized("CARD:UPDATE") | ||||
| 	@ResponseSchema(ResponseRunnerCard) | ||||
| 	@ResponseSchema(RunnerCardNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(RunnerCardIdsNotMatchingError, { statusCode: 406 }) | ||||
| 	@OpenAPI({ description: "Update the card whose id you provided. <br> Scans created via this card will still be associated with the old runner. <br> Please remember that ids can't be changed." }) | ||||
| 	async put(@Param('id') id: number, @Body({ validate: true }) card: UpdateRunnerCard) { | ||||
| 		let oldCard = await this.cardRepository.findOne({ id: id }); | ||||
|  | ||||
| 		if (!oldCard) { | ||||
| 			throw new RunnerCardNotFoundError(); | ||||
| 		} | ||||
|  | ||||
| 		if (oldCard.id != card.id) { | ||||
| 			throw new RunnerCardIdsNotMatchingError(); | ||||
| 		} | ||||
|  | ||||
| 		await this.cardRepository.save(await card.update(oldCard)); | ||||
| 		return (await this.cardRepository.findOne({ id: id }, { relations: ['runner', 'runner.group', 'runner.group.parentGroup'] })).toResponse(); | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/:id') | ||||
| 	@Authorized("CARD:DELETE") | ||||
| 	@ResponseSchema(ResponseRunnerCard) | ||||
| 	@ResponseSchema(ResponseEmpty, { statusCode: 204 }) | ||||
| 	@ResponseSchema(RunnerCardHasScansError, { statusCode: 406 }) | ||||
| 	@OnUndefined(204) | ||||
| 	@OpenAPI({ description: "Delete the card whose id you provided. <br> If no card with this id exists it will just return 204(no content). <br> If the card still has scans associated you have to provide the force=true query param (warning: this deletes all scans associated with by this card - please disable it instead or just remove the runner association)." }) | ||||
| 	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { | ||||
| 		let card = await this.cardRepository.findOne({ id: id }); | ||||
| 		if (!card) { return null; } | ||||
|  | ||||
| 		const cardScans = (await this.cardRepository.findOne({ id: id }, { relations: ["scans"] })).scans; | ||||
| 		if (cardScans.length != 0 && !force) { | ||||
| 			throw new RunnerCardHasScansError(); | ||||
| 		} | ||||
| 		const scanController = new ScanController; | ||||
| 		for (let scan of cardScans) { | ||||
| 			await scanController.remove(scan.id, force); | ||||
| 		} | ||||
|  | ||||
| 		await this.cardRepository.delete(card); | ||||
| 		return card.toResponse(); | ||||
| 	} | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { Repository, getConnectionManager } from 'typeorm'; | ||||
| import { RunnerCardHasScansError, RunnerCardIdsNotMatchingError, RunnerCardNotFoundError } from '../errors/RunnerCardErrors'; | ||||
| import { RunnerNotFoundError } from '../errors/RunnerErrors'; | ||||
| import { CreateRunnerCard } from '../models/actions/create/CreateRunnerCard'; | ||||
| import { UpdateRunnerCard } from '../models/actions/update/UpdateRunnerCard'; | ||||
| import { UpdateRunnerCardByCode } from '../models/actions/update/UpdateRunnerCardByCode'; | ||||
| import { RunnerCard } from '../models/entities/RunnerCard'; | ||||
| import { ResponseEmpty } from '../models/responses/ResponseEmpty'; | ||||
| import { ResponseRunnerCard } from '../models/responses/ResponseRunnerCard'; | ||||
| import { ScanController } from './ScanController'; | ||||
|  | ||||
| @JsonController('/cards') | ||||
| @OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
| export class RunnerCardController { | ||||
| 	private cardRepository: Repository<RunnerCard>; | ||||
|  | ||||
| 	/** | ||||
| 	 * Gets the repository of this controller's model/entity. | ||||
| 	 */ | ||||
| 	constructor() { | ||||
| 		this.cardRepository = getConnectionManager().get().getRepository(RunnerCard); | ||||
| 	} | ||||
|  | ||||
| 	@Get() | ||||
| 	@Authorized("CARD:GET") | ||||
| 	@ResponseSchema(ResponseRunnerCard, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all card.' }) | ||||
| 	async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { | ||||
| 		let responseCards: ResponseRunnerCard[] = new Array<ResponseRunnerCard>(); | ||||
| 		let cards: Array<RunnerCard>; | ||||
|  | ||||
| 		if (page != undefined) { | ||||
| 			cards = await this.cardRepository.find({ relations: ['runner', 'runner.group', 'runner.group.parentGroup'], skip: page * page_size, take: page_size }); | ||||
| 		} else { | ||||
| 			cards = await this.cardRepository.find({ relations: ['runner', 'runner.group', 'runner.group.parentGroup'] }); | ||||
| 		} | ||||
|  | ||||
| 		cards.forEach(card => { | ||||
| 			responseCards.push(new ResponseRunnerCard(card)); | ||||
| 		}); | ||||
| 		return responseCards; | ||||
| 	} | ||||
|  | ||||
| 	@Get('/:id') | ||||
| 	@Authorized("CARD:GET") | ||||
| 	@ResponseSchema(ResponseRunnerCard) | ||||
| 	@ResponseSchema(RunnerCardNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(RunnerCardNotFoundError) | ||||
| 	@OpenAPI({ description: "Lists all information about the card whose id got provided." }) | ||||
| 	async getOne(@Param('id') id: number) { | ||||
| 		let card = await this.cardRepository.findOne({ id: id }, { relations: ['runner', 'runner.group', 'runner.group.parentGroup'] }); | ||||
| 		if (!card) { throw new RunnerCardNotFoundError(); } | ||||
| 		return card.toResponse(); | ||||
| 	} | ||||
|  | ||||
| 	@Post('/bulk') | ||||
| 	@Authorized("CARD:CREATE") | ||||
| 	@ResponseSchema(ResponseEmpty, { statusCode: 200 }) | ||||
| 	@OpenAPI({ description: "Create blank cards in bulk. <br> Just provide the count as a query param and wait for the 200 response. <br> You can provide the 'returnCards' query param if you want to receive the RESPONSERUNNERCARD objects in the response." }) | ||||
| 	async postBlancoBulk(@QueryParam("count") count: number, @QueryParam("returnCards") returnCards: boolean = false) { | ||||
| 		let createPromises = new Array<any>(); | ||||
| 		for (let index = 0; index < count; index++) { | ||||
| 			createPromises.push(this.cardRepository.save({ runner: null, enabled: true })) | ||||
| 		} | ||||
|  | ||||
| 		const cards = await Promise.all(createPromises); | ||||
|  | ||||
| 		if (returnCards) { | ||||
| 			let responseCards: ResponseRunnerCard[] = new Array<ResponseRunnerCard>(); | ||||
| 			for await (let card of cards) { | ||||
| 				let dbCard = await this.cardRepository.findOne({ id: card.id }); | ||||
| 				responseCards.push(new ResponseRunnerCard(dbCard)); | ||||
| 			} | ||||
| 			return responseCards; | ||||
| 		} | ||||
| 		let response = new ResponseEmpty(); | ||||
| 		response.response = `Created ${count} new blanco cards.` | ||||
| 		return response; | ||||
| 	} | ||||
|  | ||||
| 	@Post() | ||||
| 	@Authorized("CARD:CREATE") | ||||
| 	@ResponseSchema(ResponseRunnerCard) | ||||
| 	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) | ||||
| 	@OpenAPI({ description: "Create a new card. <br> You can provide a associated runner by id but you don't have to." }) | ||||
| 	async post(@Body({ validate: true }) createCard: CreateRunnerCard) { | ||||
| 		let card = await createCard.toEntity(); | ||||
| 		card = await this.cardRepository.save(card); | ||||
| 		return (await this.cardRepository.findOne({ id: card.id }, { relations: ['runner', 'runner.group', 'runner.group.parentGroup'] })).toResponse(); | ||||
| 	} | ||||
|  | ||||
| 	@Put('/:id') | ||||
| 	@Authorized("CARD:UPDATE") | ||||
| 	@ResponseSchema(ResponseRunnerCard) | ||||
| 	@ResponseSchema(RunnerCardNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(RunnerCardIdsNotMatchingError, { statusCode: 406 }) | ||||
| 	@OpenAPI({ description: "Update the card whose id you provided. <br> Scans created via this card will still be associated with the old runner. <br> Please remember that ids can't be changed." }) | ||||
| 	async put(@Param('id') id: number, @Body({ validate: true }) card: UpdateRunnerCard) { | ||||
| 		let oldCard = await this.cardRepository.findOne({ id: id }); | ||||
|  | ||||
| 		if (!oldCard) { | ||||
| 			throw new RunnerCardNotFoundError(); | ||||
| 		} | ||||
|  | ||||
| 		if (oldCard.id != card.id) { | ||||
| 			throw new RunnerCardIdsNotMatchingError(); | ||||
| 		} | ||||
|  | ||||
| 		await this.cardRepository.save(await card.update(oldCard)); | ||||
| 		return (await this.cardRepository.findOne({ id: id }, { relations: ['runner', 'runner.group', 'runner.group.parentGroup'] })).toResponse(); | ||||
| 	} | ||||
|  | ||||
| 	@Put('/:code') | ||||
| 	@Authorized("CARD:UPDATE") | ||||
| 	@ResponseSchema(ResponseRunnerCard) | ||||
| 	@ResponseSchema(RunnerCardNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(RunnerCardIdsNotMatchingError, { statusCode: 406 }) | ||||
| 	@OpenAPI({ description: "Update the card whose code you provided." }) | ||||
| 	async putByCode(@Param('code') code: string, @Body({ validate: true }) card: UpdateRunnerCardByCode) { | ||||
| 		let oldCard = await this.cardRepository.findOne({ code: code }); | ||||
|  | ||||
| 		if (!oldCard) { | ||||
| 			throw new RunnerCardNotFoundError(); | ||||
| 		} | ||||
|  | ||||
| 		if (oldCard.code != card.code) { | ||||
| 			throw new RunnerCardIdsNotMatchingError(); | ||||
| 		} | ||||
|  | ||||
| 		await this.cardRepository.save(await card.update(oldCard)); | ||||
| 		return (await this.cardRepository.findOne({ code: code }, { relations: ['runner', 'runner.group', 'runner.group.parentGroup'] })).toResponse(); | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/:id') | ||||
| 	@Authorized("CARD:DELETE") | ||||
| 	@ResponseSchema(ResponseRunnerCard) | ||||
| 	@ResponseSchema(ResponseEmpty, { statusCode: 204 }) | ||||
| 	@ResponseSchema(RunnerCardHasScansError, { statusCode: 406 }) | ||||
| 	@OnUndefined(204) | ||||
| 	@OpenAPI({ description: "Delete the card whose id you provided. <br> If no card with this id exists it will just return 204(no content). <br> If the card still has scans associated you have to provide the force=true query param (warning: this deletes all scans associated with by this card - please disable it instead or just remove the runner association)." }) | ||||
| 	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { | ||||
| 		let card = await this.cardRepository.findOne({ id: id }); | ||||
| 		if (!card) { return null; } | ||||
|  | ||||
| 		const cardScans = (await this.cardRepository.findOne({ id: id }, { relations: ["scans"] })).scans; | ||||
| 		if (cardScans.length != 0 && !force) { | ||||
| 			throw new RunnerCardHasScansError(); | ||||
| 		} | ||||
| 		const scanController = new ScanController; | ||||
| 		for (let scan of cardScans) { | ||||
| 			await scanController.remove(scan.id, force); | ||||
| 		} | ||||
|  | ||||
| 		await this.cardRepository.delete(card); | ||||
| 		return card.toResponse(); | ||||
| 	} | ||||
| } | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { Repository, getConnectionManager } from 'typeorm'; | ||||
| import { RunnerGroupNeededError, RunnerHasDistanceDonationsError, RunnerIdsNotMatchingError, RunnerNotFoundError } from '../errors/RunnerErrors'; | ||||
| import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors'; | ||||
| import { CreateRunner } from '../models/actions/create/CreateRunner'; | ||||
| @@ -30,11 +30,25 @@ export class RunnerController { | ||||
| 	@Authorized("RUNNER:GET") | ||||
| 	@ResponseSchema(ResponseRunner, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all runners from all teams/orgs. <br> This includes the runner\'s group and distance ran.' }) | ||||
| 	async getAll() { | ||||
| 	async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100, @QueryParam("created_via", { required: false }) created_via: string = "all", @QueryParam("selfservice_links", { required: false }) selfservice_links: boolean = false) { | ||||
| 		let responseRunners: ResponseRunner[] = new Array<ResponseRunner>(); | ||||
| 		const runners = await this.runnerRepository.find({ relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] }); | ||||
| 		let runners: Array<Runner>; | ||||
|  | ||||
| 		console.log("call to RunnerController.getAll() with page: " + page + " and page_size: " + page_size + " and created_via: " + created_via + " and selfservice_links: " + selfservice_links); | ||||
| 		if (page != undefined) { | ||||
| 			runners = await this.runnerRepository.find({ relations: ['scans', 'group', 'group.parentGroup', 'scans.track'], skip: page * page_size, take: page_size }); | ||||
| 		} else { | ||||
| 			runners = await this.runnerRepository.find({ relations: ['scans', 'group', 'group.parentGroup', 'scans.track'] }); | ||||
| 		} | ||||
|  | ||||
| 		runners.forEach(runner => { | ||||
| 			responseRunners.push(new ResponseRunner(runner)); | ||||
| 			if (created_via === "all") { | ||||
| 				responseRunners.push(new ResponseRunner(runner, selfservice_links)); | ||||
| 			} else { | ||||
| 				if (runner.created_via === created_via) { | ||||
| 					responseRunners.push(new ResponseRunner(runner, selfservice_links)); | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
| 		return responseRunners; | ||||
| 	} | ||||
| @@ -48,7 +62,7 @@ export class RunnerController { | ||||
| 	async getOne(@Param('id') id: number) { | ||||
| 		let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] }) | ||||
| 		if (!runner) { throw new RunnerNotFoundError(); } | ||||
| 		return new ResponseRunner(runner); | ||||
| 		return new ResponseRunner(runner, true); | ||||
| 	} | ||||
|  | ||||
| 	@Get('/:id/scans') | ||||
| @@ -91,7 +105,7 @@ export class RunnerController { | ||||
| 		} | ||||
|  | ||||
| 		runner = await this.runnerRepository.save(runner) | ||||
| 		return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] })); | ||||
| 		return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] }), true); | ||||
| 	} | ||||
|  | ||||
| 	@Put('/:id') | ||||
| @@ -112,7 +126,7 @@ export class RunnerController { | ||||
| 		} | ||||
|  | ||||
| 		await this.runnerRepository.save(await runner.update(oldRunner)); | ||||
| 		return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] })); | ||||
| 		return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] }), true); | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/:id') | ||||
| @@ -125,7 +139,7 @@ export class RunnerController { | ||||
| 	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { | ||||
| 		let runner = await this.runnerRepository.findOne({ id: id }); | ||||
| 		if (!runner) { return null; } | ||||
| 		const responseRunner = await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] }); | ||||
| 		const responseRunner = await this.runnerRepository.findOne(runner); | ||||
|  | ||||
| 		if (!runner) { | ||||
| 			throw new RunnerNotFoundError(); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { Authorized, BadRequestError, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { Repository, getConnectionManager } from 'typeorm'; | ||||
| import { RunnerOrganizationHasRunnersError, RunnerOrganizationHasTeamsError, RunnerOrganizationIdsNotMatchingError, RunnerOrganizationNotFoundError } from '../errors/RunnerOrganizationErrors'; | ||||
| import { CreateRunnerOrganization } from '../models/actions/create/CreateRunnerOrganization'; | ||||
| import { UpdateRunnerOrganization } from '../models/actions/update/UpdateRunnerOrganization'; | ||||
| @@ -29,13 +29,20 @@ export class RunnerOrganizationController { | ||||
| 	@Authorized("ORGANIZATION:GET") | ||||
| 	@ResponseSchema(ResponseRunnerOrganization, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all organizations. <br> This includes their address, contact and teams (if existing/associated).' }) | ||||
| 	async getAll() { | ||||
| 		let responseTeams: ResponseRunnerOrganization[] = new Array<ResponseRunnerOrganization>(); | ||||
| 		const runners = await this.runnerOrganizationRepository.find({ relations: ['contact', 'teams'] }); | ||||
| 		runners.forEach(runner => { | ||||
| 			responseTeams.push(new ResponseRunnerOrganization(runner)); | ||||
| 	async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { | ||||
| 		let responseOrgs: ResponseRunnerOrganization[] = new Array<ResponseRunnerOrganization>(); | ||||
| 		let orgs: Array<RunnerOrganization>; | ||||
|  | ||||
| 		if (page != undefined) { | ||||
| 			orgs = await this.runnerOrganizationRepository.find({ relations: ['contact', 'teams'], skip: page * page_size, take: page_size }); | ||||
| 		} else { | ||||
| 			orgs = await this.runnerOrganizationRepository.find({ relations: ['contact', 'teams'] }); | ||||
| 		} | ||||
|  | ||||
| 		orgs.forEach(org => { | ||||
| 			responseOrgs.push(new ResponseRunnerOrganization(org)); | ||||
| 		}); | ||||
| 		return responseTeams; | ||||
| 		return responseOrgs; | ||||
| 	} | ||||
|  | ||||
| 	@Get('/:id') | ||||
| @@ -45,7 +52,7 @@ export class RunnerOrganizationController { | ||||
| 	@OnUndefined(RunnerOrganizationNotFoundError) | ||||
| 	@OpenAPI({ description: 'Lists all information about the organization whose id got provided.' }) | ||||
| 	async getOne(@Param('id') id: number) { | ||||
| 		let runnerOrg = await this.runnerOrganizationRepository.findOne({ id: id }, { relations: ['contact', 'teams'] }); | ||||
| 		let runnerOrg = await this.runnerOrganizationRepository.findOne({ id: id }, { relations: ['contact', 'teams', 'teams.runners', 'teams.runners.scans', 'teams.runners.scans.track', 'runners', 'runners.scans', 'runners.scans.track'] }); | ||||
| 		if (!runnerOrg) { throw new RunnerOrganizationNotFoundError(); } | ||||
| 		return new ResponseRunnerOrganization(runnerOrg); | ||||
| 	} | ||||
| @@ -55,13 +62,13 @@ export class RunnerOrganizationController { | ||||
| 	@ResponseSchema(ResponseRunner, { isArray: true }) | ||||
| 	@ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 }) | ||||
| 	@OpenAPI({ description: 'Lists all runners from this org and it\'s teams (if you don\'t provide the ?onlyDirect=true param). <br> This includes the runner\'s group and distance ran.' }) | ||||
| 	async getRunners(@Param('id') id: number, @QueryParam('onlyDirect') onlyDirect: boolean) { | ||||
| 	async getRunners(@Param('id') id: number, @QueryParam('onlyDirect') onlyDirect: boolean, @QueryParam("selfservice_links", { required: false }) selfservice_links: boolean = false) { | ||||
| 		let responseRunners: ResponseRunner[] = new Array<ResponseRunner>(); | ||||
| 		let runners: Runner[]; | ||||
| 		if (!onlyDirect) { runners = (await this.runnerOrganizationRepository.findOne({ id: id }, { relations: ['runners', 'runners.group', 'runners.group.parentGroup', 'runners.scans', 'runners.scans.track', 'teams', 'teams.runners', 'teams.runners.group', 'teams.runners.group.parentGroup', 'teams.runners.scans', 'teams.runners.scans.track'] })).allRunners; } | ||||
| 		else { runners = (await this.runnerOrganizationRepository.findOne({ id: id }, { relations: ['runners', 'runners.group', 'runners.group.parentGroup', 'runners.scans', 'runners.scans.track'] })).runners; } | ||||
| 		runners.forEach(runner => { | ||||
| 			responseRunners.push(new ResponseRunner(runner)); | ||||
| 			responseRunners.push(new ResponseRunner(runner, selfservice_links)); | ||||
| 		}); | ||||
| 		return responseRunners; | ||||
| 	} | ||||
| @@ -114,6 +121,10 @@ export class RunnerOrganizationController { | ||||
| 	@OnUndefined(204) | ||||
| 	@OpenAPI({ description: 'Delete the organsisation whose id you provided. <br> If the organization still has runners and/or teams associated this will fail. <br> To delete the organization with all associated runners and teams set the force QueryParam to true (cascading deletion might take a while). <br> This won\'t delete the associated contact. <br> If no organization with this id exists it will just return 204(no content).' }) | ||||
| 	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { | ||||
| 		if (id == 1) { | ||||
| 			throw new BadRequestError("You can't delete the citizen runner org."); | ||||
| 		} | ||||
|  | ||||
| 		let organization = await this.runnerOrganizationRepository.findOne({ id: id }); | ||||
| 		if (!organization) { return null; } | ||||
| 		let runnerOrganization = await this.runnerOrganizationRepository.findOne(organization, { relations: ['contact', 'runners', 'teams'] }); | ||||
|   | ||||
| @@ -1,228 +1,244 @@ | ||||
| import { Request } from "express"; | ||||
| import * as jwt from "jsonwebtoken"; | ||||
| import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam, Req, UseBefore } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { config } from '../config'; | ||||
| import { InvalidCredentialsError, JwtNotProvidedError } from '../errors/AuthError'; | ||||
| import { MailSendingError } from '../errors/MailErrors'; | ||||
| import { RunnerEmailNeededError, RunnerHasDistanceDonationsError, RunnerNotFoundError, RunnerSelfserviceTimeoutError } from '../errors/RunnerErrors'; | ||||
| import { RunnerOrganizationNotFoundError } from '../errors/RunnerOrganizationErrors'; | ||||
| import { ScanStationNotFoundError } from '../errors/ScanStationErrors'; | ||||
| import { JwtCreator } from '../jwtcreator'; | ||||
| import { Mailer } from '../mailer'; | ||||
| import ScanAuth from '../middlewares/ScanAuth'; | ||||
| import { CreateSelfServiceCitizenRunner } from '../models/actions/create/CreateSelfServiceCitizenRunner'; | ||||
| import { CreateSelfServiceRunner } from '../models/actions/create/CreateSelfServiceRunner'; | ||||
| import { Runner } from '../models/entities/Runner'; | ||||
| import { RunnerGroup } from '../models/entities/RunnerGroup'; | ||||
| import { RunnerOrganization } from '../models/entities/RunnerOrganization'; | ||||
| import { ScanStation } from '../models/entities/ScanStation'; | ||||
| import { ResponseEmpty } from '../models/responses/ResponseEmpty'; | ||||
| import { ResponseScanStation } from '../models/responses/ResponseScanStation'; | ||||
| import { ResponseSelfServiceOrganisation } from '../models/responses/ResponseSelfServiceOrganisation'; | ||||
| import { ResponseSelfServiceRunner } from '../models/responses/ResponseSelfServiceRunner'; | ||||
| import { ResponseSelfServiceScan } from '../models/responses/ResponseSelfServiceScan'; | ||||
| import { DonationController } from './DonationController'; | ||||
| import { RunnerCardController } from './RunnerCardController'; | ||||
| import { ScanController } from './ScanController'; | ||||
|  | ||||
| @JsonController() | ||||
| export class RunnerSelfServiceController { | ||||
| 	private runnerRepository: Repository<Runner>; | ||||
| 	private orgRepository: Repository<RunnerOrganization>; | ||||
| 	private stationRepository: Repository<ScanStation>; | ||||
|  | ||||
| 	/** | ||||
| 	 * Gets the repository of this controller's model/entity. | ||||
| 	 */ | ||||
| 	constructor() { | ||||
| 		this.runnerRepository = getConnectionManager().get().getRepository(Runner); | ||||
| 		this.orgRepository = getConnectionManager().get().getRepository(RunnerOrganization); | ||||
| 		this.stationRepository = getConnectionManager().get().getRepository(ScanStation); | ||||
| 	} | ||||
|  | ||||
| 	@Get('/runners/me/:jwt') | ||||
| 	@ResponseSchema(ResponseSelfServiceRunner) | ||||
| 	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(RunnerNotFoundError) | ||||
| 	@OpenAPI({ description: 'Lists all information about yourself. <br> Please provide your runner jwt(that code we gave you during registration) for auth. <br> If you lost your jwt/personalized link please use the forgot endpoint.' }) | ||||
| 	async get(@Param('jwt') token: string) { | ||||
| 		return (new ResponseSelfServiceRunner(await this.getRunner(token))); | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/runners/me/:jwt') | ||||
| 	@ResponseSchema(ResponseSelfServiceRunner) | ||||
| 	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(RunnerNotFoundError) | ||||
| 	@OpenAPI({ description: 'Deletes all information about yourself. <br> Please provide your runner jwt(that code we gave you during registration) for auth. <br> If you lost your jwt/personalized link please use the forgot endpoint.' }) | ||||
| 	async remove(@Param('jwt') token: string, @QueryParam("force") force: boolean) { | ||||
| 		const responseRunner = await this.getRunner(token); | ||||
| 		let runner = await this.runnerRepository.findOne({ id: responseRunner.id }); | ||||
|  | ||||
| 		if (!runner) { return null; } | ||||
| 		if (!runner) { | ||||
| 			throw new RunnerNotFoundError(); | ||||
| 		} | ||||
|  | ||||
| 		const runnerDonations = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["distanceDonations"] })).distanceDonations; | ||||
| 		if (runnerDonations.length > 0 && !force) { | ||||
| 			throw new RunnerHasDistanceDonationsError(); | ||||
| 		} | ||||
| 		const donationController = new DonationController(); | ||||
| 		for (let donation of runnerDonations) { | ||||
| 			await donationController.remove(donation.id, force); | ||||
| 		} | ||||
|  | ||||
| 		const runnerCards = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["cards"] })).cards; | ||||
| 		const cardController = new RunnerCardController; | ||||
| 		for (let card of runnerCards) { | ||||
| 			await cardController.remove(card.id, force); | ||||
| 		} | ||||
|  | ||||
| 		const runnerScans = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["scans"] })).scans; | ||||
| 		const scanController = new ScanController; | ||||
| 		for (let scan of runnerScans) { | ||||
| 			await scanController.remove(scan.id, force); | ||||
| 		} | ||||
|  | ||||
| 		await this.runnerRepository.delete(runner); | ||||
| 		return new ResponseSelfServiceRunner(responseRunner); | ||||
| 	} | ||||
|  | ||||
| 	@Get('/runners/me/:jwt/scans') | ||||
| 	@ResponseSchema(ResponseSelfServiceScan, { isArray: true }) | ||||
| 	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(RunnerNotFoundError) | ||||
| 	@OpenAPI({ description: 'Lists all your (runner) scans. <br> Please provide your runner jwt(that code we gave you during registration) for auth. <br> If you lost your jwt/personalized link please contact support.' }) | ||||
| 	async getScans(@Param('jwt') token: string) { | ||||
| 		const scans = (await this.getRunner(token)).scans; | ||||
| 		let responseScans = new Array<ResponseSelfServiceScan>() | ||||
| 		for (let scan of scans) { | ||||
| 			responseScans.push(new ResponseSelfServiceScan(scan)); | ||||
| 		} | ||||
| 		return responseScans; | ||||
| 	} | ||||
|  | ||||
| 	@Get('/stations/me') | ||||
| 	@UseBefore(ScanAuth) | ||||
| 	@ResponseSchema(ResponseScanStation) | ||||
| 	@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(ScanStationNotFoundError) | ||||
| 	@OpenAPI({ description: 'Lists basic information about the station whose token got provided. <br> This includes it\'s associated track.', security: [{ "StationApiToken": [] }] }) | ||||
| 	async getStationMe(@Req() req: Request) { | ||||
| 		let scan = await this.stationRepository.findOne({ id: parseInt(req.headers["station_id"].toString()) }, { relations: ['track'] }) | ||||
| 		if (!scan) { throw new ScanStationNotFoundError(); } | ||||
| 		return scan.toResponse(); | ||||
| 	} | ||||
|  | ||||
| 	@Post('/runners/forgot') | ||||
| 	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(ResponseEmpty) | ||||
| 	@OpenAPI({ description: 'Use this endpoint to reuqest a new selfservice token/link to be sent to your mail address (rate limited to one mail every 24hrs).' }) | ||||
| 	async requestNewToken(@QueryParam('mail') mail: string) { | ||||
| 		if (!mail) { | ||||
| 			throw new RunnerNotFoundError(); | ||||
| 		} | ||||
| 		const runner = await this.runnerRepository.findOne({ email: mail }); | ||||
| 		if (!runner) { throw new RunnerNotFoundError(); } | ||||
|  | ||||
| 		if (runner.resetRequestedTimestamp > (Math.floor(Date.now() / 1000) - 60 * 60 * 24)) { throw new RunnerSelfserviceTimeoutError(); } | ||||
| 		const token = JwtCreator.createSelfService(runner); | ||||
|  | ||||
| 		try { | ||||
| 			await Mailer.sendSelfserviceForgottenMail(runner.email, token, "en") | ||||
| 		} catch (error) { | ||||
| 			throw new MailSendingError(); | ||||
| 		} | ||||
|  | ||||
| 		runner.resetRequestedTimestamp = Math.floor(Date.now() / 1000); | ||||
| 		await this.runnerRepository.save(runner); | ||||
|  | ||||
| 		return { token }; | ||||
| 	} | ||||
|  | ||||
| 	@Post('/runners/register') | ||||
| 	@ResponseSchema(ResponseSelfServiceRunner) | ||||
| 	@ResponseSchema(RunnerEmailNeededError, { statusCode: 406 }) | ||||
| 	@OpenAPI({ description: 'Create a new selfservice runner in the citizen org. <br> This endpoint shoud be used to allow "everyday citizen" to register themselves. <br> You have to provide a mail address, b/c the future we\'ll implement email verification.' }) | ||||
| 	async registerRunner(@Body({ validate: true }) createRunner: CreateSelfServiceCitizenRunner) { | ||||
| 		let runner = await createRunner.toEntity(); | ||||
|  | ||||
| 		runner = await this.runnerRepository.save(runner); | ||||
| 		let response = new ResponseSelfServiceRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] })); | ||||
| 		response.token = JwtCreator.createSelfService(runner); | ||||
|  | ||||
| 		try { | ||||
| 			await Mailer.sendSelfserviceWelcomeMail(runner.email, response.token, "en") | ||||
| 		} catch (error) { | ||||
| 			throw new MailSendingError(); | ||||
| 		} | ||||
|  | ||||
| 		return response; | ||||
| 	} | ||||
|  | ||||
| 	@Post('/runners/register/:token') | ||||
| 	@ResponseSchema(ResponseSelfServiceRunner) | ||||
| 	@ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 }) | ||||
| 	@OpenAPI({ description: 'Create a new selfservice runner in a provided org. <br> The orgs get provided and authorized via api tokens that can be optained via the /organizations endpoint.' }) | ||||
| 	async registerOrganizationRunner(@Param('token') token: string, @Body({ validate: true }) createRunner: CreateSelfServiceRunner) { | ||||
| 		const org = await this.getOrgansisation(token); | ||||
|  | ||||
| 		let runner = await createRunner.toEntity(org); | ||||
| 		runner = await this.runnerRepository.save(runner); | ||||
|  | ||||
| 		let response = new ResponseSelfServiceRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] })); | ||||
| 		response.token = JwtCreator.createSelfService(runner); | ||||
|  | ||||
| 		try { | ||||
| 			await Mailer.sendSelfserviceWelcomeMail(runner.email, response.token, "en") | ||||
| 		} catch (error) { | ||||
| 			throw new MailSendingError(); | ||||
| 		} | ||||
|  | ||||
| 		return response; | ||||
| 	} | ||||
|  | ||||
| 	@Get('/organizations/selfservice/:token') | ||||
| 	@ResponseSchema(ResponseSelfServiceOrganisation, { isArray: false }) | ||||
| 	@ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 }) | ||||
| 	@OpenAPI({ description: 'Get the basic info and teams for a org.' }) | ||||
| 	async getSelfserviceOrg(@Param('token') token: string) { | ||||
| 		const orgid = (await this.getOrgansisation(token)).id; | ||||
| 		const org = await this.orgRepository.findOne({ id: orgid }, { relations: ['teams'] }) | ||||
|  | ||||
| 		return new ResponseSelfServiceOrganisation(<RunnerOrganization>org); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Get's a runner by a provided jwt token. | ||||
| 	 * @param token The runner jwt provided by the runner to identitfy themselves. | ||||
| 	 */ | ||||
| 	private async getRunner(token: string): Promise<Runner> { | ||||
| 		if (token == "") { throw new JwtNotProvidedError(); } | ||||
| 		let jwtPayload = undefined | ||||
| 		try { | ||||
| 			jwtPayload = <any>jwt.verify(token, config.jwt_secret); | ||||
| 		} catch (error) { | ||||
| 			throw new InvalidCredentialsError(); | ||||
| 		} | ||||
|  | ||||
| 		const runner = await this.runnerRepository.findOne({ id: jwtPayload["id"] }, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] }); | ||||
| 		if (!runner) { throw new RunnerNotFoundError() } | ||||
| 		return runner; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Get's a runner org by a provided registration api key. | ||||
| 	 * @param token The organization's registration api token. | ||||
| 	 */ | ||||
| 	private async getOrgansisation(token: string): Promise<RunnerGroup> { | ||||
| 		token = Buffer.from(token, 'base64').toString('utf8'); | ||||
|  | ||||
| 		const organization = await this.orgRepository.findOne({ key: token }); | ||||
| 		if (!organization) { throw new RunnerOrganizationNotFoundError; } | ||||
|  | ||||
| 		return organization; | ||||
| 	} | ||||
| import { Request } from "express"; | ||||
| import * as jwt from "jsonwebtoken"; | ||||
| import { BadRequestError, Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam, Req, UseBefore } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { config } from '../config'; | ||||
| import { InvalidCredentialsError, JwtNotProvidedError } from '../errors/AuthError'; | ||||
| import { MailSendingError } from '../errors/MailErrors'; | ||||
| import { RunnerEmailNeededError, RunnerHasDistanceDonationsError, RunnerNotFoundError, RunnerSelfserviceTimeoutError } from '../errors/RunnerErrors'; | ||||
| import { RunnerOrganizationNotFoundError } from '../errors/RunnerOrganizationErrors'; | ||||
| import { ScanStationNotFoundError } from '../errors/ScanStationErrors'; | ||||
| import { JwtCreator } from '../jwtcreator'; | ||||
| import { Mailer } from '../mailer'; | ||||
| import ScanAuth from '../middlewares/ScanAuth'; | ||||
| import { CreateSelfServiceCitizenRunner } from '../models/actions/create/CreateSelfServiceCitizenRunner'; | ||||
| import { CreateSelfServiceRunner } from '../models/actions/create/CreateSelfServiceRunner'; | ||||
| import { Runner } from '../models/entities/Runner'; | ||||
| import { RunnerGroup } from '../models/entities/RunnerGroup'; | ||||
| import { RunnerOrganization } from '../models/entities/RunnerOrganization'; | ||||
| import { ScanStation } from '../models/entities/ScanStation'; | ||||
| import { ResponseEmpty } from '../models/responses/ResponseEmpty'; | ||||
| import { ResponseScanStation } from '../models/responses/ResponseScanStation'; | ||||
| import { ResponseSelfServiceOrganisation } from '../models/responses/ResponseSelfServiceOrganisation'; | ||||
| import { ResponseSelfServiceRunner } from '../models/responses/ResponseSelfServiceRunner'; | ||||
| import { ResponseSelfServiceScan } from '../models/responses/ResponseSelfServiceScan'; | ||||
| import { DonationController } from './DonationController'; | ||||
| import { RunnerCardController } from './RunnerCardController'; | ||||
| import { ScanController } from './ScanController'; | ||||
|  | ||||
| @JsonController() | ||||
| export class RunnerSelfServiceController { | ||||
| 	private runnerRepository: Repository<Runner>; | ||||
| 	private orgRepository: Repository<RunnerOrganization>; | ||||
| 	private stationRepository: Repository<ScanStation>; | ||||
|  | ||||
| 	/** | ||||
| 	 * Gets the repository of this controller's model/entity. | ||||
| 	 */ | ||||
| 	constructor() { | ||||
| 		this.runnerRepository = getConnectionManager().get().getRepository(Runner); | ||||
| 		this.orgRepository = getConnectionManager().get().getRepository(RunnerOrganization); | ||||
| 		this.stationRepository = getConnectionManager().get().getRepository(ScanStation); | ||||
| 	} | ||||
|  | ||||
| 	@Get('/runners/me/:jwt') | ||||
| 	@ResponseSchema(ResponseSelfServiceRunner) | ||||
| 	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(RunnerNotFoundError) | ||||
| 	@OpenAPI({ description: 'Lists all information about yourself. <br> Please provide your runner jwt(that code we gave you during registration) for auth. <br> If you lost your jwt/personalized link please use the forgot endpoint.' }) | ||||
| 	async get(@Param('jwt') token: string) { | ||||
| 		return (new ResponseSelfServiceRunner(await this.getRunner(token))); | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/runners/me/:jwt') | ||||
| 	@ResponseSchema(ResponseSelfServiceRunner) | ||||
| 	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(RunnerNotFoundError) | ||||
| 	@OpenAPI({ description: 'Deletes all information about yourself. <br> Please provide your runner jwt(that code we gave you during registration) for auth. <br> If you lost your jwt/personalized link please use the forgot endpoint.' }) | ||||
| 	async remove(@Param('jwt') token: string, @QueryParam("force") force: boolean) { | ||||
| 		const responseRunner = await this.getRunner(token); | ||||
| 		let runner = await this.runnerRepository.findOne({ id: responseRunner.id }); | ||||
|  | ||||
| 		if (!runner) { return null; } | ||||
| 		if (!runner) { | ||||
| 			throw new RunnerNotFoundError(); | ||||
| 		} | ||||
|  | ||||
| 		const runnerDonations = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["distanceDonations"] })).distanceDonations; | ||||
| 		if (runnerDonations.length > 0 && !force) { | ||||
| 			throw new RunnerHasDistanceDonationsError(); | ||||
| 		} | ||||
| 		const donationController = new DonationController(); | ||||
| 		for (let donation of runnerDonations) { | ||||
| 			await donationController.remove(donation.id, force); | ||||
| 		} | ||||
|  | ||||
| 		const runnerCards = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["cards"] })).cards; | ||||
| 		const cardController = new RunnerCardController; | ||||
| 		for (let card of runnerCards) { | ||||
| 			await cardController.remove(card.id, force); | ||||
| 		} | ||||
|  | ||||
| 		const runnerScans = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["scans"] })).scans; | ||||
| 		const scanController = new ScanController; | ||||
| 		for (let scan of runnerScans) { | ||||
| 			await scanController.remove(scan.id, force); | ||||
| 		} | ||||
|  | ||||
| 		await this.runnerRepository.delete(runner); | ||||
| 		return new ResponseSelfServiceRunner(responseRunner); | ||||
| 	} | ||||
|  | ||||
| 	@Get('/runners/me/:jwt/scans') | ||||
| 	@ResponseSchema(ResponseSelfServiceScan, { isArray: true }) | ||||
| 	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(RunnerNotFoundError) | ||||
| 	@OpenAPI({ description: 'Lists all your (runner) scans. <br> Please provide your runner jwt(that code we gave you during registration) for auth. <br> If you lost your jwt/personalized link please contact support.' }) | ||||
| 	async getScans(@Param('jwt') token: string) { | ||||
| 		const scans = (await this.getRunner(token)).scans; | ||||
| 		let responseScans = new Array<ResponseSelfServiceScan>() | ||||
| 		for (let scan of scans) { | ||||
| 			responseScans.push(new ResponseSelfServiceScan(scan)); | ||||
| 		} | ||||
| 		return responseScans; | ||||
| 	} | ||||
|  | ||||
| 	@Get('/stations/me') | ||||
| 	@UseBefore(ScanAuth) | ||||
| 	@ResponseSchema(ResponseScanStation) | ||||
| 	@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(ScanStationNotFoundError) | ||||
| 	@OpenAPI({ description: 'Lists basic information about the station whose token got provided. <br> This includes it\'s associated track.', security: [{ "StationApiToken": [] }] }) | ||||
| 	async getStationMe(@Req() req: Request) { | ||||
| 		let scan = await this.stationRepository.findOne({ id: parseInt(req.headers["station_id"].toString()) }, { relations: ['track'] }) | ||||
| 		if (!scan) { throw new ScanStationNotFoundError(); } | ||||
| 		return scan.toResponse(); | ||||
| 	} | ||||
|  | ||||
| 	@Post('/runners/login') | ||||
| 	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(ResponseEmpty) | ||||
| 	@OpenAPI({ description: 'Use this endpoint to reuqest a new selfservice magic-login-link to be sent to your mail address (rate limited to one mail every 15mins).' }) | ||||
| 	async requestNewToken(@QueryParam('mail') mail: string, @QueryParam("locale") locale: string = "en") { | ||||
| 		if (!mail) { | ||||
| 			throw new RunnerNotFoundError(); | ||||
| 		} | ||||
| 		const runner = await this.runnerRepository.findOne({ email: mail }); | ||||
| 		if (!runner) { throw new RunnerNotFoundError(); } | ||||
|  | ||||
| 		if (runner.resetRequestedTimestamp > (Math.floor(Date.now() / 1000) - 30)) { throw new RunnerSelfserviceTimeoutError(); } | ||||
| 		const token = JwtCreator.createSelfService(runner); | ||||
|  | ||||
| 		try { | ||||
| 			await Mailer.sendSelfserviceForgottenMail(runner.email, runner.id, runner.firstname, runner.middlename, runner.lastname, token, locale) | ||||
| 		} catch (error) { | ||||
| 			throw new MailSendingError(); | ||||
| 		} | ||||
|  | ||||
| 		runner.resetRequestedTimestamp = Math.floor(Date.now() / 1000); | ||||
| 		await this.runnerRepository.save(runner); | ||||
|  | ||||
| 		return { token }; | ||||
| 	} | ||||
|  | ||||
| 	@Post('/runners/register') | ||||
| 	@ResponseSchema(ResponseSelfServiceRunner) | ||||
| 	@ResponseSchema(RunnerEmailNeededError, { statusCode: 406 }) | ||||
| 	@OpenAPI({ description: 'Create a new selfservice runner in the citizen org. <br> This endpoint shoud be used to allow "everyday citizen" to register themselves. <br> You have to provide a mail address, b/c the future we\'ll implement email verification.' }) | ||||
| 	async registerRunner(@Body({ validate: true }) createRunner: CreateSelfServiceCitizenRunner, @QueryParam("locale") locale: string = "en") { | ||||
| 		let runner = await createRunner.toEntity(); | ||||
| 		if (await this.getRunnerExistsByMail(runner.email)) { | ||||
| 			throw new BadRequestError("E-Mail already registered") | ||||
| 		} | ||||
| 		runner = await this.runnerRepository.save(runner); | ||||
|  | ||||
| 		let response = new ResponseSelfServiceRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] })); | ||||
| 		response.token = JwtCreator.createSelfService(runner); | ||||
|  | ||||
| 		try { | ||||
| 			await Mailer.sendSelfserviceWelcomeMail(runner.email, runner.id, runner.firstname, runner.middlename, runner.lastname, response.token, locale) | ||||
| 		} catch (error) { | ||||
| 			throw new MailSendingError(); | ||||
| 		} | ||||
|  | ||||
| 		return response; | ||||
| 	} | ||||
|  | ||||
| 	@Post('/runners/register/:token') | ||||
| 	@ResponseSchema(ResponseSelfServiceRunner) | ||||
| 	@ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 }) | ||||
| 	@OpenAPI({ description: 'Create a new selfservice runner in a provided org. <br> The orgs get provided and authorized via api tokens that can be optained via the /organizations endpoint.' }) | ||||
| 	async registerOrganizationRunner(@Param('token') token: string, @Body({ validate: true }) createRunner: CreateSelfServiceRunner, @QueryParam("locale") locale: string = "en") { | ||||
| 		const org = await this.getOrgansisation(token); | ||||
|  | ||||
| 		let runner = await createRunner.toEntity(org); | ||||
| 		if (await this.getRunnerExistsByMail(runner.email)) { | ||||
| 			throw new BadRequestError("E-Mail already registered") | ||||
| 		} | ||||
| 		runner = await this.runnerRepository.save(runner); | ||||
|  | ||||
| 		let response = new ResponseSelfServiceRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] })); | ||||
| 		response.token = JwtCreator.createSelfService(runner); | ||||
|  | ||||
| 		try { | ||||
| 			await Mailer.sendSelfserviceWelcomeMail(runner.email, runner.id, runner.firstname, runner.middlename, runner.lastname, response.token, locale) | ||||
| 		} catch (error) { | ||||
| 			throw new MailSendingError(); | ||||
| 		} | ||||
|  | ||||
| 		return response; | ||||
| 	} | ||||
|  | ||||
| 	@Get('/organizations/selfservice/:token') | ||||
| 	@ResponseSchema(ResponseSelfServiceOrganisation, { isArray: false }) | ||||
| 	@ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 }) | ||||
| 	@OpenAPI({ description: 'Get the basic info and teams for a org.' }) | ||||
| 	async getSelfserviceOrg(@Param('token') token: string) { | ||||
| 		const orgid = (await this.getOrgansisation(token)).id; | ||||
| 		const org = await this.orgRepository.findOne({ id: orgid }, { relations: ['teams'] }) | ||||
|  | ||||
| 		return new ResponseSelfServiceOrganisation(<RunnerOrganization>org); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Get's a runner by a provided jwt token. | ||||
| 	 * @param token The runner jwt provided by the runner to identitfy themselves. | ||||
| 	 */ | ||||
| 	private async getRunner(token: string): Promise<Runner> { | ||||
| 		if (token == "") { throw new JwtNotProvidedError(); } | ||||
| 		let jwtPayload = undefined | ||||
| 		try { | ||||
| 			jwtPayload = <any>jwt.verify(token, config.jwt_secret); | ||||
| 		} catch (error) { | ||||
| 			throw new InvalidCredentialsError(); | ||||
| 		} | ||||
|  | ||||
| 		const runner = await this.runnerRepository.findOne({ id: jwtPayload["id"] }, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] }); | ||||
| 		if (!runner) { throw new RunnerNotFoundError() } | ||||
| 		return runner; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Get's a runner org by a provided registration api key. | ||||
| 	 * @param token The organization's registration api token. | ||||
| 	 */ | ||||
| 	private async getOrgansisation(token: string): Promise<RunnerGroup> { | ||||
| 		token = Buffer.from(token, 'base64').toString('utf8'); | ||||
|  | ||||
| 		const organization = await this.orgRepository.findOne({ key: token }); | ||||
| 		if (!organization) { throw new RunnerOrganizationNotFoundError; } | ||||
|  | ||||
| 		return organization; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Checks if a runner already exists | ||||
| 	 * @param email The runner's email address | ||||
| 	 * @returns Boolean (true if exists, false if not) | ||||
| 	 */ | ||||
| 	private async getRunnerExistsByMail(email: string): Promise<boolean> { | ||||
| 		const runner = await this.runnerRepository.findOne({ email }); | ||||
| 		return runner != undefined | ||||
| 	} | ||||
| } | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { Repository, getConnectionManager } from 'typeorm'; | ||||
| import { RunnerTeamHasRunnersError, RunnerTeamIdsNotMatchingError, RunnerTeamNotFoundError } from '../errors/RunnerTeamErrors'; | ||||
| import { CreateRunnerTeam } from '../models/actions/create/CreateRunnerTeam'; | ||||
| import { UpdateRunnerTeam } from '../models/actions/update/UpdateRunnerTeam'; | ||||
| @@ -27,11 +27,18 @@ export class RunnerTeamController { | ||||
| 	@Authorized("TEAM:GET") | ||||
| 	@ResponseSchema(ResponseRunnerTeam, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all teams. <br> This includes their parent organization and contact (if existing/associated).' }) | ||||
| 	async getAll() { | ||||
| 	async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { | ||||
| 		let responseTeams: ResponseRunnerTeam[] = new Array<ResponseRunnerTeam>(); | ||||
| 		const runners = await this.runnerTeamRepository.find({ relations: ['parentGroup', 'contact'] }); | ||||
| 		runners.forEach(runner => { | ||||
| 			responseTeams.push(new ResponseRunnerTeam(runner)); | ||||
| 		let teams: Array<RunnerTeam>; | ||||
|  | ||||
| 		if (page != undefined) { | ||||
| 			teams = await this.runnerTeamRepository.find({ relations: ['parentGroup', 'contact'], skip: page * page_size, take: page_size }); | ||||
| 		} else { | ||||
| 			teams = await this.runnerTeamRepository.find({ relations: ['parentGroup', 'contact'] }); | ||||
| 		} | ||||
|  | ||||
| 		teams.forEach(team => { | ||||
| 			responseTeams.push(new ResponseRunnerTeam(team)); | ||||
| 		}); | ||||
| 		return responseTeams; | ||||
| 	} | ||||
| @@ -43,7 +50,7 @@ export class RunnerTeamController { | ||||
| 	@OnUndefined(RunnerTeamNotFoundError) | ||||
| 	@OpenAPI({ description: 'Lists all information about the team whose id got provided.' }) | ||||
| 	async getOne(@Param('id') id: number) { | ||||
| 		let runnerTeam = await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact'] }); | ||||
| 		let runnerTeam = await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact', 'runners', 'runners.scans', 'runners.scans.track'] }); | ||||
| 		if (!runnerTeam) { throw new RunnerTeamNotFoundError(); } | ||||
| 		return new ResponseRunnerTeam(runnerTeam); | ||||
| 	} | ||||
| @@ -53,11 +60,11 @@ export class RunnerTeamController { | ||||
| 	@ResponseSchema(ResponseRunner, { isArray: true }) | ||||
| 	@ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 }) | ||||
| 	@OpenAPI({ description: 'Lists all runners from this team. <br> This includes the runner\'s group and distance ran.' }) | ||||
| 	async getRunners(@Param('id') id: number) { | ||||
| 	async getRunners(@Param('id') id: number, @QueryParam("selfservice_links", { required: false }) selfservice_links: boolean = false) { | ||||
| 		let responseRunners: ResponseRunner[] = new Array<ResponseRunner>(); | ||||
| 		const runners = (await this.runnerTeamRepository.findOne({ id: id }, { relations: ['runners', 'runners.group', 'runners.group.parentGroup', 'runners.scans', 'runners.scans.track'] })).runners; | ||||
| 		runners.forEach(runner => { | ||||
| 			responseRunners.push(new ResponseRunner(runner)); | ||||
| 			responseRunners.push(new ResponseRunner(runner, selfservice_links)); | ||||
| 		}); | ||||
| 		return responseRunners; | ||||
| 	} | ||||
| @@ -112,7 +119,7 @@ export class RunnerTeamController { | ||||
| 	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { | ||||
| 		let team = await this.runnerTeamRepository.findOne({ id: id }); | ||||
| 		if (!team) { return null; } | ||||
| 		let runnerTeam = await this.runnerTeamRepository.findOne(team, { relations: ['parentGroup', 'contact', 'runners'] }); | ||||
| 		let runnerTeam = await this.runnerTeamRepository.findOne(team, { relations: ['runners'] }); | ||||
|  | ||||
| 		if (!force) { | ||||
| 			if (runnerTeam.runners.length != 0) { | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { Request } from "express"; | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam, Req, UseBefore } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { Repository, getConnectionManager } from 'typeorm'; | ||||
| import { RunnerNotFoundError } from '../errors/RunnerErrors'; | ||||
| import { ScanIdsNotMatchingError, ScanNotFoundError } from '../errors/ScanErrors'; | ||||
| import { ScanStationNotFoundError } from '../errors/ScanStationErrors'; | ||||
| @@ -34,9 +34,16 @@ export class ScanController { | ||||
| 	@ResponseSchema(ResponseScan, { isArray: true }) | ||||
| 	@ResponseSchema(ResponseTrackScan, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all scans (normal or track) from all runners. <br> This includes the scan\'s runner\'s distance ran.' }) | ||||
| 	async getAll() { | ||||
| 	async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { | ||||
| 		let responseScans: ResponseScan[] = new Array<ResponseScan>(); | ||||
| 		const scans = await this.scanRepository.find({ relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] }); | ||||
| 		let scans: Array<Scan>; | ||||
|  | ||||
| 		if (page != undefined) { | ||||
| 			scans = await this.scanRepository.find({ relations: ['runner', 'track'], skip: page * page_size, take: page_size }); | ||||
| 		} else { | ||||
| 			scans = await this.scanRepository.find({ relations: ['runner', 'track'] }); | ||||
| 		} | ||||
|  | ||||
| 		scans.forEach(scan => { | ||||
| 			responseScans.push(scan.toResponse()); | ||||
| 		}); | ||||
| @@ -51,7 +58,7 @@ export class ScanController { | ||||
| 	@OnUndefined(ScanNotFoundError) | ||||
| 	@OpenAPI({ description: 'Lists all information about the scan whose id got provided. This includes the scan\'s runner\'s distance ran.' }) | ||||
| 	async getOne(@Param('id') id: number) { | ||||
| 		let scan = await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] }) | ||||
| 		let scan = await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.group', 'card', 'station'] }) | ||||
| 		if (!scan) { throw new ScanNotFoundError(); } | ||||
| 		return scan.toResponse(); | ||||
| 	} | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { Repository, getConnectionManager } from 'typeorm'; | ||||
| import { ScanStationHasScansError, ScanStationIdsNotMatchingError, ScanStationNotFoundError } from '../errors/ScanStationErrors'; | ||||
| import { TrackNotFoundError } from '../errors/TrackErrors'; | ||||
| import { CreateScanStation } from '../models/actions/create/CreateScanStation'; | ||||
| @@ -26,9 +26,16 @@ export class ScanStationController { | ||||
| 	@Authorized("STATION:GET") | ||||
| 	@ResponseSchema(ResponseScanStation, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all stations. <br> This includes their associated tracks.' }) | ||||
| 	async getAll() { | ||||
| 	async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { | ||||
| 		let responseStations: ResponseScanStation[] = new Array<ResponseScanStation>(); | ||||
| 		const stations = await this.stationRepository.find({ relations: ['track'] }); | ||||
| 		let stations: Array<ScanStation>; | ||||
|  | ||||
| 		if (page != undefined) { | ||||
| 			stations = await this.stationRepository.find({ relations: ['track'], skip: page * page_size, take: page_size }); | ||||
| 		} else { | ||||
| 			stations = await this.stationRepository.find({ relations: ['track'] }); | ||||
| 		} | ||||
|  | ||||
| 		stations.forEach(station => { | ||||
| 			responseStations.push(station.toResponse()); | ||||
| 		}); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { Repository, getConnectionManager } from 'typeorm'; | ||||
| import { StatsClientNotFoundError } from '../errors/StatsClientErrors'; | ||||
| import { TrackNotFoundError } from "../errors/TrackErrors"; | ||||
| import { CreateStatsClient } from '../models/actions/create/CreateStatsClient'; | ||||
| @@ -24,9 +24,16 @@ export class StatsClientController { | ||||
| 	@Authorized("STATSCLIENT:GET") | ||||
| 	@ResponseSchema(ResponseStatsClient, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all stats clients. Please remember that the key can only be viewed on creation.' }) | ||||
| 	async getAll() { | ||||
| 	async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { | ||||
| 		let responseClients: ResponseStatsClient[] = new Array<ResponseStatsClient>(); | ||||
| 		const clients = await this.clientRepository.find(); | ||||
| 		let clients: Array<StatsClient>; | ||||
|  | ||||
| 		if (page != undefined) { | ||||
| 			clients = await this.clientRepository.find({ skip: page * page_size, take: page_size }); | ||||
| 		} else { | ||||
| 			clients = await this.clientRepository.find(); | ||||
| 		} | ||||
|  | ||||
| 		clients.forEach(clients => { | ||||
| 			responseClients.push(new ResponseStatsClient(clients)); | ||||
| 		}); | ||||
|   | ||||
| @@ -1,12 +1,14 @@ | ||||
| import { Get, JsonController, UseBefore } from 'routing-controllers'; | ||||
| import { Get, JsonController, QueryParam, UseBefore } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnection } from 'typeorm'; | ||||
| import StatsAuth from '../middlewares/StatsAuth'; | ||||
| import { Donation } from '../models/entities/Donation'; | ||||
| import { Donor } from '../models/entities/Donor'; | ||||
| import { Runner } from '../models/entities/Runner'; | ||||
| import { RunnerOrganization } from '../models/entities/RunnerOrganization'; | ||||
| import { RunnerTeam } from '../models/entities/RunnerTeam'; | ||||
| import { Scan } from '../models/entities/Scan'; | ||||
| import { TrackScan } from '../models/entities/TrackScan'; | ||||
| import { User } from '../models/entities/User'; | ||||
| import { ResponseStats } from '../models/responses/ResponseStats'; | ||||
| import { ResponseStatsOrgnisation } from '../models/responses/ResponseStatsOrganization'; | ||||
| @@ -20,14 +22,28 @@ export class StatsController { | ||||
|     @ResponseSchema(ResponseStats) | ||||
|     @OpenAPI({ description: "A very basic stats endpoint providing basic counters for a dashboard or simmilar" }) | ||||
|     async get() { | ||||
|         let connection = getConnection(); | ||||
|         let runners = await connection.getRepository(Runner).find({ relations: ['scans', 'scans.track'] }); | ||||
|         let teams = await connection.getRepository(RunnerTeam).find(); | ||||
|         let orgs = await connection.getRepository(RunnerOrganization).find(); | ||||
|         let users = await connection.getRepository(User).find(); | ||||
|         let scans = await connection.getRepository(Scan).find(); | ||||
|         const connection = getConnection(); | ||||
|         const runnersViaSelfservice = await connection.getRepository(Runner).count({ where: { created_via: "selfservice" } }); | ||||
|         const runnersViaKiosk = await connection.getRepository(Runner).count({ where: { created_via: "kiosk" } }); | ||||
|         const runners = await connection.getRepository(Runner).count(); | ||||
|         const teams = await connection.getRepository(RunnerTeam).count(); | ||||
|         const orgs = await connection.getRepository(RunnerOrganization).count(); | ||||
|         const users = await connection.getRepository(User).count(); | ||||
|         const scans = await connection.getRepository(Scan).count({ where: { valid: true } }); | ||||
|  | ||||
|         const distance_query = await connection.getRepository(Scan).createQueryBuilder('scan') | ||||
|             .leftJoinAndSelect("scan.track", "track").where("scan.valid = TRUE") | ||||
|             .select("SUM(track.distance)", "sum_track").addSelect("SUM(_distance)", "sum_distance") | ||||
|             .getRawOne(); | ||||
|         let distace = parseInt(distance_query.sum_track) | ||||
|         if (distance_query.sum_distance) { | ||||
|             distace += parseInt(distance_query.sum_distance) | ||||
|         } | ||||
|  | ||||
|         let donations = await connection.getRepository(Donation).find({ relations: ['runner', 'runner.scans', 'runner.scans.track'] }); | ||||
|         return new ResponseStats(runners, teams, orgs, users, scans, donations) | ||||
|         const donors = await connection.getRepository(Donor).count(); | ||||
|  | ||||
|         return new ResponseStats(runnersViaSelfservice, runners, teams, orgs, users, scans, donations, distace, donors, runnersViaKiosk) | ||||
|     } | ||||
|  | ||||
|     @Get("/runners/distance") | ||||
| @@ -36,7 +52,10 @@ export class StatsController { | ||||
|     @OpenAPI({ description: "Returns the top ten runners by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
|     async getTopRunnersByDistance() { | ||||
|         let runners = await getConnection().getRepository(Runner).find({ relations: ['scans', 'group', 'distanceDonations', 'scans.track'] }); | ||||
|         let topRunners = runners.sort((runner1, runner2) => runner1.distance - runner2.distance).slice(0, 9); | ||||
|         if (!runners || runners.length == 0) { | ||||
|             return []; | ||||
|         } | ||||
|         let topRunners = runners.sort((runner1, runner2) => runner2.distance - runner1.distance).slice(0, 10); | ||||
|         let responseRunners: ResponseStatsRunner[] = new Array<ResponseStatsRunner>(); | ||||
|         topRunners.forEach(runner => { | ||||
|             responseRunners.push(new ResponseStatsRunner(runner)); | ||||
| @@ -49,8 +68,11 @@ export class StatsController { | ||||
|     @ResponseSchema(ResponseStatsRunner, { isArray: true }) | ||||
|     @OpenAPI({ description: "Returns the top ten runners by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
|     async getTopRunnersByDonations() { | ||||
|         let runners = await getConnection().getRepository(Runner).find({ relations: ['scans', 'group', 'distanceDonations', 'scans.track'] }); | ||||
|         let topRunners = runners.sort((runner1, runner2) => runner1.distanceDonationAmount - runner2.distanceDonationAmount).slice(0, 9); | ||||
|         let runners = await getConnection().getRepository(Runner).find({ relations: ['group', 'distanceDonations', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] }); | ||||
|         if (!runners || runners.length == 0) { | ||||
|             return []; | ||||
|         } | ||||
|         let topRunners = runners.sort((runner1, runner2) => runner2.distanceDonationAmount - runner1.distanceDonationAmount).slice(0, 10); | ||||
|         let responseRunners: ResponseStatsRunner[] = new Array<ResponseStatsRunner>(); | ||||
|         topRunners.forEach(runner => { | ||||
|             responseRunners.push(new ResponseStatsRunner(runner)); | ||||
| @@ -58,6 +80,34 @@ export class StatsController { | ||||
|         return responseRunners; | ||||
|     } | ||||
|  | ||||
|     @Get("/runners/laptime") | ||||
|     @UseBefore(StatsAuth) | ||||
|     @ResponseSchema(ResponseStatsRunner, { isArray: true }) | ||||
|     @OpenAPI({ description: "Returns the top ten runners by fastest laptime on your selected track (track by id).", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
|     async getTopRunnersByLaptime(@QueryParam("track") track: number) { | ||||
|         let scans = await getConnection().getRepository(TrackScan).find({ relations: ['track', 'runner', 'runner.group', 'runner.scans', 'runner.scans.track', 'runner.distanceDonations'] }); | ||||
|         if (!scans || scans.length == 0) { | ||||
|             return []; | ||||
|         } | ||||
|         scans = scans.filter((s) => { return s.track.id == track && s.valid == true && s.lapTime != 0 }).sort((scan1, scan2) => scan1.lapTime - scan2.lapTime); | ||||
|  | ||||
|         let topScans = new Array<TrackScan>(); | ||||
|         let knownRunners = new Array<number>(); | ||||
|         for (let i = 0; i < scans.length && topScans.length < 10; i++) { | ||||
|             const element = scans[i]; | ||||
|             if (!knownRunners.includes(element.runner.id)) { | ||||
|                 topScans.push(element); | ||||
|                 knownRunners.push(element.runner.id); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         let responseRunners: ResponseStatsRunner[] = new Array<ResponseStatsRunner>(); | ||||
|         topScans.forEach(scan => { | ||||
|             responseRunners.push(new ResponseStatsRunner(scan.runner, scan.lapTime)); | ||||
|         }); | ||||
|         return responseRunners; | ||||
|     } | ||||
|  | ||||
|     @Get("/scans") | ||||
|     @UseBefore(StatsAuth) | ||||
|     @ResponseSchema(ResponseStatsRunner, { isArray: true }) | ||||
| @@ -71,8 +121,11 @@ export class StatsController { | ||||
|     @ResponseSchema(ResponseStatsTeam, { isArray: true }) | ||||
|     @OpenAPI({ description: "Returns the top ten teams by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
|     async getTopTeamsByDistance() { | ||||
|         let teams = await getConnection().getRepository(RunnerTeam).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track'] }); | ||||
|         let topTeams = teams.sort((team1, team2) => team1.distance - team2.distance).slice(0, 9); | ||||
|         let teams = await getConnection().getRepository(RunnerTeam).find({ relations: ['parentGroup', 'runners', 'runners.scans', 'runners.scans.track'] }); | ||||
|         if (!teams || teams.length == 0) { | ||||
|             return []; | ||||
|         } | ||||
|         let topTeams = teams.sort((team1, team2) => team2.distance - team1.distance).slice(0, 10); | ||||
|         let responseTeams: ResponseStatsTeam[] = new Array<ResponseStatsTeam>(); | ||||
|         topTeams.forEach(team => { | ||||
|             responseTeams.push(new ResponseStatsTeam(team)); | ||||
| @@ -85,8 +138,11 @@ export class StatsController { | ||||
|     @ResponseSchema(ResponseStatsTeam, { isArray: true }) | ||||
|     @OpenAPI({ description: "Returns the top ten teams by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
|     async getTopTeamsByDonations() { | ||||
|         let teams = await getConnection().getRepository(RunnerTeam).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track'] }); | ||||
|         let topTeams = teams.sort((team1, team2) => team1.distanceDonationAmount - team2.distanceDonationAmount).slice(0, 9); | ||||
|         let teams = await getConnection().getRepository(RunnerTeam).find({ relations: ['parentGroup', 'runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track'] }); | ||||
|         if (!teams || teams.length == 0) { | ||||
|             return []; | ||||
|         } | ||||
|         let topTeams = teams.sort((team1, team2) => team2.distanceDonationAmount - team1.distanceDonationAmount).slice(0, 10); | ||||
|         let responseTeams: ResponseStatsTeam[] = new Array<ResponseStatsTeam>(); | ||||
|         topTeams.forEach(team => { | ||||
|             responseTeams.push(new ResponseStatsTeam(team)); | ||||
| @@ -100,7 +156,10 @@ export class StatsController { | ||||
|     @OpenAPI({ description: "Returns the top ten organizations by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
|     async getTopOrgsByDistance() { | ||||
|         let orgs = await getConnection().getRepository(RunnerOrganization).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track', 'teams', 'teams.runners', 'teams.runners.scans', 'teams.runners.distanceDonations', 'teams.runners.scans.track'] }); | ||||
|         let topOrgs = orgs.sort((org1, org2) => org1.distance - org2.distance).slice(0, 9); | ||||
|         if (!orgs || orgs.length == 0) { | ||||
|             return []; | ||||
|         } | ||||
|         let topOrgs = orgs.sort((org1, org2) => org2.distance - org1.distance).slice(0, 10); | ||||
|         let responseOrgs: ResponseStatsOrgnisation[] = new Array<ResponseStatsOrgnisation>(); | ||||
|         topOrgs.forEach(org => { | ||||
|             responseOrgs.push(new ResponseStatsOrgnisation(org)); | ||||
| @@ -113,8 +172,11 @@ export class StatsController { | ||||
|     @ResponseSchema(ResponseStatsOrgnisation, { isArray: true }) | ||||
|     @OpenAPI({ description: "Returns the top ten organizations by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
|     async getTopOrgsByDonations() { | ||||
|         let orgs = await getConnection().getRepository(RunnerOrganization).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track', 'teams', 'teams.runners', 'teams.runners.scans', 'teams.runners.distanceDonations', 'teams.runners.scans.track'] }); | ||||
|         let topOrgs = orgs.sort((org1, org2) => org1.distanceDonationAmount - org2.distanceDonationAmount).slice(0, 9); | ||||
|         let orgs = await getConnection().getRepository(RunnerOrganization).find({ relations: ['runners', 'runners.distanceDonations', 'runners.distanceDonations.runner', 'runners.distanceDonations.runner.scans', 'runners.distanceDonations.runner.scans.track', 'teams', 'teams.runners', 'teams.runners.distanceDonations', 'teams.runners.distanceDonations.runner', 'teams.runners.distanceDonations.runner.scans', 'teams.runners.distanceDonations.runner.scans.track'] }); | ||||
|         if (!orgs || orgs.length == 0) { | ||||
|             return []; | ||||
|         } | ||||
|         let topOrgs = orgs.sort((org1, org2) => org2.distanceDonationAmount - org1.distanceDonationAmount).slice(0, 10); | ||||
|         let responseOrgs: ResponseStatsOrgnisation[] = new Array<ResponseStatsOrgnisation>(); | ||||
|         topOrgs.forEach(org => { | ||||
|             responseOrgs.push(new ResponseStatsOrgnisation(org)); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { Repository, getConnectionManager } from 'typeorm'; | ||||
| import { TrackHasScanStationsError, TrackIdsNotMatchingError, TrackLapTimeCantBeNegativeError, TrackNotFoundError } from "../errors/TrackErrors"; | ||||
| import { CreateTrack } from '../models/actions/create/CreateTrack'; | ||||
| import { UpdateTrack } from '../models/actions/update/UpdateTrack'; | ||||
| @@ -25,9 +25,17 @@ export class TrackController { | ||||
| 	@Authorized("TRACK:GET") | ||||
| 	@ResponseSchema(ResponseTrack, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all tracks.' }) | ||||
| 	async getAll() { | ||||
| 	async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { | ||||
| 		let responseTracks: ResponseTrack[] = new Array<ResponseTrack>(); | ||||
| 		const tracks = await this.trackRepository.find(); | ||||
| 		let tracks: Array<Track>; | ||||
|  | ||||
| 		if (page != undefined) { | ||||
| 			tracks = await this.trackRepository.find({ skip: page * page_size, take: page_size }); | ||||
| 		} | ||||
| 		else { | ||||
| 			tracks = await this.trackRepository.find(); | ||||
| 		} | ||||
|  | ||||
| 		tracks.forEach(track => { | ||||
| 			responseTracks.push(new ResponseTrack(track)); | ||||
| 		}); | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { PasswordMustContainLowercaseLetterError, PasswordMustContainNumberError, PasswordMustContainUppercaseLetterError, PasswordTooShortError, UserDeletionNotConfirmedError, UserIdsNotMatchingError, UsernameContainsIllegalCharacterError, UserNotFoundError } from '../errors/UserErrors'; | ||||
| import { Repository, getConnectionManager } from 'typeorm'; | ||||
| import { PasswordMustContainLowercaseLetterError, PasswordMustContainNumberError, PasswordMustContainUppercaseLetterError, PasswordTooShortError, UserDeletionNotConfirmedError, UserIdsNotMatchingError, UserNotFoundError, UsernameContainsIllegalCharacterError } from '../errors/UserErrors'; | ||||
| import { UserGroupNotFoundError } from '../errors/UserGroupErrors'; | ||||
| import { CreateUser } from '../models/actions/create/CreateUser'; | ||||
| import { UpdateUser } from '../models/actions/update/UpdateUser'; | ||||
| @@ -28,9 +28,17 @@ export class UserController { | ||||
| 	@Authorized("USER:GET") | ||||
| 	@ResponseSchema(ResponseUser, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all users. <br> This includes their groups and permissions granted to them.' }) | ||||
| 	async getAll() { | ||||
| 	async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { | ||||
| 		let responseUsers: ResponseUser[] = new Array<ResponseUser>(); | ||||
| 		const users = await this.userRepository.find({ relations: ['permissions', 'groups', 'groups.permissions'] }); | ||||
| 		let users: Array<User>; | ||||
|  | ||||
| 		if (page != undefined) { | ||||
| 			users = await this.userRepository.find({ relations: ['permissions', 'groups', 'groups.permissions'], skip: page * page_size, take: page_size }); | ||||
| 		} | ||||
| 		else { | ||||
| 			users = await this.userRepository.find({ relations: ['permissions', 'groups', 'groups.permissions'] }); | ||||
| 		} | ||||
|  | ||||
| 		users.forEach(user => { | ||||
| 			responseUsers.push(new ResponseUser(user)); | ||||
| 		}); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { Repository, getConnectionManager } from 'typeorm'; | ||||
| import { UserGroupIdsNotMatchingError, UserGroupNotFoundError } from '../errors/UserGroupErrors'; | ||||
| import { CreateUserGroup } from '../models/actions/create/CreateUserGroup'; | ||||
| import { UpdateUserGroup } from '../models/actions/update/UpdateUserGroup'; | ||||
| @@ -27,9 +27,16 @@ export class UserGroupController { | ||||
| 	@Authorized("USERGROUP:GET") | ||||
| 	@ResponseSchema(ResponseUserGroup, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all groups. <br> The information provided might change while the project continues to evolve.' }) | ||||
| 	async getAll() { | ||||
| 	async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { | ||||
| 		let responseGroups: ResponseUserGroup[] = new Array<ResponseUserGroup>(); | ||||
| 		const groups = await this.userGroupsRepository.find({ relations: ['permissions'] }); | ||||
| 		let groups: Array<UserGroup>; | ||||
|  | ||||
| 		if (page != undefined) { | ||||
| 			groups = await this.userGroupsRepository.find({ relations: ['permissions'], skip: page * page_size, take: page_size }); | ||||
| 		} else { | ||||
| 			groups = await this.userGroupsRepository.find({ relations: ['permissions'] }); | ||||
| 		} | ||||
|  | ||||
| 		groups.forEach(group => { | ||||
| 			responseGroups.push(group.toResponse()); | ||||
| 		}); | ||||
|   | ||||
| @@ -47,14 +47,14 @@ export class RunnerEmailNeededError extends NotAcceptableError { | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a runner already requested a new selfservice link in the last 24hrs. | ||||
|  * Error to throw when a runner already requested a new selfservice link in the last 30s. | ||||
|  */ | ||||
| export class RunnerSelfserviceTimeoutError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "RunnerSelfserviceTimeoutError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "You can only reqest a new token every 24hrs." | ||||
| 	message = "You can only reqest a new token every 30s." | ||||
| } | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -18,9 +18,19 @@ export class Mailer { | ||||
|      */ | ||||
|     public static async sendResetMail(to_address: string, token: string, locale: string = "en") { | ||||
|         try { | ||||
|             await axios.post(`${Mailer.base}/reset?locale=${locale}&key=${Mailer.key}`, { | ||||
|                 address: to_address, | ||||
|                 resetKey: token | ||||
|             await axios.request({ | ||||
|                 method: 'POST', | ||||
|                 url: `${Mailer.base}/api/v1/email`, | ||||
|                 headers: { | ||||
|                     authorization: `Bearer ${Mailer.key}`, | ||||
|                     'content-type': 'application/json' | ||||
|                 }, | ||||
|                 data: { | ||||
|                     to: to_address, | ||||
|                     templateName: 'password-reset', | ||||
|                     language: locale, | ||||
|                     data: { token: token } | ||||
|                 } | ||||
|             }); | ||||
|         } catch (error) { | ||||
|             if (Mailer.testing) { return true; } | ||||
| @@ -32,12 +42,26 @@ export class Mailer { | ||||
|      * Function for sending a runner selfservice welcome mail. | ||||
|      * @param to_address The address the mail will be sent to. Should always get pulled from a runner object. | ||||
|      * @param token The requested selfservice token - will be combined with the app_url to generate a selfservice profile link. | ||||
|      */ | ||||
|     public static async sendSelfserviceWelcomeMail(to_address: string, token: string, locale: string = "en") { | ||||
|     */ | ||||
|     public static async sendSelfserviceWelcomeMail(to_address: string, runner_id: number, firstname: string, middlename: string, lastname: string, token: string, locale: string = "en") { | ||||
|         try { | ||||
|             await axios.post(`${Mailer.base}/registration?locale=${locale}&key=${Mailer.key}`, { | ||||
|                 address: to_address, | ||||
|                 selfserviceToken: token | ||||
|             await axios.request({ | ||||
|                 method: 'POST', | ||||
|                 url: `${Mailer.base}/api/v1/email`, | ||||
|                 headers: { | ||||
|                     authorization: `Bearer ${Mailer.key}`, | ||||
|                     'content-type': 'application/json' | ||||
|                 }, | ||||
|                 data: { | ||||
|                     to: to_address, | ||||
|                     templateName: 'welcome', | ||||
|                     language: locale, | ||||
|                     data: { | ||||
|                         name: `${firstname} ${middlename} ${lastname}`, | ||||
|                         barcode_content: `${runner_id}`, | ||||
|                         link: `${process.env.SELFSERVICE_URL}/profile/${token}` | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|         } catch (error) { | ||||
|             if (Mailer.testing) { return true; } | ||||
| @@ -49,12 +73,26 @@ export class Mailer { | ||||
|      * Function for sending a runner selfservice link forgotten mail. | ||||
|      * @param to_address The address the mail will be sent to. Should always get pulled from a runner object. | ||||
|      * @param token The requested selfservice token - will be combined with the app_url to generate a selfservice profile link. | ||||
|      */ | ||||
|     public static async sendSelfserviceForgottenMail(to_address: string, token: string, locale: string = "en") { | ||||
|     */ | ||||
|     public static async sendSelfserviceForgottenMail(to_address: string, runner_id: number, firstname: string, middlename: string, lastname: string, token: string, locale: string = "en") { | ||||
|         try { | ||||
|             await axios.post(`${Mailer.base}/registration_forgot?locale=${locale}&key=${Mailer.key}`, { | ||||
|                 address: to_address, | ||||
|                 selfserviceToken: token | ||||
|             await axios.request({ | ||||
|                 method: 'POST', | ||||
|                 url: `${Mailer.base}/api/v1/email`, | ||||
|                 headers: { | ||||
|                     authorization: `Bearer ${Mailer.key}`, | ||||
|                     'content-type': 'application/json' | ||||
|                 }, | ||||
|                 data: { | ||||
|                     to: to_address, | ||||
|                     templateName: 'welcome', | ||||
|                     language: locale, | ||||
|                     data: { | ||||
|                         name: `${firstname} ${middlename} ${lastname}`, | ||||
|                         barcode_content: `${runner_id}`, | ||||
|                         link: `${process.env.SELFSERVICE_URL}/profile/${token}` | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|         } catch (error) { | ||||
|             if (Mailer.testing) { return true; } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import * as argon2 from "argon2"; | ||||
| import { verify } from '@node-rs/argon2'; | ||||
| import { Request, Response } from 'express'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { ScanStation } from '../models/entities/ScanStation'; | ||||
| @@ -58,7 +58,7 @@ const ScanAuth = async (req: Request, res: Response, next: () => void) => { | ||||
|         if (station.enabled == false) { | ||||
|             res.status(401).send({ http_code: 401, short: "station_disabled", message: "Station is disabled." }); | ||||
|         } | ||||
|         if (!(await argon2.verify(station.key, provided_token))) { | ||||
|         if (!(await verify(station.key, provided_token))) { | ||||
|             res.status(401).send({ http_code: 401, short: "invalid_token", message: "Api token non-existent or invalid syntax." }); | ||||
|             return; | ||||
|         } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import * as argon2 from "argon2"; | ||||
| import { verify } from '@node-rs/argon2'; | ||||
| import { Request, Response } from 'express'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { StatsClient } from '../models/entities/StatsClient'; | ||||
| @@ -55,7 +55,7 @@ const StatsAuth = async (req: Request, res: Response, next: () => void) => { | ||||
|         } | ||||
|     } | ||||
|     else { | ||||
|         if (!(await argon2.verify(client.key, provided_token))) { | ||||
|         if (!(await verify(client.key, provided_token))) { | ||||
|             res.status(401).send("Api token invalid."); | ||||
|             return; | ||||
|         } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import * as argon2 from "argon2"; | ||||
| import { hash } from '@node-rs/argon2'; | ||||
| import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; | ||||
| import * as jsonwebtoken from 'jsonwebtoken'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| @@ -49,7 +49,7 @@ export class ResetPassword { | ||||
|         if (found_user.refreshTokenCount !== decoded["refreshTokenCount"]) { throw new RefreshTokenCountInvalidError(); } | ||||
|  | ||||
|         found_user.refreshTokenCount = found_user.refreshTokenCount + 1; | ||||
|         found_user.password = await argon2.hash(this.password + found_user.uuid); | ||||
|         found_user.password = await hash(this.password + found_user.uuid); | ||||
|         await getConnectionManager().get().getRepository(User).save(found_user); | ||||
|  | ||||
|         return "password reset successfull"; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import * as argon2 from "argon2"; | ||||
| import { verify } from '@node-rs/argon2'; | ||||
| import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { InvalidCredentialsError, PasswordNeededError, UserDisabledError, UserNotFoundError } from '../../../errors/AuthError'; | ||||
| @@ -56,7 +56,7 @@ export class CreateAuth { | ||||
|             throw new UserNotFoundError(); | ||||
|         } | ||||
|         if (found_user.enabled == false) { throw new UserDisabledError(); } | ||||
|         if (!(await argon2.verify(found_user.password, this.password + found_user.uuid))) { | ||||
|         if (!(await verify(found_user.password, this.password + found_user.uuid))) { | ||||
|             throw new InvalidCredentialsError(); | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -33,6 +33,7 @@ export class CreateDistanceDonation extends CreateDonation { | ||||
|         let newDonation = new DistanceDonation; | ||||
|  | ||||
|         newDonation.amountPerDistance = this.amountPerDistance; | ||||
|         newDonation.paidAmount = this.paidAmount; | ||||
|         newDonation.donor = await this.getDonor(); | ||||
|         newDonation.runner = await this.getRunner(); | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { IsInt, IsPositive } from 'class-validator'; | ||||
| import { IsInt, IsOptional, IsPositive } from 'class-validator'; | ||||
| import { getConnection } from 'typeorm'; | ||||
| import { DonorNotFoundError } from '../../../errors/DonorErrors'; | ||||
| import { Donation } from '../../entities/Donation'; | ||||
| @@ -16,6 +16,13 @@ export abstract class CreateDonation { | ||||
|     @IsPositive() | ||||
|     donor: number; | ||||
|  | ||||
|     /** | ||||
|      * The donation's paid amount in the smalles unit of your currency (default: euro cent). | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsOptional() | ||||
|     paidAmount?: number; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new Donation entity from this. | ||||
|      */ | ||||
|   | ||||
| @@ -21,6 +21,7 @@ export class CreateFixedDonation extends CreateDonation { | ||||
|         let newDonation = new FixedDonation; | ||||
|  | ||||
|         newDonation.amount = this.amount; | ||||
|         newDonation.paidAmount = this.paidAmount; | ||||
|         newDonation.donor = await this.getDonor(); | ||||
|  | ||||
|         return newDonation; | ||||
|   | ||||
| @@ -50,4 +50,11 @@ export abstract class CreateParticipant { | ||||
|     @IsOptional() | ||||
|     @IsObject() | ||||
|     address?: Address; | ||||
|  | ||||
|     /** | ||||
|      * how the participant got into the system | ||||
|      */ | ||||
|     @IsOptional() | ||||
|     @IsString() | ||||
|     created_via?: string; | ||||
| } | ||||
| @@ -32,6 +32,9 @@ export class CreateRunner extends CreateParticipant { | ||||
|         newRunner.email = this.email; | ||||
|         newRunner.group = await this.getGroup(); | ||||
|         newRunner.address = this.address; | ||||
|         if (this.created_via) { | ||||
|             newRunner.created_via = this.created_via; | ||||
|         } | ||||
|         Address.validate(newRunner.address); | ||||
|  | ||||
|         return newRunner; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import * as argon2 from "argon2"; | ||||
| import { hash } from '@node-rs/argon2'; | ||||
| import { IsBoolean, IsInt, IsOptional, IsPositive, IsString } from 'class-validator'; | ||||
| import crypto from 'crypto'; | ||||
| import { getConnection } from 'typeorm'; | ||||
| @@ -44,7 +44,7 @@ export class CreateScanStation { | ||||
|  | ||||
|         let newUUID = uuid.v4().toUpperCase(); | ||||
|         newStation.prefix = crypto.createHash("sha3-512").update(newUUID).digest('hex').substring(0, 7).toUpperCase(); | ||||
|         newStation.key = await argon2.hash(newStation.prefix + "." + newUUID); | ||||
|         newStation.key = await hash(newStation.prefix + "." + newUUID); | ||||
|         newStation.cleartextkey = newStation.prefix + "." + newUUID; | ||||
|  | ||||
|         return newStation; | ||||
|   | ||||
| @@ -26,6 +26,7 @@ export class CreateSelfServiceCitizenRunner extends CreateParticipant { | ||||
|     public async toEntity(): Promise<Runner> { | ||||
|         let newRunner: Runner = new Runner(); | ||||
|  | ||||
|         newRunner.created_via = "selfservice"; | ||||
|         newRunner.firstname = this.firstname; | ||||
|         newRunner.middlename = this.middlename; | ||||
|         newRunner.lastname = this.lastname; | ||||
|   | ||||
| @@ -28,6 +28,7 @@ export class CreateSelfServiceRunner extends CreateParticipant { | ||||
|     public async toEntity(group: RunnerGroup): Promise<Runner> { | ||||
|         let newRunner: Runner = new Runner(); | ||||
|  | ||||
|         newRunner.created_via = "selfservice"; | ||||
|         newRunner.firstname = this.firstname; | ||||
|         newRunner.middlename = this.middlename; | ||||
|         newRunner.lastname = this.lastname; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import * as argon2 from "argon2"; | ||||
| import { hash } from '@node-rs/argon2'; | ||||
| import { IsOptional, IsString } from 'class-validator'; | ||||
| import crypto from 'crypto'; | ||||
| import * as uuid from 'uuid'; | ||||
| @@ -25,7 +25,7 @@ export class CreateStatsClient { | ||||
|  | ||||
|         let newUUID = uuid.v4().toUpperCase(); | ||||
|         newClient.prefix = crypto.createHash("sha3-512").update(newUUID).digest('hex').substring(0, 7).toUpperCase(); | ||||
|         newClient.key = await argon2.hash(newClient.prefix + "." + newUUID); | ||||
|         newClient.key = await hash(newClient.prefix + "." + newUUID); | ||||
|         newClient.cleartextkey = newClient.prefix + "." + newUUID; | ||||
|  | ||||
|         return newClient; | ||||
|   | ||||
| @@ -57,11 +57,12 @@ export class CreateTrackScan { | ||||
|      * @returns The runnerCard whom's id you provided. | ||||
|      */ | ||||
|     public async getCard(): Promise<RunnerCard> { | ||||
|         const track = await getConnection().getRepository(RunnerCard).findOne({ id: this.card }, { relations: ["runner"] }); | ||||
|         if (!track) { | ||||
|         const id = this.card % 200000000000; | ||||
|         const runnerCard = await getConnection().getRepository(RunnerCard).findOne({ id: id }, { relations: ["runner"] }); | ||||
|         if (!runnerCard) { | ||||
|             throw new RunnerCardNotFoundError(); | ||||
|         } | ||||
|         return track; | ||||
|         return runnerCard; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -85,14 +86,13 @@ export class CreateTrackScan { | ||||
|      * @returns The validated scan with it's laptime set. | ||||
|      */ | ||||
|     public async validateScan(scan: TrackScan): Promise<TrackScan> { | ||||
|         const scans = await getConnection().getRepository(TrackScan).find({ where: { runner: scan.runner, valid: true }, relations: ["track"] }); | ||||
|         if (scans.length == 0) { | ||||
|         const latestScan = await getConnection().getRepository(TrackScan).findOne({ where: { runner: scan.runner, valid: true }, relations: ["track"], order: { id: 'DESC' } }); | ||||
|         if (!latestScan) { | ||||
|             scan.lapTime = 0; | ||||
|             scan.valid = true; | ||||
|         } | ||||
|         else { | ||||
|             const newestScan = scans[scans.length - 1]; | ||||
|             scan.lapTime = scan.timestamp - newestScan.timestamp; | ||||
|             scan.lapTime = scan.timestamp - latestScan.timestamp; | ||||
|             scan.valid = (scan.lapTime > scan.track.minimumLapTime); | ||||
|         } | ||||
|         return scan; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import * as argon2 from "argon2"; | ||||
| import { hash } from "@node-rs/argon2"; | ||||
| import { passwordStrength } from "check-password-strength"; | ||||
| import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| @@ -110,11 +110,11 @@ export class CreateUser { | ||||
|         newUser.lastname = this.lastname | ||||
|         newUser.uuid = uuid.v4() | ||||
|         newUser.phone = this.phone | ||||
|         newUser.password = await argon2.hash(this.password + newUser.uuid); | ||||
|         newUser.password = await hash(this.password + newUser.uuid); | ||||
|         newUser.groups = await this.getGroups(); | ||||
|         newUser.enabled = this.enabled; | ||||
|  | ||||
|         if (!this.profilePic) { newUser.profilePic = `https://dev.lauf-fuer-kaya.de/lfk-logo.png`; } | ||||
|         if (!this.profilePic) { newUser.profilePic = `https://lauf-fuer-kaya.de/lfk-logo.png`; } | ||||
|         else { newUser.profilePic = this.profilePic; } | ||||
|  | ||||
|         return newUser; | ||||
|   | ||||
| @@ -32,6 +32,7 @@ export class UpdateDistanceDonation extends UpdateDonation { | ||||
|      */ | ||||
|     public async update(donation: DistanceDonation): Promise<DistanceDonation> { | ||||
|         donation.amountPerDistance = this.amountPerDistance; | ||||
|         donation.paidAmount = this.paidAmount; | ||||
|         donation.donor = await this.getDonor(); | ||||
|         donation.runner = await this.getRunner(); | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { IsInt, IsPositive } from 'class-validator'; | ||||
| import { IsInt, IsOptional, IsPositive } from 'class-validator'; | ||||
| import { getConnection } from 'typeorm'; | ||||
| import { DonorNotFoundError } from '../../../errors/DonorErrors'; | ||||
| import { Donation } from '../../entities/Donation'; | ||||
| @@ -23,6 +23,13 @@ export abstract class UpdateDonation { | ||||
|     @IsPositive() | ||||
|     donor: number; | ||||
|  | ||||
|     /** | ||||
|      * The donation's paid amount in the smalles unit of your currency (default: euro cent). | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsOptional() | ||||
|     paidAmount?: number; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new Donation entity from this. | ||||
|      */ | ||||
|   | ||||
| @@ -20,6 +20,7 @@ export class UpdateFixedDonation extends UpdateDonation { | ||||
|      */ | ||||
|     public async update(donation: FixedDonation): Promise<FixedDonation> { | ||||
|         donation.amount = this.amount; | ||||
|         donation.paidAmount = this.paidAmount; | ||||
|         donation.donor = await this.getDonor(); | ||||
|  | ||||
|         return donation; | ||||
|   | ||||
							
								
								
									
										50
									
								
								src/models/actions/update/UpdateRunnerCardByCode.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/models/actions/update/UpdateRunnerCardByCode.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| import { IsBoolean, IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator'; | ||||
| import { getConnection } from 'typeorm'; | ||||
| import { RunnerNotFoundError } from '../../../errors/RunnerErrors'; | ||||
| import { Runner } from '../../entities/Runner'; | ||||
| import { RunnerCard } from '../../entities/RunnerCard'; | ||||
|  | ||||
| /** | ||||
|  * This class is used to update a RunnerCard entity (via put request). | ||||
|  */ | ||||
| export class UpdateRunnerCardByCode { | ||||
|     /** | ||||
|      * The card's code. | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsNotEmpty() | ||||
|     code?: string; | ||||
|  | ||||
|     /** | ||||
|      * The runner's id. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsOptional() | ||||
|     runner?: number; | ||||
|  | ||||
|     /** | ||||
|      * Is the updated card enabled (for fraud reasons)? | ||||
|      * Default: true | ||||
|      */ | ||||
|     @IsBoolean() | ||||
|     enabled: boolean = true; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new RunnerCard entity from this. | ||||
|      */ | ||||
|     public async update(card: RunnerCard): Promise<RunnerCard> { | ||||
|         card.enabled = this.enabled; | ||||
|         card.runner = await this.getRunner(); | ||||
|  | ||||
|         return card; | ||||
|     } | ||||
|  | ||||
|     public async getRunner(): Promise<Runner> { | ||||
|         if (!this.runner) { return null; } | ||||
|         const runner = await getConnection().getRepository(Runner).findOne({ id: this.runner }); | ||||
|         if (!runner) { | ||||
|             throw new RunnerNotFoundError(); | ||||
|         } | ||||
|         return runner; | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| import * as argon2 from "argon2"; | ||||
| import { hash } from '@node-rs/argon2'; | ||||
| import { passwordStrength } from "check-password-strength"; | ||||
| import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| @@ -111,7 +111,7 @@ export class UpdateUser { | ||||
|             if (!password_strength.contains.includes("lowercase")) { throw new PasswordMustContainLowercaseLetterError(); } | ||||
|             if (!password_strength.contains.includes("number")) { throw new PasswordMustContainNumberError(); } | ||||
|             if (!(password_strength.length > 9)) { throw new PasswordTooShortError(); } | ||||
|             user.password = await argon2.hash(this.password + user.uuid); | ||||
|             user.password = await hash(this.password + user.uuid); | ||||
|             user.refreshTokenCount = user.refreshTokenCount + 1; | ||||
|         } | ||||
|  | ||||
| @@ -124,7 +124,7 @@ export class UpdateUser { | ||||
|         user.phone = this.phone; | ||||
|         user.groups = await this.getGroups(); | ||||
|  | ||||
|         if (!this.profilePic) { user.profilePic = `https://dev.lauf-fuer-kaya.de/lfk-logo.png`; } | ||||
|         if (!this.profilePic) { user.profilePic = `https://lauf-fuer-kaya.de/lfk-logo.png`; } | ||||
|         else { user.profilePic = this.profilePic; } | ||||
|  | ||||
|         return user; | ||||
|   | ||||
| @@ -2,7 +2,7 @@ import { | ||||
|   IsInt, | ||||
|   IsNotEmpty | ||||
| } from "class-validator"; | ||||
| import { Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; | ||||
| import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; | ||||
| import { ResponseDonation } from '../responses/ResponseDonation'; | ||||
| import { Donor } from './Donor'; | ||||
|  | ||||
| @@ -34,6 +34,13 @@ export abstract class Donation { | ||||
|    */ | ||||
|   public abstract get amount(): number; | ||||
|  | ||||
|   /** | ||||
|    * The donation's paid amount in cents (or whatever your currency's smallest unit is.). | ||||
|    * Used to mark donations as paid. | ||||
|    */ | ||||
|   @Column({ nullable: true }) | ||||
|   @IsInt() | ||||
|   paidAmount: number; | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|   | ||||
| @@ -33,6 +33,15 @@ export class Donor extends Participant { | ||||
|     return this.donations.reduce((sum, current) => sum + current.amount, 0); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Returns the total paid donations of a donor based on his linked donations. | ||||
|   */ | ||||
|   @IsInt() | ||||
|   public get paidDonationAmount(): number { | ||||
|     if (!this.donations) { return 0; } | ||||
|     return this.donations.reduce((sum, current) => sum + current.paidAmount, 0); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   | ||||
| @@ -75,6 +75,14 @@ export abstract class Participant { | ||||
|   @IsEmail() | ||||
|   email?: string; | ||||
|  | ||||
|   /** | ||||
|    * how the participant got into the system | ||||
|    */ | ||||
|   @Column({ nullable: true, default: "backend" }) | ||||
|   @IsOptional() | ||||
|   @IsString() | ||||
|   created_via?: string; | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   | ||||
| @@ -57,7 +57,10 @@ export class Runner extends Participant { | ||||
|    * This is implemented here to avoid duplicate code in other files. | ||||
|    */ | ||||
|   public get validScans(): Scan[] { | ||||
|     return this.scans.filter(scan => scan.valid == true); | ||||
|     if (this.scans) { | ||||
|       return this.scans.filter(scan => scan.valid == true); | ||||
|     } | ||||
|     return [] | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -81,6 +84,6 @@ export class Runner extends Participant { | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   public toResponse(): ResponseRunner { | ||||
|     return new ResponseRunner(this); | ||||
|     return new ResponseRunner(this, true); | ||||
|   } | ||||
| } | ||||
| @@ -52,13 +52,7 @@ export class RunnerCard { | ||||
|    * Generates a ean-13 compliant string for barcode generation. | ||||
|    */ | ||||
|   public get code(): string { | ||||
|     const multiply = [1, 3]; | ||||
|     let total = 0; | ||||
|     this.paddedId.split('').forEach((letter, index) => { | ||||
|       total += parseInt(letter, 10) * multiply[index % 2]; | ||||
|     }); | ||||
|     const checkSum = (Math.ceil(total / 10) * 10) - total; | ||||
|     return this.paddedId + checkSum.toString(); | ||||
|     return this.paddedId | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -67,10 +61,11 @@ export class RunnerCard { | ||||
|   private get paddedId(): string { | ||||
|     let id: string = this.id.toString(); | ||||
|  | ||||
|     if (id.length > 12) { | ||||
|     if (id.length > 11) { | ||||
|       throw new RunnerCardIdOutOfRangeError(); | ||||
|     } | ||||
|     while (id.length < 12) { id = '0' + id; } | ||||
|     while (id.length < 11) { id = '0' + id; } | ||||
|     id = '2' + id; | ||||
|  | ||||
|     return id; | ||||
|   } | ||||
|   | ||||
| @@ -51,6 +51,9 @@ export abstract class RunnerGroup { | ||||
|   */ | ||||
|   @IsInt() | ||||
|   public get distance(): number { | ||||
|     if (!this.runners || this.runners.length == 0) { | ||||
|       return 0; | ||||
|     } | ||||
|     return this.runners.reduce((sum, current) => sum + current.distance, 0); | ||||
|   } | ||||
|  | ||||
|   | ||||
							
								
								
									
										7
									
								
								src/models/enums/DonationStatus.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/models/enums/DonationStatus.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| /** | ||||
|  * This enum contains all status a donation can inherit regarding it's payment status. | ||||
|  */ | ||||
| export enum DonationStatus { | ||||
|     OPEN = 'OPEN', | ||||
|     PAID = 'PAID' | ||||
| } | ||||
| @@ -35,4 +35,5 @@ export enum ResponseObjectType { | ||||
|     USER = 'USER', | ||||
|     USERGROUP = 'USERGROUP', | ||||
|     USERPERMISSIONS = 'USERPERMISSIONS', | ||||
|     SELFSERVICEDONOR = 'SELFSERVICEDONOR' | ||||
| } | ||||
| @@ -1,5 +1,6 @@ | ||||
| import { IsInt, IsNotEmpty, IsPositive } from "class-validator"; | ||||
| import { Donation } from '../entities/Donation'; | ||||
| import { DonationStatus } from '../enums/DonationStatus'; | ||||
| import { ResponseObjectType } from '../enums/ResponseObjectType'; | ||||
| import { IResponse } from './IResponse'; | ||||
| import { ResponseDonor } from './ResponseDonor'; | ||||
| @@ -15,6 +16,12 @@ export class ResponseDonation implements IResponse { | ||||
|     */ | ||||
|     responseType: ResponseObjectType = ResponseObjectType.DONATION; | ||||
|  | ||||
|     /** | ||||
|     * The donation's payment status. | ||||
|     * Provides you with a quick indicator of it's payment status. | ||||
|     */ | ||||
|     status: DonationStatus; | ||||
|  | ||||
|     /** | ||||
|      * The donation's id. | ||||
|      */ | ||||
| @@ -34,13 +41,28 @@ export class ResponseDonation implements IResponse { | ||||
|     @IsInt() | ||||
|     amount: number; | ||||
|  | ||||
|     /** | ||||
|      * The donation's paid amount in the smalles unit of your currency (default: euro cent). | ||||
|      */ | ||||
|     @IsInt() | ||||
|     paidAmount: number; | ||||
|  | ||||
|     /** | ||||
|      * Creates a ResponseDonation object from a scan. | ||||
|      * @param donation The donation the response shall be build for. | ||||
|      */ | ||||
|     public constructor(donation: Donation) { | ||||
|         this.id = donation.id; | ||||
|         this.donor = donation.donor.toResponse(); | ||||
|         if (donation.donor) { | ||||
|             this.donor = donation.donor.toResponse(); | ||||
|         } | ||||
|         this.amount = donation.amount; | ||||
|         this.paidAmount = donation.paidAmount || 0; | ||||
|         if (this.paidAmount < this.amount) { | ||||
|             this.status = DonationStatus.OPEN; | ||||
|         } | ||||
|         else { | ||||
|             this.status = DonationStatus.PAID; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import { | ||||
| import { Donor } from '../entities/Donor'; | ||||
| import { ResponseObjectType } from '../enums/ResponseObjectType'; | ||||
| import { IResponse } from './IResponse'; | ||||
| import { ResponseDonation } from './ResponseDonation'; | ||||
| import { ResponseParticipant } from './ResponseParticipant'; | ||||
|  | ||||
| /** | ||||
| @@ -28,6 +29,14 @@ export class ResponseDonor extends ResponseParticipant implements IResponse { | ||||
|     @IsInt() | ||||
|     donationAmount: number; | ||||
|  | ||||
|     /** | ||||
|     * Returns the total paid donations of a donor based on his linked donations. | ||||
|     */ | ||||
|     @IsInt() | ||||
|     paidDonationAmount: number; | ||||
|  | ||||
|     donations: Array<ResponseDonation>; | ||||
|  | ||||
|     /** | ||||
|      * Creates a ResponseRunner object from a runner. | ||||
|      * @param runner The user the response shall be build for. | ||||
| @@ -36,5 +45,12 @@ export class ResponseDonor extends ResponseParticipant implements IResponse { | ||||
|         super(donor); | ||||
|         this.receiptNeeded = donor.receiptNeeded; | ||||
|         this.donationAmount = donor.donationAmount; | ||||
|         this.paidDonationAmount = donor.paidDonationAmount; | ||||
|         this.donations = new Array<ResponseDonation>(); | ||||
|         if (donor.donations?.length > 0) { | ||||
|             for (const donation of donor.donations) { | ||||
|                 this.donations.push(donation.toResponse()) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -50,6 +50,12 @@ export abstract class ResponseParticipant implements IResponse { | ||||
|     @IsString() | ||||
|     email?: string; | ||||
|  | ||||
|     /** | ||||
|      * how the participant got into the system | ||||
|      */ | ||||
|     @IsString() | ||||
|     created_via?: string; | ||||
|  | ||||
|     /** | ||||
|      * The participant's address. | ||||
|      */ | ||||
| @@ -64,6 +70,7 @@ export abstract class ResponseParticipant implements IResponse { | ||||
|     public constructor(participant: Participant) { | ||||
|         this.id = participant.id; | ||||
|         this.firstname = participant.firstname; | ||||
|         this.created_via = participant.created_via; | ||||
|         this.middlename = participant.middlename; | ||||
|         this.lastname = participant.lastname; | ||||
|         this.phone = participant.phone; | ||||
|   | ||||
| @@ -1,7 +1,10 @@ | ||||
| import { | ||||
|     IsInt, | ||||
|     IsObject | ||||
|     IsObject, | ||||
|     IsOptional, | ||||
|     IsString | ||||
| } from "class-validator"; | ||||
| import { JwtCreator } from '../../jwtcreator'; | ||||
| import { Runner } from '../entities/Runner'; | ||||
| import { ResponseObjectType } from '../enums/ResponseObjectType'; | ||||
| import { IResponse } from './IResponse'; | ||||
| @@ -30,14 +33,26 @@ export class ResponseRunner extends ResponseParticipant implements IResponse { | ||||
|     @IsObject() | ||||
|     group: ResponseRunnerGroup; | ||||
|  | ||||
|     /** | ||||
|      * A selfservice link for our new runner. | ||||
|      */ | ||||
|     @IsOptional() | ||||
|     @IsString() | ||||
|     selfserviceLink: string; | ||||
|  | ||||
|     /** | ||||
|      * Creates a ResponseRunner object from a runner. | ||||
|      * @param runner The user the response shall be build for. | ||||
|      */ | ||||
|     public constructor(runner: Runner) { | ||||
|     public constructor(runner: Runner, generateSelfServiceLink: boolean = false) { | ||||
|         super(runner); | ||||
|         if (!runner.scans) { this.distance = 0 } | ||||
|         else { this.distance = runner.validScans.reduce((sum, current) => sum + current.distance, 0); } | ||||
|         if (runner.group) { this.group = runner.group.toResponse(); } | ||||
|  | ||||
|         if (generateSelfServiceLink) { | ||||
|             const token = JwtCreator.createSelfService(runner); | ||||
|             this.selfserviceLink = `${process.env.SELFSERVICE_URL}/profile/${token}`; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { IsInt, IsNotEmpty, IsObject, IsOptional, IsString } from "class-validator"; | ||||
| import { IsInt, IsNotEmpty, IsNumber, IsObject, IsOptional, IsString } from "class-validator"; | ||||
| import { RunnerGroup } from '../entities/RunnerGroup'; | ||||
| import { ResponseObjectType } from '../enums/ResponseObjectType'; | ||||
| import { IResponse } from './IResponse'; | ||||
| @@ -36,6 +36,10 @@ export abstract class ResponseRunnerGroup implements IResponse { | ||||
|     @IsOptional() | ||||
|     contact?: ResponseGroupContact; | ||||
|  | ||||
|     @IsOptional() | ||||
|     @IsNumber() | ||||
|     total_distance: number | ||||
|  | ||||
|     /** | ||||
|      * Creates a ResponseRunnerGroup object from a runnerGroup. | ||||
|      * @param group The runnerGroup the response shall be build for. | ||||
| @@ -44,5 +48,6 @@ export abstract class ResponseRunnerGroup implements IResponse { | ||||
|         this.id = group.id; | ||||
|         this.name = group.name; | ||||
|         if (group.contact) { this.contact = group.contact.toResponse(); }; | ||||
|         if (group.runners) { this.total_distance = group.runners.reduce((p, c) => p + c.distance, 0) } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -67,6 +67,9 @@ export class ResponseRunnerOrganization extends ResponseRunnerGroup implements I | ||||
|             for (let team of org.teams) { | ||||
|                 this.teams.push(team.toResponse()); | ||||
|             } | ||||
|             for (const team of this.teams) { | ||||
|                 this.total_distance += team.total_distance; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (!org.key) { this.registrationEnabled = false; } | ||||
|   | ||||
| @@ -2,6 +2,7 @@ import { IsInt, IsNotEmpty, IsPositive } from 'class-validator'; | ||||
| import { DistanceDonation } from '../entities/DistanceDonation'; | ||||
| import { ResponseObjectType } from '../enums/ResponseObjectType'; | ||||
| import { IResponse } from './IResponse'; | ||||
| import { ResponseSelfServiceDonor } from './ResponseSelfServiceDonor'; | ||||
|  | ||||
| /** | ||||
|  * Defines the runner selfservice donation response. | ||||
| @@ -18,7 +19,7 @@ export class ResponseSelfServiceDonation implements IResponse { | ||||
|      * The donation's donor. | ||||
|      */ | ||||
|     @IsNotEmpty() | ||||
|     donor: string; | ||||
|     donor: ResponseSelfServiceDonor; | ||||
|  | ||||
|     /** | ||||
|      * The donation's amount in the smalles unit of your currency (default: euro cent). | ||||
| @@ -35,9 +36,7 @@ export class ResponseSelfServiceDonation implements IResponse { | ||||
|     amountPerDistance: number; | ||||
|  | ||||
|     public constructor(donation: DistanceDonation) { | ||||
|         if (!donation.donor.middlename) { this.donor = donation.donor.firstname + " " + donation.donor.lastname; } | ||||
|         else { this.donor = donation.donor.firstname + " " + donation.donor.middlename + " " + donation.donor.lastname; } | ||||
|  | ||||
|         this.donor = new ResponseSelfServiceDonor(donation.donor); | ||||
|         this.amountPerDistance = donation.amountPerDistance; | ||||
|         this.amount = donation.amount; | ||||
|     } | ||||
|   | ||||
							
								
								
									
										51
									
								
								src/models/responses/ResponseSelfServiceDonor.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/models/responses/ResponseSelfServiceDonor.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| import { IsInt, IsString } from "class-validator"; | ||||
| import { Donor } from '../entities/Donor'; | ||||
| import { ResponseObjectType } from '../enums/ResponseObjectType'; | ||||
| import { IResponse } from './IResponse'; | ||||
|  | ||||
| /** | ||||
|  * Defines the donor selfservice response. | ||||
|  * Why? B/C runner's are not allowed to view all information available to admin users. | ||||
| */ | ||||
| export class ResponseSelfServiceDonor implements IResponse { | ||||
|     /** | ||||
|     * The responseType. | ||||
|     * This contains the type of class/entity this response contains. | ||||
|     */ | ||||
|     responseType: ResponseObjectType = ResponseObjectType.SELFSERVICEDONOR; | ||||
|  | ||||
|     /** | ||||
|      * The participant's id. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     id: number; | ||||
|  | ||||
|     /** | ||||
|      * The participant's first name. | ||||
|      */ | ||||
|     @IsString() | ||||
|     firstname: string; | ||||
|  | ||||
|     /** | ||||
|      * The participant's middle name. | ||||
|      */ | ||||
|     @IsString() | ||||
|     middlename?: string; | ||||
|  | ||||
|     /** | ||||
|      * The participant's last name. | ||||
|      */ | ||||
|     @IsString() | ||||
|     lastname: string; | ||||
|  | ||||
|     /** | ||||
|      * Creates a ResponseSelfServiceDonor object from a runner. | ||||
|      * @param donor The donor the response shall be build for. | ||||
|      */ | ||||
|     public constructor(donor: Donor) { | ||||
|         this.id = donor.id; | ||||
|         this.firstname = donor.firstname; | ||||
|         this.middlename = donor.middlename; | ||||
|         this.lastname = donor.lastname; | ||||
|     } | ||||
| } | ||||
| @@ -38,10 +38,10 @@ export class ResponseSelfServiceRunner extends ResponseParticipant implements IR | ||||
|     group: string; | ||||
|  | ||||
|     /** | ||||
|      * The runner's associated donations. | ||||
|      * The runner's associated distance donations. | ||||
|      */ | ||||
|     @IsString() | ||||
|     donations: ResponseSelfServiceDonation[] | ||||
|     distanceDonations: ResponseSelfServiceDonation[] | ||||
|  | ||||
|     /** | ||||
|      * The runner's self-service jwt for auth. | ||||
| @@ -60,7 +60,7 @@ export class ResponseSelfServiceRunner extends ResponseParticipant implements IR | ||||
|         this.distance = runner.distance; | ||||
|         this.donationAmount = runner.distanceDonationAmount; | ||||
|         this.group = this.getTeamString(runner.group); | ||||
|         this.donations = this.getDonations(runner.distanceDonations); | ||||
|         this.distanceDonations = this.getDonations(runner.distanceDonations); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -2,11 +2,6 @@ import { | ||||
|     IsInt | ||||
| } from "class-validator"; | ||||
| import { Donation } from '../entities/Donation'; | ||||
| import { Runner } from '../entities/Runner'; | ||||
| import { RunnerOrganization } from '../entities/RunnerOrganization'; | ||||
| import { RunnerTeam } from '../entities/RunnerTeam'; | ||||
| import { Scan } from '../entities/Scan'; | ||||
| import { User } from '../entities/User'; | ||||
| import { ResponseObjectType } from '../enums/ResponseObjectType'; | ||||
| import { IResponse } from './IResponse'; | ||||
|  | ||||
| @@ -21,6 +16,18 @@ export class ResponseStats implements IResponse { | ||||
|     */ | ||||
|     responseType: ResponseObjectType = ResponseObjectType.STATS; | ||||
|  | ||||
|     /** | ||||
|      * The amount of runners registered via selfservice. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     runnersViaSelfservice: number; | ||||
|  | ||||
|     /** | ||||
|      * The amount of runners registered via kiosk. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     runnersViaKiosk: number; | ||||
|  | ||||
|     /** | ||||
|      * The amount of runners registered in the system. | ||||
|      */ | ||||
| @@ -63,29 +70,53 @@ export class ResponseStats implements IResponse { | ||||
|     @IsInt() | ||||
|     total_donation: number; | ||||
|  | ||||
|     /** | ||||
|      * The total donation count (cent). | ||||
|      */ | ||||
|     @IsInt() | ||||
|     total_donations: number; | ||||
|  | ||||
|     /** | ||||
|      * The total donor count. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     total_donors: number; | ||||
|  | ||||
|     /** | ||||
|      * The average distance ran per runner. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     average_distance: number; | ||||
|  | ||||
|     /** | ||||
|      * The average donation per distance (cent). | ||||
|      */ | ||||
|     @IsInt() | ||||
|     average_donation: number; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new stats response containing some basic statistics for a dashboard or public display. | ||||
|      * @param runners Array containing all runners - the following relations have to be resolved: scans, scans.track | ||||
|      * @param teams Array containing all teams - no relations have to be resolved. | ||||
|      * @param orgs Array containing all orgs - no relations have to be resolved. | ||||
|      * @param users Array containing all users - no relations have to be resolved. | ||||
|      * @param scans Array containing all scans - no relations have to be resolved. | ||||
|      * @param runnersViaSelfservice number of runners registered via selfservice | ||||
|      * @param runners number of runners | ||||
|      * @param teams number of teams - no relations have to be resolved. | ||||
|      * @param orgs number of orgs - no relations have to be resolved. | ||||
|      * @param users number of users - no relations have to be resolved. | ||||
|      * @param scans number of scans - no relations have to be resolved. | ||||
|      * @param donations Array containing all donations - the following relations have to be resolved: runner, runner.scans, runner.scans.track | ||||
|      */ | ||||
|     public constructor(runners: Runner[], teams: RunnerTeam[], orgs: RunnerOrganization[], users: User[], scans: Scan[], donations: Donation[]) { | ||||
|         this.total_runners = runners.length; | ||||
|         this.total_teams = teams.length; | ||||
|         this.total_orgs = orgs.length; | ||||
|         this.total_users = users.length; | ||||
|         this.total_scans = scans.filter(scan => { scan.valid === true }).length; | ||||
|         this.total_distance = runners.reduce((sum, current) => sum + current.distance, 0); | ||||
|     public constructor(runnersViaSelfservice: number, runners: number, teams: number, orgs: number, users: number, scans: number, donations: Donation[], distance: number, donors: number, runnersViaKiosk: number) { | ||||
|         this.runnersViaSelfservice = runnersViaSelfservice; | ||||
|         this.total_runners = runners; | ||||
|         this.total_teams = teams; | ||||
|         this.total_orgs = orgs; | ||||
|         this.total_users = users; | ||||
|         this.total_scans = scans; | ||||
|         this.total_distance = distance; | ||||
|         this.total_donation = donations.reduce((sum, current) => sum + current.amount, 0); | ||||
|         this.total_donations = donations.length; | ||||
|         this.average_donation = this.total_donation / this.total_donations | ||||
|         this.total_donors = donors; | ||||
|         this.average_distance = this.total_distance / this.total_runners; | ||||
|         this.runnersViaKiosk = runnersViaKiosk; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -49,7 +49,15 @@ export class ResponseStatsOrgnisation implements IResponse { | ||||
|     public constructor(org: RunnerOrganization) { | ||||
|         this.name = org.name; | ||||
|         this.id = org.id; | ||||
|         this.distance = org.distance; | ||||
|         this.donationAmount = org.distanceDonationAmount; | ||||
|         try { | ||||
|             this.distance = org.distance; | ||||
|         } catch { | ||||
|             this.distance = -1; | ||||
|         } | ||||
|         try { | ||||
|             this.donationAmount = org.distanceDonationAmount; | ||||
|         } catch { | ||||
|             this.donationAmount = -1; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import { | ||||
|     IsInt, | ||||
|     IsObject, | ||||
|     IsOptional, | ||||
|     IsString | ||||
| } from "class-validator"; | ||||
| import { Runner } from '../entities/Runner'; | ||||
| @@ -55,6 +56,13 @@ export class ResponseStatsRunner implements IResponse { | ||||
|     @IsInt() | ||||
|     donationAmount: number; | ||||
|  | ||||
|     /** | ||||
|      * The runner's fastest laptime in seconds. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsOptional() | ||||
|     minLaptime?: number; | ||||
|  | ||||
|     /** | ||||
|      * The runner's group. | ||||
|      */ | ||||
| @@ -65,13 +73,28 @@ export class ResponseStatsRunner implements IResponse { | ||||
|      * Creates a new runner stats response from a runner | ||||
|      * @param runner The runner whoes response shall be generated - the following relations have to be resolved: scans, group, distanceDonations, scans.track | ||||
|      */ | ||||
|     public constructor(runner: Runner) { | ||||
|     public constructor(runner: Runner, laptime?: number) { | ||||
|         this.id = runner.id; | ||||
|         this.firstname = runner.firstname; | ||||
|         this.middlename = runner.middlename; | ||||
|         if (runner.firstname) { | ||||
|             this.middlename = runner.middlename; | ||||
|         } | ||||
|         this.lastname = runner.lastname; | ||||
|         this.distance = runner.distance; | ||||
|         this.donationAmount = runner.distanceDonationAmount; | ||||
|         try { | ||||
|             this.distance = runner.distance; | ||||
|         } | ||||
|         catch { | ||||
|             this.distance = -1; | ||||
|         } | ||||
|         try { | ||||
|             this.donationAmount = runner.distanceDonationAmount; | ||||
|         } | ||||
|         catch { | ||||
|             this.donationAmount = -1; | ||||
|         } | ||||
|         if (laptime) { | ||||
|             this.minLaptime = laptime; | ||||
|         } | ||||
|         this.group = runner.group.toResponse(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -57,7 +57,15 @@ export class ResponseStatsTeam implements IResponse { | ||||
|         this.name = team.name; | ||||
|         this.id = team.id; | ||||
|         this.parent = team.parentGroup.toResponse(); | ||||
|         this.distance = team.distance; | ||||
|         this.donationAmount = team.distanceDonationAmount; | ||||
|         try { | ||||
|             this.distance = team.distance; | ||||
|         } catch { | ||||
|             this.distance = -1; | ||||
|         } | ||||
|         try { | ||||
|             this.donationAmount = team.distanceDonationAmount; | ||||
|         } catch { | ||||
|             this.donationAmount = -1; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import * as argon2 from "argon2"; | ||||
| import { hash } from '@node-rs/argon2'; | ||||
| import { Connection } from 'typeorm'; | ||||
| import { Factory, Seeder } from 'typeorm-seeding'; | ||||
| import * as uuid from 'uuid'; | ||||
| @@ -11,7 +11,7 @@ import { PermissionAction } from '../models/enums/PermissionAction'; | ||||
| import { PermissionTarget } from '../models/enums/PermissionTargets'; | ||||
| /** | ||||
|  * Seeds a admin group with a demo user into the database for initial setup and auto recovery. | ||||
|  * We know that the nameing isn't perfectly fitting. Feel free to change it. | ||||
|  * We know that the naming isn't perfectly fitting. Feel free to change it. | ||||
|  */ | ||||
| export default class SeedUsers implements Seeder { | ||||
|     public async run(factory: Factory, connection: Connection): Promise<any> { | ||||
| @@ -33,7 +33,7 @@ export default class SeedUsers implements Seeder { | ||||
|         initialUser.lastname = "demo"; | ||||
|         initialUser.username = "demo"; | ||||
|         initialUser.uuid = uuid.v4(); | ||||
|         initialUser.password = await argon2.hash("demo" + initialUser.uuid); | ||||
|         initialUser.password = await hash("demo" + initialUser.uuid); | ||||
|         initialUser.email = "demo@dev.lauf-fuer-kaya.de" | ||||
|         initialUser.groups = [group]; | ||||
|         return await connection.getRepository(User).save(initialUser); | ||||
|   | ||||
| @@ -1,174 +1,186 @@ | ||||
| import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
|  | ||||
| let access_token; | ||||
| let axios_config; | ||||
|  | ||||
| beforeAll(async () => { | ||||
| 	jest.setTimeout(20000); | ||||
| 	const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); | ||||
| 	access_token = res.data["access_token"]; | ||||
| 	axios_config = { | ||||
| 		headers: { "authorization": "Bearer " + access_token }, | ||||
| 		validateStatus: undefined | ||||
| 	}; | ||||
| }); | ||||
|  | ||||
|  | ||||
| describe('POST /api/cards illegally', () => { | ||||
| 	it('non-existant runner input should return 404', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards', { | ||||
| 			"runner": 999999999999999999999999 | ||||
| 		}, axios_config); | ||||
| 		expect(res.status).toEqual(404); | ||||
| 		expect(res.headers['content-type']).toContain("application/json") | ||||
| 	}); | ||||
| }); | ||||
| // --------------- | ||||
| describe('POST /api/cards successfully (without runner)', () => { | ||||
| 	it('creating a card with the minimum amount of parameters should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards', null, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		delete res.data.id; | ||||
| 		delete res.data.code; | ||||
| 		expect(res.data).toEqual({ | ||||
| 			"runner": null, | ||||
| 			"enabled": true, | ||||
| 			"responseType": "RUNNERCARD" | ||||
| 		}); | ||||
| 	}); | ||||
| 	it('creating a disabled card should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards', { | ||||
| 			"enabled": false | ||||
| 		}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		delete res.data.id; | ||||
| 		delete res.data.code; | ||||
| 		expect(res.data).toEqual({ | ||||
| 			"runner": null, | ||||
| 			"enabled": false, | ||||
| 			"responseType": "RUNNERCARD" | ||||
| 		}); | ||||
| 	}); | ||||
| 	it('creating a enabled card should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards', { | ||||
| 			"enabled": true | ||||
| 		}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		delete res.data.id; | ||||
| 		delete res.data.code; | ||||
| 		expect(res.data).toEqual({ | ||||
| 			"runner": null, | ||||
| 			"enabled": true, | ||||
| 			"responseType": "RUNNERCARD" | ||||
| 		}); | ||||
| 	}); | ||||
| }); | ||||
| // --------------- | ||||
| describe('POST /api/cards successfully (with runner)', () => { | ||||
| 	let added_org; | ||||
| 	let added_runner; | ||||
| 	it('creating a new org with just a name should return 200', async () => { | ||||
| 		const res1 = await axios.post(base + '/api/organizations', { | ||||
| 			"name": "test123" | ||||
| 		}, axios_config); | ||||
| 		added_org = res1.data | ||||
| 		expect(res1.status).toEqual(200); | ||||
| 		expect(res1.headers['content-type']).toContain("application/json") | ||||
| 	}); | ||||
| 	it('creating a new runner with only needed params should return 200', async () => { | ||||
| 		const res2 = await axios.post(base + '/api/runners', { | ||||
| 			"firstname": "first", | ||||
| 			"lastname": "last", | ||||
| 			"group": added_org.id | ||||
| 		}, axios_config); | ||||
| 		added_runner = res2.data; | ||||
| 		expect(res2.status).toEqual(200); | ||||
| 		expect(res2.headers['content-type']).toContain("application/json") | ||||
| 	}); | ||||
| 	it('creating a card with the minimum amount of parameters should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards', { | ||||
| 			"runner": added_runner.id | ||||
| 		}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		delete res.data.id; | ||||
| 		delete res.data.code; | ||||
| 		expect(res.data).toEqual({ | ||||
| 			"runner": added_runner, | ||||
| 			"enabled": true, | ||||
| 			"responseType": "RUNNERCARD" | ||||
| 		}); | ||||
| 	}); | ||||
| 	it('creating a card with runner (no optional params) should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards', { | ||||
| 			"runner": added_runner.id | ||||
| 		}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		delete res.data.id; | ||||
| 		delete res.data.code; | ||||
| 		expect(res.data).toEqual({ | ||||
| 			"runner": added_runner, | ||||
| 			"enabled": true, | ||||
| 			"responseType": "RUNNERCARD" | ||||
| 		}); | ||||
| 	}); | ||||
| 	it('creating a enabled card with runner should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards', { | ||||
| 			"runner": added_runner.id, | ||||
| 			"enabled": true | ||||
| 		}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		delete res.data.id; | ||||
| 		delete res.data.code; | ||||
| 		expect(res.data).toEqual({ | ||||
| 			"runner": added_runner, | ||||
| 			"enabled": true, | ||||
| 			"responseType": "RUNNERCARD" | ||||
| 		}); | ||||
| 	}); | ||||
| 	it('creating a disabled card with runner should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards', { | ||||
| 			"runner": added_runner.id, | ||||
| 			"enabled": false | ||||
| 		}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		delete res.data.id; | ||||
| 		delete res.data.code; | ||||
| 		expect(res.data).toEqual({ | ||||
| 			"runner": added_runner, | ||||
| 			"enabled": false, | ||||
| 			"responseType": "RUNNERCARD" | ||||
| 		}); | ||||
| 	}); | ||||
| }); | ||||
| // --------------- | ||||
| describe('POST /api/cards/bulk successfully', () => { | ||||
| 	it('creating a single new bulk card should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards/bulk?count=1', {}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 	}); | ||||
| 	it('creating 50 new bulk card should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards/bulk?count=50', {}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 	}); | ||||
| 	it('creating 250 new bulk card should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards/bulk?count=250', {}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 	}); | ||||
| 	it('creating 2000 new bulk card should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards/bulk?count=2000', {}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 	}); | ||||
| import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
|  | ||||
| let access_token; | ||||
| let axios_config; | ||||
|  | ||||
| beforeAll(async () => { | ||||
| 	jest.setTimeout(20000); | ||||
| 	const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); | ||||
| 	access_token = res.data["access_token"]; | ||||
| 	axios_config = { | ||||
| 		headers: { "authorization": "Bearer " + access_token }, | ||||
| 		validateStatus: undefined | ||||
| 	}; | ||||
| }); | ||||
|  | ||||
|  | ||||
| describe('POST /api/cards illegally', () => { | ||||
| 	it('non-existant runner input should return 404', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards', { | ||||
| 			"runner": 999999999999999999999999 | ||||
| 		}, axios_config); | ||||
| 		expect(res.status).toEqual(404); | ||||
| 		expect(res.headers['content-type']).toContain("application/json") | ||||
| 	}); | ||||
| }); | ||||
| // --------------- | ||||
| describe('POST /api/cards successfully (without runner)', () => { | ||||
| 	it('creating a card with the minimum amount of parameters should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards', null, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		delete res.data.id; | ||||
| 		delete res.data.code; | ||||
| 		expect(res.data).toEqual({ | ||||
| 			"runner": null, | ||||
| 			"enabled": true, | ||||
| 			"responseType": "RUNNERCARD" | ||||
| 		}); | ||||
| 	}); | ||||
| 	it('creating a disabled card should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards', { | ||||
| 			"enabled": false | ||||
| 		}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		delete res.data.id; | ||||
| 		delete res.data.code; | ||||
| 		expect(res.data).toEqual({ | ||||
| 			"runner": null, | ||||
| 			"enabled": false, | ||||
| 			"responseType": "RUNNERCARD" | ||||
| 		}); | ||||
| 	}); | ||||
| 	it('creating a enabled card should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards', { | ||||
| 			"enabled": true | ||||
| 		}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		delete res.data.id; | ||||
| 		delete res.data.code; | ||||
| 		expect(res.data).toEqual({ | ||||
| 			"runner": null, | ||||
| 			"enabled": true, | ||||
| 			"responseType": "RUNNERCARD" | ||||
| 		}); | ||||
| 	}); | ||||
| }); | ||||
| // --------------- | ||||
| describe('POST /api/cards successfully (with runner)', () => { | ||||
| 	let added_org; | ||||
| 	let added_runner; | ||||
| 	it('creating a new org with just a name should return 200', async () => { | ||||
| 		const res1 = await axios.post(base + '/api/organizations', { | ||||
| 			"name": "test123" | ||||
| 		}, axios_config); | ||||
| 		added_org = res1.data | ||||
| 		expect(res1.status).toEqual(200); | ||||
| 		expect(res1.headers['content-type']).toContain("application/json") | ||||
| 	}); | ||||
| 	it('creating a new runner with only needed params should return 200', async () => { | ||||
| 		const res2 = await axios.post(base + '/api/runners', { | ||||
| 			"firstname": "first", | ||||
| 			"lastname": "last", | ||||
| 			"group": added_org.id | ||||
| 		}, axios_config); | ||||
| 		added_runner = res2.data; | ||||
| 		expect(res2.status).toEqual(200); | ||||
| 		expect(res2.headers['content-type']).toContain("application/json") | ||||
| 	}); | ||||
| 	it('creating a card with the minimum amount of parameters should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards', { | ||||
| 			"runner": added_runner.id | ||||
| 		}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		delete res.data.id; | ||||
| 		delete res.data.code; | ||||
| 		expect(res.data).toEqual({ | ||||
| 			"runner": added_runner, | ||||
| 			"enabled": true, | ||||
| 			"responseType": "RUNNERCARD" | ||||
| 		}); | ||||
| 	}); | ||||
| 	it('creating a card with runner (no optional params) should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards', { | ||||
| 			"runner": added_runner.id | ||||
| 		}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		delete res.data.id; | ||||
| 		delete res.data.code; | ||||
| 		expect(res.data).toEqual({ | ||||
| 			"runner": added_runner, | ||||
| 			"enabled": true, | ||||
| 			"responseType": "RUNNERCARD" | ||||
| 		}); | ||||
| 	}); | ||||
| 	it('creating a enabled card with runner should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards', { | ||||
| 			"runner": added_runner.id, | ||||
| 			"enabled": true | ||||
| 		}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		delete res.data.id; | ||||
| 		delete res.data.code; | ||||
| 		expect(res.data).toEqual({ | ||||
| 			"runner": added_runner, | ||||
| 			"enabled": true, | ||||
| 			"responseType": "RUNNERCARD" | ||||
| 		}); | ||||
| 	}); | ||||
| 	it('creating a disabled card with runner should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards', { | ||||
| 			"runner": added_runner.id, | ||||
| 			"enabled": false | ||||
| 		}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		delete res.data.id; | ||||
| 		delete res.data.code; | ||||
| 		expect(res.data).toEqual({ | ||||
| 			"runner": added_runner, | ||||
| 			"enabled": false, | ||||
| 			"responseType": "RUNNERCARD" | ||||
| 		}); | ||||
| 	}); | ||||
| }); | ||||
| // --------------- | ||||
| describe('POST /api/cards/bulk successfully', () => { | ||||
| 	it('creating a single new bulk card should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards/bulk?count=1', {}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 	}); | ||||
| 	it('creating a single new bulk card and letting the system return it should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards/bulk?count=1&returnCards=true', {}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		expect(res.data[0].id).toBeDefined(); | ||||
| 	}); | ||||
| 	it('creating 50 new bulk card should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards/bulk?count=50', {}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 	}); | ||||
| 	it('creating 50 new bulk cards and letting the system return it should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards/bulk?count=50&returnCards=true', {}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		expect(res.data.length).toEqual(50); | ||||
| 	}); | ||||
| 	it('creating 250 new bulk card should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards/bulk?count=250', {}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 	}); | ||||
| 	it('creating 2000 new bulk card should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/cards/bulk?count=2000', {}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 	}); | ||||
| }); | ||||
							
								
								
									
										19
									
								
								src/tests/create_mass_scans.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/tests/create_mass_scans.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| import axios from 'axios'; | ||||
|  | ||||
| async function main() { | ||||
|     console.time("batches") | ||||
|     for (let i = 0; i < 100; i++) { | ||||
|         const batch = []; | ||||
|         for (let i = 0; i < 6; i++) { | ||||
|             batch.push(axios.post('http://localhost:4010/api/scans/trackscans', { card: 200000000001, station: 2 }, { | ||||
|                 headers: { | ||||
|                     Authorization: 'Bearer 10F2E64.BB4F6CC5-2148-4CCF-88B5-0AA85D0508A9' | ||||
|                 } | ||||
|             })) | ||||
|         } | ||||
|         await Promise.all(batch) | ||||
|         console.timeLog("batches", `Finished batch ${i}`) | ||||
|     } | ||||
|     console.timeEnd("batches") | ||||
| } | ||||
| main(); | ||||
| @@ -170,7 +170,7 @@ describe('POST /api/donations/fixed successfully', () => { | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json") | ||||
| 	}); | ||||
| 	it('creating a new fixed donation should return 200', async () => { | ||||
| 	it('creating a new fixed donation with more params should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/donations/fixed', { | ||||
| 			"donor": added_donor.id, | ||||
| 			"amount": 1000 | ||||
| @@ -181,6 +181,25 @@ describe('POST /api/donations/fixed successfully', () => { | ||||
| 		expect(res.data).toEqual({ | ||||
| 			"donor": added_donor, | ||||
| 			"amount": 1000, | ||||
| 			"paidAmount": 0, | ||||
| 			"status": "OPEN", | ||||
| 			"responseType": "DONATION" | ||||
| 		}); | ||||
| 	}); | ||||
| 	it('creating a new fixed donation with all params should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/donations/fixed', { | ||||
| 			"donor": added_donor.id, | ||||
| 			"amount": 1000, | ||||
| 			"paidAmount": 1000 | ||||
| 		}, axios_config); | ||||
| 		delete res.data.id; | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		expect(res.data).toEqual({ | ||||
| 			"donor": added_donor, | ||||
| 			"amount": 1000, | ||||
| 			"paidAmount": 1000, | ||||
| 			"status": "PAID", | ||||
| 			"responseType": "DONATION" | ||||
| 		}); | ||||
| 	}); | ||||
| @@ -219,7 +238,7 @@ describe('POST /api/donations/distance successfully', () => { | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json") | ||||
| 	}); | ||||
| 	it('creating a new fixed donation should return 200', async () => { | ||||
| 	it('creating a new distance donation with most params should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/donations/distance', { | ||||
| 			"runner": added_runner.id, | ||||
| 			"amountPerDistance": 100, | ||||
| @@ -233,6 +252,28 @@ describe('POST /api/donations/distance successfully', () => { | ||||
| 			"amountPerDistance": 100, | ||||
| 			"runner": added_runner, | ||||
| 			"amount": 0, | ||||
| 			"paidAmount": 0, | ||||
| 			"status": "PAID", | ||||
| 			"responseType": "DISTANCEDONATION" | ||||
| 		}) | ||||
| 	}); | ||||
| 	it('creating a new distance donation with all params should return 200', async () => { | ||||
| 		const res = await axios.post(base + '/api/donations/distance', { | ||||
| 			"runner": added_runner.id, | ||||
| 			"amountPerDistance": 100, | ||||
| 			"donor": added_donor.id, | ||||
| 			"paidAmount": 1000 | ||||
| 		}, axios_config); | ||||
| 		delete res.data.id; | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		expect(res.data).toEqual({ | ||||
| 			"donor": added_donor, | ||||
| 			"amountPerDistance": 100, | ||||
| 			"runner": added_runner, | ||||
| 			"amount": 0, | ||||
| 			"paidAmount": 1000, | ||||
| 			"status": "PAID", | ||||
| 			"responseType": "DISTANCEDONATION" | ||||
| 		}) | ||||
| 	}); | ||||
|   | ||||
| @@ -213,6 +213,17 @@ describe('adding + updating fixed donation valid', () => { | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		expect(res.data.amount).toEqual(42); | ||||
| 	}); | ||||
| 	it('updating paidAmount should return 200', async () => { | ||||
| 		const res = await axios.put(base + '/api/donations/fixed/' + added_donation.id, { | ||||
| 			"id": added_donation.id, | ||||
| 			"donor": added_donor.id, | ||||
| 			"amount": 42, | ||||
| 			"paidAmount": 10 | ||||
| 		}, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		expect(res.data.paidAmount).toEqual(10); | ||||
| 	}); | ||||
| 	it('updating donor should return 200', async () => { | ||||
| 		const res = await axios.put(base + '/api/donations/fixed/' + added_donation.id, { | ||||
| 			"id": added_donation.id, | ||||
| @@ -317,6 +328,19 @@ describe('adding + updating distance donation valid', () => { | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		expect(res.data.amountPerDistance).toEqual(69); | ||||
| 	}); | ||||
| 	it('updating paidAmount should return 200', async () => { | ||||
| 		const res = await axios.put(base + '/api/donations/distance/' + added_donation.id, { | ||||
| 			"id": added_donation.id, | ||||
| 			"runner": added_runner.id, | ||||
| 			"amountPerDistance": 69, | ||||
| 			"donor": added_donor.id, | ||||
| 			"paidAmount": 10 | ||||
| 		}, axios_config); | ||||
| 		delete res.data.donor.donationAmount; | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		expect(res.data.paidAmount).toEqual(10); | ||||
| 	}); | ||||
| 	it('updating runner should return 200', async () => { | ||||
| 		const res = await axios.put(base + '/api/donations/distance/' + added_donation.id, { | ||||
| 			"id": added_donation.id, | ||||
|   | ||||
| @@ -22,6 +22,12 @@ describe('deletion (non-existant)', () => { | ||||
|         expect(res2.status).toEqual(204); | ||||
|     }); | ||||
| }); | ||||
| describe('deletion of citizen sould fail', () => { | ||||
|     it('delete', async () => { | ||||
|         const res3 = await axios.delete(base + '/api/organizations/1', axios_config); | ||||
|         expect(res3.status).toEqual(400); | ||||
|     }); | ||||
| }); | ||||
| // --------------- | ||||
| describe('adding + deletion (successfull)', () => { | ||||
|     let added_org_id | ||||
|   | ||||
| @@ -66,6 +66,8 @@ describe('adding + getting scans', () => { | ||||
| 		const res = await axios.get(base + '/api/scans/' + added_scan.id, axios_config); | ||||
| 		expect(res.status).toEqual(200); | ||||
| 		expect(res.headers['content-type']).toContain("application/json"); | ||||
| 		delete res.data.runner.distance; | ||||
| 		delete added_scan.runner.distance; | ||||
| 		expect(res.data).toEqual(added_scan); | ||||
| 	}); | ||||
| 	it('check if scans was added via the runner/scans endpoint.', async () => { | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| import { faker } from '@faker-js/faker'; | ||||
| import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
|  | ||||
| const base = "http://localhost:" + config.internal_port | ||||
|  | ||||
| let access_token; | ||||
| @@ -21,7 +23,7 @@ describe('delete selfservice runner invalid', () => { | ||||
|         const res = await axios.post(base + '/api/runners/register', { | ||||
|             "firstname": "string", | ||||
|             "lastname": "string", | ||||
|             "email": "user@example.com" | ||||
|             "email": faker.internet.exampleEmail(), | ||||
|         }, axios_config); | ||||
|         added_runner = res.data; | ||||
|         expect(res.status).toEqual(200); | ||||
| @@ -50,7 +52,7 @@ describe('delete selfservice runner valid', () => { | ||||
|         const res = await axios.post(base + '/api/runners/register', { | ||||
|             "firstname": "string", | ||||
|             "lastname": "string", | ||||
|             "email": "user@example.com" | ||||
|             "email": faker.internet.exampleEmail(), | ||||
|         }, axios_config); | ||||
|         added_runner = res.data; | ||||
|         expect(res.status).toEqual(200); | ||||
|   | ||||
| @@ -15,20 +15,20 @@ beforeAll(async () => { | ||||
|     }; | ||||
| }); | ||||
|  | ||||
| describe('POST /api/runners/me/forgot invalid syntax/mail should fail', () => { | ||||
| describe('POST /api/runners/me/login invalid syntax/mail should fail', () => { | ||||
|     it('get without mail return 404', async () => { | ||||
|         const res = await axios.post(base + '/api/runners/forgot', null, axios_config); | ||||
|         const res = await axios.post(base + '/api/runners/login', null, axios_config); | ||||
|         expect(res.status).toEqual(404); | ||||
|         expect(res.headers['content-type']).toContain("application/json"); | ||||
|     }); | ||||
|     it('get without bs mail return 404', async () => { | ||||
|         const res = await axios.post(base + '/api/runners/forgot?mail=asdasdasdasdasd@tester.test.dev.lauf-fuer-kaya.de', null, axios_config); | ||||
|         const res = await axios.post(base + '/api/runners/login?mail=asdasdasdasdasd@tester.test.dev.lauf-fuer-kaya.de', null, axios_config); | ||||
|         expect(res.status).toEqual(404); | ||||
|         expect(res.headers['content-type']).toContain("application/json"); | ||||
|     }); | ||||
| }); | ||||
| // --------------- | ||||
| describe('POST /api/runners/me/forgot 2 times within timeout should fail', () => { | ||||
| describe('POST /api/runners/me/login 2 times within timeout should fail', () => { | ||||
|     let added_runner; | ||||
|     it('registering as citizen should return 200', async () => { | ||||
|         const res = await axios.post(base + '/api/runners/register', { | ||||
| @@ -42,19 +42,19 @@ describe('POST /api/runners/me/forgot 2 times within timeout should fail', () => | ||||
|         added_runner = res.data; | ||||
|     }); | ||||
|     it('post with valid mail should return 200', async () => { | ||||
|         const res = await axios.post(base + '/api/runners/forgot?mail=' + added_runner.email, null, axios_config); | ||||
|         const res = await axios.post(base + '/api/runners/login?mail=' + added_runner.email, null, axios_config); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json"); | ||||
|     }); | ||||
|     it('2nd post with valid mail should return 406', async () => { | ||||
|         const res = await axios.post(base + '/api/runners/forgot?mail=' + added_runner.email, null, axios_config); | ||||
|         const res = await axios.post(base + '/api/runners/login?mail=' + added_runner.email, null, axios_config); | ||||
|         expect(res.status).toEqual(406); | ||||
|         expect(res.headers['content-type']).toContain("application/json"); | ||||
|     }); | ||||
| }); | ||||
|  | ||||
| // --------------- | ||||
| describe('POST /api/runners/me/forgot valid should return 200', () => { | ||||
| describe('POST /api/runners/me/login valid should return 200', () => { | ||||
|     let added_runner; | ||||
|     let new_token; | ||||
|     it('registering as citizen should return 200', async () => { | ||||
| @@ -69,7 +69,7 @@ describe('POST /api/runners/me/forgot valid should return 200', () => { | ||||
|         added_runner = res.data; | ||||
|     }); | ||||
|     it('post with valid mail should return 200', async () => { | ||||
|         const res = await axios.post(base + '/api/runners/forgot?mail=' + added_runner.email, null, axios_config); | ||||
|         const res = await axios.post(base + '/api/runners/login?mail=' + added_runner.email, null, axios_config); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json"); | ||||
|         new_token = res.data.token; | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import { faker } from '@faker-js/faker'; | ||||
| import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
| @@ -30,7 +31,7 @@ describe('register + get should return 200', () => { | ||||
|             "firstname": "string", | ||||
|             "middlename": "string", | ||||
|             "lastname": "string", | ||||
|             "email": "user@example.com" | ||||
|             "email": faker.internet.exampleEmail(), | ||||
|         }, axios_config); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json"); | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import { faker } from '@faker-js/faker'; | ||||
| import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
| @@ -39,7 +40,7 @@ describe('register invalid citizen', () => { | ||||
|         const res = await axios.post(base + '/api/runners/register', { | ||||
|             "middlename": "string", | ||||
|             "lastname": "string", | ||||
|             "email": "user@example.com" | ||||
|             "email": faker.internet.exampleEmail(), | ||||
|         }, axios_config); | ||||
|         expect(res.status).toEqual(400); | ||||
|         expect(res.headers['content-type']).toContain("application/json"); | ||||
| @@ -48,7 +49,7 @@ describe('register invalid citizen', () => { | ||||
|         const res = await axios.post(base + '/api/runners/register', { | ||||
|             "firstname": "string", | ||||
|             "middlename": "string", | ||||
|             "email": "user@example.com" | ||||
|             "email": faker.internet.exampleEmail(), | ||||
|         }, axios_config); | ||||
|         expect(res.status).toEqual(400); | ||||
|         expect(res.headers['content-type']).toContain("application/json"); | ||||
| @@ -59,7 +60,26 @@ describe('register invalid citizen', () => { | ||||
|             "middlename": "string", | ||||
|             "lastname": "string", | ||||
|             "phone": "peter", | ||||
|             "email": "user@example.com" | ||||
|             "email": faker.internet.exampleEmail(), | ||||
|         }, axios_config); | ||||
|         expect(res.status).toEqual(400); | ||||
|         expect(res.headers['content-type']).toContain("application/json"); | ||||
|     }); | ||||
|     it('registering as citizen with duplicate mail should return 400', async () => { | ||||
|         const mail = faker.internet.exampleEmail(); | ||||
|         await axios.post(base + '/api/runners/register', { | ||||
|             "firstname": "string", | ||||
|             "middlename": "string", | ||||
|             "lastname": "string", | ||||
|             "phone": "peter", | ||||
|             "email": mail, | ||||
|         }, axios_config); | ||||
|         const res = await axios.post(base + '/api/runners/register', { | ||||
|             "firstname": "string", | ||||
|             "middlename": "string", | ||||
|             "lastname": "string", | ||||
|             "phone": "peter", | ||||
|             "email": mail, | ||||
|         }, axios_config); | ||||
|         expect(res.status).toEqual(400); | ||||
|         expect(res.headers['content-type']).toContain("application/json"); | ||||
| @@ -71,7 +91,7 @@ describe('register citizen valid', () => { | ||||
|         const res = await axios.post(base + '/api/runners/register', { | ||||
|             "firstname": "string", | ||||
|             "lastname": "string", | ||||
|             "email": "user@example.com" | ||||
|             "email": faker.internet.exampleEmail(), | ||||
|         }, axios_config); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json"); | ||||
| @@ -81,7 +101,7 @@ describe('register citizen valid', () => { | ||||
|             "firstname": "string", | ||||
|             "middlename": "string", | ||||
|             "lastname": "string", | ||||
|             "email": "user@example.com", | ||||
|             "email": faker.internet.exampleEmail(), | ||||
|             "phone": "+4909132123456", | ||||
|             "address": { | ||||
|                 address1: "Teststreet 1", | ||||
| @@ -187,7 +207,7 @@ describe('register valid company', () => { | ||||
|             "firstname": "string", | ||||
|             "middlename": "string", | ||||
|             "lastname": "string", | ||||
|             "email": "user@example.com", | ||||
|             "email": faker.internet.exampleEmail(), | ||||
|             "phone": "+4909132123456", | ||||
|             "address": { | ||||
|                 address1: "Teststreet 1", | ||||
| @@ -214,7 +234,7 @@ describe('register valid company', () => { | ||||
|             "firstname": "string", | ||||
|             "middlename": "string", | ||||
|             "lastname": "string", | ||||
|             "email": "user@example.com", | ||||
|             "email": faker.internet.exampleEmail(), | ||||
|             "phone": "+4909132123456", | ||||
|             "address": { | ||||
|                 address1: "Teststreet 1", | ||||
| @@ -232,7 +252,7 @@ describe('register valid company', () => { | ||||
|             "firstname": "string", | ||||
|             "middlename": "string", | ||||
|             "lastname": "string", | ||||
|             "email": "user@example.com", | ||||
|             "email": faker.internet.exampleEmail(), | ||||
|             "phone": "+4909132123456", | ||||
|             "address": { | ||||
|                 address1: "Teststreet 1", | ||||
|   | ||||
							
								
								
									
										89
									
								
								src/tests/stats/stats_get.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/tests/stats/stats_get.spec.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| import axios from 'axios'; | ||||
| import { config } from '../../config'; | ||||
| const base = "http://localhost:" + config.internal_port | ||||
|  | ||||
| let axios_config_full; | ||||
| let axios_config_stats; | ||||
|  | ||||
| beforeAll(async () => { | ||||
|     jest.setTimeout(20000); | ||||
|     const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); | ||||
|     let access_token = res.data["access_token"]; | ||||
|     axios_config_full = { | ||||
|         headers: { "authorization": "Bearer " + access_token }, | ||||
|         validateStatus: undefined | ||||
|     }; | ||||
|     const res2 = await axios.post(base + '/api/statsclients', { username: "demo", password: "demo" }, axios_config_full); | ||||
|     access_token = res2.data["key"]; | ||||
|     axios_config_stats = { | ||||
|         headers: { "authorization": "Bearer " + access_token }, | ||||
|         validateStatus: undefined | ||||
|     }; | ||||
| }); | ||||
|  | ||||
| describe('GET /api/stats/runners/distance w/o auth should return 200', () => { | ||||
|     it('get with invalid token should return 401', async () => { | ||||
|         const res = await axios.get(base + '/api/stats/runners/distance', { | ||||
|             headers: { "authorization": "Bearer 123123123123123123" }, | ||||
|             validateStatus: undefined | ||||
|         }); | ||||
|         expect(res.status).toEqual(401); | ||||
|     }); | ||||
| }); | ||||
| // --------------- | ||||
| describe('GET /api/stats should return 200', () => { | ||||
|     it('get w/o auth should return 200', async () => { | ||||
|         const res = await axios.get(base + '/api/stats', { validateStatus: undefined }); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json"); | ||||
|     }); | ||||
|     it('get w/ auth should return 200', async () => { | ||||
|         const res = await axios.get(base + '/api/stats', axios_config_stats); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json"); | ||||
|     }); | ||||
| }); | ||||
| // --------------- | ||||
| describe('GET /api/stats/runners/* should return 200', () => { | ||||
|     it('get by distance w/ auth should return 200', async () => { | ||||
|         const res = await axios.get(base + '/api/stats/runners/distance', axios_config_stats); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json"); | ||||
|     }); | ||||
|     it('get by donations w/ auth should return 200', async () => { | ||||
|         const res = await axios.get(base + '/api/stats/runners/donations', axios_config_stats); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json"); | ||||
|     }); | ||||
|     it('get by laptime w/ auth should return 200', async () => { | ||||
|         const res = await axios.get(base + '/api/stats/runners/laptime', axios_config_stats); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json"); | ||||
|     }); | ||||
| }); | ||||
| // --------------- | ||||
| describe('GET /api/stats/teams/* should return 200', () => { | ||||
|     it('get by distance w/ auth should return 200', async () => { | ||||
|         const res = await axios.get(base + '/api/stats/teams/distance', axios_config_stats); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json"); | ||||
|     }); | ||||
|     it('get by donations w/ auth should return 200', async () => { | ||||
|         const res = await axios.get(base + '/api/stats/teams/donations', axios_config_stats); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json"); | ||||
|     }); | ||||
| }); | ||||
| // --------------- | ||||
| describe('GET /api/stats/organizations/* should return 200', () => { | ||||
|     it('get by distance w/ auth should return 200', async () => { | ||||
|         const res = await axios.get(base + '/api/stats/organizations/distance', axios_config_stats); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json"); | ||||
|     }); | ||||
|     it('get by donations w/ auth should return 200', async () => { | ||||
|         const res = await axios.get(base + '/api/stats/organizations/donations', axios_config_stats); | ||||
|         expect(res.status).toEqual(200); | ||||
|         expect(res.headers['content-type']).toContain("application/json"); | ||||
|     }); | ||||
| }); | ||||
		Reference in New Issue
	
	Block a user