Compare commits
	
		
			388 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c4edccace7 | |||
| 74de6559d7 | |||
| a6f73c733c | |||
| ca3d093e54 | |||
| 28cfbaa662 | |||
| 90e1ad7db7 | |||
| 906a1dc9e7 | |||
| 5872c6335b | |||
| 701706c028 | |||
| 09bbc70f5f | |||
| dd9cb6d3ef | |||
| 23c732b690 | |||
| 656d564baa | |||
| f3f5cb462e | |||
| 9959172f2a | |||
| 8f0a396dd0 | |||
| a18d4d3cee | |||
| 390b36dfd4 | |||
| 3b718f3ce5 | |||
| 321b20b073 | |||
| f7a0ec7174 | |||
| 110a84783e | |||
| 333e806da4 | |||
| f4f621973a | |||
| bcad691045 | |||
| 74791df68b | |||
| 8425043099 | |||
| 74b982afba | |||
| 3aefa75412 | |||
| 71cab4e836 | |||
| 4e10077901 | |||
| c32fa93673 | |||
| 3d1baae0cc | |||
| 94dd7963b7 | |||
| 7ba67b9dca | |||
| 6e5f1bd5ff | |||
| 60ee6ebc1e | |||
| 02295346da | |||
| c4ea808e06 | |||
| ff7406e71a | |||
| 8dc2810c0c | |||
| ff8af090e3 | |||
| bcc15e4286 | |||
| 2a87819486 | |||
| 9d5e486c6d | |||
| e44cc4c4cb | |||
| 581ca5ff6c | |||
| b972395ae8 | |||
| e5f4f6ee59 | |||
| fea4857685 | |||
| f9e75d06b8 | |||
| 38223b194b | |||
| 09b24aa609 | |||
| 348e6cdec7 | |||
| bd1813a0e8 | |||
| e07f258a31 | |||
| 61bbeb0d8f | |||
| 650a55e586 | |||
| 2071c4db33 | |||
| 80e606aa96 | |||
| 20f960ed67 | |||
| e6fe8fcd58 | |||
| 870fd47c83 | |||
| 644045db44 | |||
| 8611fcb849 | |||
| 08e6e59655 | |||
| ae74b3963f | |||
| 54ed313342 | |||
| ad4b903c25 | |||
| 9bd7636a23 | |||
| b94179e3ca | |||
| 827002989e | |||
| eeff67c192 | |||
| 583a4bc0dd | |||
| 53fcff77d0 | |||
| 1f0c842d9e | |||
| 13ccab5e28 | |||
| b5018eb114 | |||
| aedfcfcc83 | |||
| db0876015b | |||
| 69417e93c0 | |||
| f71a22f4dd | |||
| 570c34bed0 | |||
| 7be2971a9e | |||
| b92f633d68 | |||
| d3647e3399 | |||
| 389e423850 | |||
| 46af786516 | |||
| b4c117b7dc | |||
| 5cade25eeb | |||
| fb77f4d798 | |||
| c116338cd7 | |||
| 979d36ea91 | |||
| c43334bf96 | |||
| 71c4caae8b | |||
| 536de2a319 | |||
| e26744b792 | |||
| d02e9dec56 | |||
| 637975305f | |||
| c418603423 | |||
| 78d2ac3027 | |||
| 470703c4de | |||
| e260e16d66 | |||
| 6b0155f014 | |||
| 33890b544b | |||
| d7ea928714 | |||
| 908ac4f1ce | |||
| cf012c0b7e | |||
| 71898d576c | |||
| c964591839 | |||
| cc4bf4451c | |||
| 7dbbd3780d | |||
| 3697783e19 | |||
| 161feaf364 | |||
| 75e2a44c9c | |||
| cd7e9b86b4 | |||
| c6c643ecf1 | |||
| ef15d0d576 | |||
| 5660aecb50 | |||
| 6a66dd803b | |||
| b42f0722d7 | |||
| 45c8bb83be | |||
| 6469e3bc97 | |||
| 10f98e9c99 | |||
| e5b6f650b2 | |||
| 3b2ed3f0f2 | |||
| 20e102ec5c | |||
| 5a003945ac | |||
| 29aeb046de | |||
| 72941da1cb | |||
| 81d2197a3e | |||
| 9dd9304a71 | |||
| 0c87906cc3 | |||
| 1227408407 | |||
| f8d7544517 | |||
| a9843ed459 | |||
| 46f9503543 | |||
| c5d0646c42 | |||
| b441658570 | |||
| e95c457e44 | |||
| 6de9d547b7 | |||
| 3a93c9c078 | |||
| 36d01a0a89 | |||
| 6434b4dfce | |||
| e964a8ed44 | |||
| c39a59e54e | |||
| 34c852b12a | |||
| 7b00b19fce | |||
| ad446500f9 | |||
| d490247d1e | |||
| dee36395a6 | |||
| 6df195b6ec | |||
| 946efef252 | |||
| 73b1114883 | |||
| 1b5465bea8 | |||
| 5288c701c1 | |||
| 10af1ba341 | |||
| 26dff4f418 | |||
| b5f3dec93b | |||
| a82fc0fb9e | |||
| e2ec0a3b64 | |||
| f4668b6e81 | |||
| d5281348b6 | |||
| 1717df113e | |||
| 0355bdbbab | |||
| 02677de5c0 | |||
| 886c1092d6 | |||
| 191569792c | |||
| da1fe34249 | |||
| 4ee807973e | |||
| c5f7cb2c68 | |||
| 88a7089289 | |||
| b89f7ac1b4 | |||
| 8079769881 | |||
| 2274b476d6 | |||
| e12aedd1aa | |||
| 434aaf6136 | |||
| d8b6669d12 | |||
| dd3d93edc7 | |||
| 7bc603028d | |||
| c18012f65a | |||
| b15967ff31 | |||
| 2db6510a8a | |||
| 1837336865 | |||
| eab0e634a2 | |||
| 8870ebdb5e | |||
| 9df9d9ae80 | |||
| 67ba489fe2 | |||
| da9a359251 | |||
| 0661729e5f | |||
| ddafd90d3e | |||
| 8960aa5545 | |||
| a0c2b5ade8 | |||
| a1acd3519f | |||
| c3d008ec0f | |||
| 8ae53f1c49 | |||
| 179c2a5157 | |||
| dd7e5dae36 | |||
| e165f01930 | |||
| 940d62cde4 | |||
| b002cf2df1 | |||
| 56c73c2555 | |||
| 28fb9834e1 | |||
| 6b4b16c13b | |||
| d743f7ee12 | |||
| a4e8311cbd | |||
| c172aa8bf8 | |||
| d1926fe372 | |||
| 2b658ac381 | |||
| 321d291b4b | |||
| 2eb26e4e38 | |||
| 3b06d1a6ef | |||
| de824375d3 | |||
| 11af9c02d9 | |||
| 09e429fc67 | |||
| 703b4f89a6 | |||
| 32e054eb84 | |||
| 5e368552ea | |||
| 0379786cbd | |||
| a9a5eb6735 | |||
| ab70f7e498 | |||
| 1407fe36f3 | |||
| d12801e34d | |||
| 3e7190e279 | |||
| 41423feffe | |||
| 30b585c0c1 | |||
| a3c93f0d39 | |||
| f53894b16a | |||
| 7533c349ef | |||
| 91569ced40 | |||
| f9ae778b21 | |||
| 427dfaafab | |||
| ae589aeb54 | |||
| 1b9d2969eb | |||
| daffbcde72 | |||
| 9445c6f21e | |||
| 6febb99499 | |||
| 6e6979cfe3 | |||
| 230cdb0e37 | |||
| ce450e9b6d | |||
| de36a24191 | |||
| b167ba07f7 | |||
| 4d40225a44 | |||
| 57b9c2babc | |||
| 9dc9ce37d8 | |||
| f245840cde | |||
| 4824547dde | |||
| 8dbee32eee | |||
| ae7c5ff0c3 | |||
| 2a465f88c5 | |||
| 58ae9b589a | |||
| 8bc01d3f24 | |||
| d0df5dd641 | |||
| 2cd15d25e9 | |||
| dafac06bc8 | |||
| e2651728c5 | |||
| 673dea2e57 | |||
| 7fbe649dc9 | |||
| 3766899c83 | |||
| a6c7d54fe7 | |||
| 79bc04bec1 | |||
| f9834b5f4d | |||
| fc7b8f4c16 | |||
| 4f6e81677c | |||
| 6b7ecd3044 | |||
| 8ef5f90abd | |||
| a334adffc6 | |||
| f1db883609 | |||
| e586a11e2a | |||
| 50b893f537 | |||
| 02efb9a8e5 | |||
| 38b9a772cd | |||
| 618430433d | |||
| 84cd398c09 | |||
| 385a9bba73 | |||
| 8218a452bd | |||
| a77e2eb3ad | |||
| d1a0bed00e | |||
| 66d4770858 | |||
| 80c5f9b84d | |||
| 79f46cb745 | |||
| de32a9862d | |||
| 0e119e4834 | |||
| 29c8e00477 | |||
| dc6ad9cdd3 | |||
| dcd754dac8 | |||
| d88fb18319 | |||
| 420e9c4662 | |||
| 98d6a1cc64 | |||
| 09ad081b37 | |||
| aa0fd9cafd | |||
| bae8290273 | |||
| 1b799a6973 | |||
| ed3b55a1e2 | |||
| 97c01ce81a | |||
| e96637219f | |||
| 17244b0006 | |||
| 67a02f06da | |||
| 6b6f345618 | |||
| 2ac9d3e977 | |||
| 93692ec255 | |||
| 99852f591e | |||
| b89525746d | |||
| c05834f2a1 | |||
| 9bbfb4763d | |||
| 22e6070e53 | |||
| ba218c85e0 | |||
| 644d2b06ac | |||
| 8d4c8a4553 | |||
| 077174a9a2 | |||
| ce31b95fb7 | |||
| 881eedbf3a | |||
| 09cb6f7b2b | |||
| bd091d5cb9 | |||
| 8cb67a8d20 | |||
| 290bb29e64 | |||
| d0769a5e37 | |||
| c5b28df2ae | |||
| c108fa509f | |||
| 1e5e9801be | |||
| 09b16c980b | |||
| 4c26fc808e | |||
| 525b11b346 | |||
| 86679b498b | |||
| 46df8b0528 | |||
| 1a4f896a8a | |||
| aaaa15a0ef | |||
| de65b1c699 | |||
| f9437065ee | |||
| b495cadae9 | |||
| 47995b77f7 | |||
| bc24ec5272 | |||
| 2947c41a72 | |||
| ef53035f70 | |||
| 290afc3f8f | |||
| d6e89b0880 | |||
| 2b72552b1f | |||
| df69418855 | |||
| 472e402521 | |||
| a3f282667c | |||
| b86263d972 | |||
| f278320b93 | |||
| 6345666ae6 | |||
| 7b5ebab453 | |||
| d4d713b12d | |||
| ab3af54e15 | |||
| b01e1eb8a1 | |||
| 0724932152 | |||
| cd7b15aadf | |||
| 37fc167002 | |||
| 9feeb302e8 | |||
| bba35d189e | |||
| cd5e4bbd60 | |||
| a513bf13ca | |||
| e3e570e664 | |||
| badff85e28 | |||
| 4a0f75044f | |||
| b729a7cead | |||
| 4375ca92d3 | |||
| 71537b283f | |||
| 63506dac1c | |||
| e716fae1c5 | |||
| f7370bc802 | |||
| 72c3fc78b3 | |||
| 110387dbd3 | |||
| 2820f151e8 | |||
| 9517df5082 | |||
| 56cedf0144 | |||
| bbaee7cd4d | |||
| 8ee2bdf488 | |||
| 97ecc83fe4 | |||
| 57f62a6087 | |||
| 2e760ff461 | |||
| 0df26cbd54 | |||
| 5f1ab4a2f3 | |||
| e1ff8c03e1 | |||
| 55f72c35a6 | |||
| 6c53701a59 | |||
| 02bb634257 | |||
| 5581c03f77 | |||
| cf788fe07b | |||
| 4bf425e1ca | |||
| a2f4fd5d9b | |||
| 295a1524d8 | |||
| 234154255c | |||
| 7b087840ec | |||
| 16b594ebdd | |||
| 67b3101fd1 | 
							
								
								
									
										50
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								.drone.yml
									
									
									
									
									
								
							@@ -9,9 +9,8 @@ steps:
 | 
			
		||||
    commands:
 | 
			
		||||
      - git clone $DRONE_REMOTE_URL .
 | 
			
		||||
      - git checkout $DRONE_SOURCE_BRANCH
 | 
			
		||||
      - mv .env.ci .env
 | 
			
		||||
  - name: run tests
 | 
			
		||||
    image: node:14.15.1-alpine3.12
 | 
			
		||||
    image: node:latest
 | 
			
		||||
    commands:
 | 
			
		||||
      - yarn
 | 
			
		||||
      - yarn test:ci
 | 
			
		||||
@@ -23,8 +22,15 @@ trigger:
 | 
			
		||||
kind: pipeline
 | 
			
		||||
type: docker
 | 
			
		||||
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]
 | 
			
		||||
@@ -37,6 +43,22 @@ steps:
 | 
			
		||||
      tags:
 | 
			
		||||
        - dev
 | 
			
		||||
      registry: registry.odit.services
 | 
			
		||||
  - 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: GITLAB_SSHKEY
 | 
			
		||||
  - name: run full license export
 | 
			
		||||
    depends_on: ["clone"]
 | 
			
		||||
    image: node:14.15.1-alpine3.12
 | 
			
		||||
@@ -49,12 +71,14 @@ steps:
 | 
			
		||||
    settings:
 | 
			
		||||
      branch: dev
 | 
			
		||||
      commit: true
 | 
			
		||||
      commit_message: new license file version [CI SKIP]
 | 
			
		||||
      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: GITLAB_SSHKEY
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
trigger:
 | 
			
		||||
  branch:
 | 
			
		||||
    - dev
 | 
			
		||||
@@ -65,11 +89,20 @@ trigger:
 | 
			
		||||
kind: pipeline
 | 
			
		||||
type: docker
 | 
			
		||||
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
 | 
			
		||||
    depends_on: [clone]
 | 
			
		||||
    settings:
 | 
			
		||||
      username:
 | 
			
		||||
        from_secret: DOCKER_REGISTRY_USER
 | 
			
		||||
@@ -79,6 +112,15 @@ steps:
 | 
			
		||||
      tags:
 | 
			
		||||
        - latest
 | 
			
		||||
      registry: registry.odit.services
 | 
			
		||||
  - 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: GITLAB_SSHKEY
 | 
			
		||||
 | 
			
		||||
trigger:
 | 
			
		||||
  branch:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.env.ci
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								.env.ci
									
									
									
									
									
								
							@@ -6,4 +6,4 @@ DB_USER=unused
 | 
			
		||||
DB_PASSWORD=bla
 | 
			
		||||
DB_NAME=./test.sqlite
 | 
			
		||||
NODE_ENV=dev
 | 
			
		||||
POSTALCODE_COUNTRYCODE=null
 | 
			
		||||
POSTALCODE_COUNTRYCODE=DE
 | 
			
		||||
@@ -1,9 +1,10 @@
 | 
			
		||||
APP_PORT=4010
 | 
			
		||||
DB_TYPE=bla
 | 
			
		||||
DB_TYPE=sqlite
 | 
			
		||||
DB_HOST=bla
 | 
			
		||||
DB_PORT=bla
 | 
			
		||||
DB_USER=bla
 | 
			
		||||
DB_PASSWORD=bla
 | 
			
		||||
DB_NAME=bla
 | 
			
		||||
DB_NAME=./test.sqlite
 | 
			
		||||
NODE_ENV=production
 | 
			
		||||
POSTALCODE_COUNTRYCODE=null
 | 
			
		||||
POSTALCODE_COUNTRYCODE=DE
 | 
			
		||||
SEED_TEST_DATA=false
 | 
			
		||||
							
								
								
									
										1241
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1241
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										104
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										104
									
								
								README.md
									
									
									
									
									
								
							@@ -2,20 +2,18 @@
 | 
			
		||||
 | 
			
		||||
Backend Server
 | 
			
		||||
 | 
			
		||||
## Quickstart 🐳
 | 
			
		||||
> Use this to run the backend with a postgresql db in docker
 | 
			
		||||
 | 
			
		||||
1. Clone the repo or copy the docker-compose 
 | 
			
		||||
2. Run in toe folder that contains the docker-compose file: `docker-compose up -d`
 | 
			
		||||
3. Visit http://127.0.0.1:4010/api/docs to check if the server is running
 | 
			
		||||
4. You can now use the default admin user (`demo:demo`)
 | 
			
		||||
 | 
			
		||||
## Dev Setup 🛠
 | 
			
		||||
> Local dev setup utilizing sqlite3 as the database.
 | 
			
		||||
 | 
			
		||||
### Local w/ sqlite
 | 
			
		||||
 | 
			
		||||
1. Create a .env file in the project root containing:
 | 
			
		||||
   ```
 | 
			
		||||
    APP_PORT=4010
 | 
			
		||||
    DB_TYPE=sqlite
 | 
			
		||||
    DB_HOST=bla
 | 
			
		||||
    DB_PORT=bla
 | 
			
		||||
    DB_USER=bla
 | 
			
		||||
    DB_PASSWORD=bla
 | 
			
		||||
    DB_NAME=./test.sqlite
 | 
			
		||||
   ```
 | 
			
		||||
1. Rename the .env.example file to .env (you can adjust app port and other settings, if needed)
 | 
			
		||||
2. Install Dependencies
 | 
			
		||||
   ```bash
 | 
			
		||||
   yarn
 | 
			
		||||
@@ -25,16 +23,55 @@ Backend Server
 | 
			
		||||
   yarn dev
 | 
			
		||||
   ```
 | 
			
		||||
 | 
			
		||||
### Generate Docs
 | 
			
		||||
### Run Tests
 | 
			
		||||
```bash
 | 
			
		||||
# Run tests once (server has to run)
 | 
			
		||||
yarn test
 | 
			
		||||
 | 
			
		||||
# Run test in watch mode (reruns on change)
 | 
			
		||||
yarn test:watch
 | 
			
		||||
 | 
			
		||||
# Run test in ci mode (automaticly starts the dev server)
 | 
			
		||||
yarn 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
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Docker w/ postgres 🐳
 | 
			
		||||
## 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 [ethereal.email](https://ethereal.email) as the mailserver).
 | 
			
		||||
 | 
			
		||||
| 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.
 | 
			
		||||
| 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
 | 
			
		||||
| MAIL_SERVER | String | N/A | The smtp server's ip-address/fqdn
 | 
			
		||||
| MAIL_PORT | String | N/A | The smtp server's port
 | 
			
		||||
| MAIL_USER | String | N/A | The username for sending mails
 | 
			
		||||
| MAIL_PASSWORD | String | N/A | The user's password for sending mails
 | 
			
		||||
| MAIL_FROM | String | N/A | The from-address for sending mails
 | 
			
		||||
| 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)
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
docker-compose up --build
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Recommended Editor
 | 
			
		||||
 | 
			
		||||
@@ -42,22 +79,19 @@ docker-compose up --build
 | 
			
		||||
 | 
			
		||||
### Recommended Extensions
 | 
			
		||||
 | 
			
		||||
- will be automatically recommended via ./vscode/extensions.json
 | 
			
		||||
* will be automatically recommended via ./vscode/extensions.json
 | 
			
		||||
 | 
			
		||||
## Branches
 | 
			
		||||
- main: Protected "release" branch
 | 
			
		||||
- dev: Current dev branch for merging the different features - only push for merges or minor changes!
 | 
			
		||||
- feature/xyz: Feature branches - `feature/issueid-title`
 | 
			
		||||
- bugfix/xyz: Branches for bugfixes - `bugfix/issueid-title` (no id for readme changes needed)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## File Structure
 | 
			
		||||
 | 
			
		||||
- src/models/entities\* - database models (typeorm entities)
 | 
			
		||||
- src/models/actions\* - actions models
 | 
			
		||||
- src/models/responses\* - response models
 | 
			
		||||
- src/controllers/\* - routing-controllers
 | 
			
		||||
- src/loaders/\* - loaders for the different init steps of the api server
 | 
			
		||||
- src/middlewares/\* - express middlewares (mainly auth r/n)
 | 
			
		||||
- src/errors/* - our custom (http) errors
 | 
			
		||||
- src/routes/\* - express routes for everything we don't do via routing-controllers (depreciated)
 | 
			
		||||
## Staging
 | 
			
		||||
### Branches & Tags
 | 
			
		||||
* vX.Y.Z: Release tags created from the main branch
 | 
			
		||||
   * The version numbers follow the semver standard
 | 
			
		||||
   * 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
 | 
			
		||||
   * 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`
 | 
			
		||||
							
								
								
									
										166
									
								
								licenses.md
									
									
									
									
									
								
							
							
						
						
									
										166
									
								
								licenses.md
									
									
									
									
									
								
							@@ -1,3 +1,32 @@
 | 
			
		||||
# @odit/class-validator-jsonschema
 | 
			
		||||
**Author**: Aleksi Pekkala <aleksipekkala@gmail.com>
 | 
			
		||||
**Repo**: git@github.com:epiphone/class-validator-jsonschema.git
 | 
			
		||||
**License**: MIT
 | 
			
		||||
**Description**: Convert class-validator-decorated classes into JSON schema
 | 
			
		||||
## License Text
 | 
			
		||||
MIT License
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2017 Aleksi Pekkala
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
 
 | 
			
		||||
 | 
			
		||||
# argon2
 | 
			
		||||
**Author**: Ranieri Althoff <ranisalt+argon2@gmail.com>
 | 
			
		||||
**Repo**: [object Object]
 | 
			
		||||
@@ -88,22 +117,15 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
			
		||||
THE SOFTWARE. 
 | 
			
		||||
 | 
			
		||||
# class-validator
 | 
			
		||||
**Author**: [object Object]
 | 
			
		||||
**Author**: TypeStack contributors
 | 
			
		||||
**Repo**: [object Object]
 | 
			
		||||
**License**: MIT
 | 
			
		||||
**Description**: Class-based validation with Typescript / ES6 / ES5 using decorators or validation schemas. Supports both node.js and browser
 | 
			
		||||
**Description**: Decorator-based property validation for classes.
 | 
			
		||||
## License Text
 | 
			
		||||
 
 | 
			
		||||
 | 
			
		||||
# class-validator-jsonschema
 | 
			
		||||
**Author**: Aleksi Pekkala <aleksipekkala@gmail.com>
 | 
			
		||||
**Repo**: git@github.com:epiphone/class-validator-jsonschema.git
 | 
			
		||||
**License**: MIT
 | 
			
		||||
**Description**: Convert class-validator-decorated classes into JSON schema
 | 
			
		||||
## License Text
 | 
			
		||||
MIT License
 | 
			
		||||
The MIT License
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2017 Aleksi Pekkala
 | 
			
		||||
Copyright (c) 2015-2020 TypeStack
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
@@ -112,17 +134,16 @@ 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 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.
 | 
			
		||||
 
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
			
		||||
THE SOFTWARE. 
 | 
			
		||||
 | 
			
		||||
# consola
 | 
			
		||||
**Author**: undefined
 | 
			
		||||
@@ -332,6 +353,35 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
SOFTWARE.
 | 
			
		||||
 
 | 
			
		||||
 | 
			
		||||
# libphonenumber-js
 | 
			
		||||
**Author**: catamphetamine <purecatamphetamine@gmail.com>
 | 
			
		||||
**Repo**: [object Object]
 | 
			
		||||
**License**: MIT
 | 
			
		||||
**Description**: A simpler (and smaller) rewrite of Google Android's libphonenumber library in javascript
 | 
			
		||||
## License Text
 | 
			
		||||
(The MIT License)
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2016 @catamphetamine <purecatamphetamine@gmail.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. 
 | 
			
		||||
 | 
			
		||||
# mysql
 | 
			
		||||
**Author**: Felix Geisendörfer <felix@debuggable.com> (http://debuggable.com/)
 | 
			
		||||
**Repo**: mysqljs/mysql
 | 
			
		||||
@@ -340,6 +390,30 @@ SOFTWARE.
 | 
			
		||||
## License Text
 | 
			
		||||
 
 | 
			
		||||
 | 
			
		||||
# nodemailer
 | 
			
		||||
**Author**: Andris Reinman
 | 
			
		||||
**Repo**: [object Object]
 | 
			
		||||
**License**: MIT
 | 
			
		||||
**Description**: Easy as cake e-mail sending from your Node.js applications
 | 
			
		||||
## License Text
 | 
			
		||||
Copyright (c) 2011-2019 Andris Reinman
 | 
			
		||||
 | 
			
		||||
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 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
 | 
			
		||||
**Author**: Brian Carlson <brian.m.carlson@gmail.com>
 | 
			
		||||
**Repo**: [object Object]
 | 
			
		||||
@@ -594,7 +668,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 | 
			
		||||
**Author**: ODIT.Services
 | 
			
		||||
**Repo**: [object Object]
 | 
			
		||||
**License**: MIT
 | 
			
		||||
**Description**: A simple license crawler
 | 
			
		||||
**Description**: A simple license crawler for crediting open source work
 | 
			
		||||
## License Text
 | 
			
		||||
MIT License Copyright (c) 2020 ODIT.Services (info@odit.services)
 | 
			
		||||
 | 
			
		||||
@@ -791,6 +865,35 @@ OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 | 
			
		||||
    SOFTWARE
 | 
			
		||||
 
 | 
			
		||||
 | 
			
		||||
# @types/nodemailer
 | 
			
		||||
**Author**: undefined
 | 
			
		||||
**Repo**: [object Object]
 | 
			
		||||
**License**: MIT
 | 
			
		||||
**Description**: TypeScript definitions for Nodemailer
 | 
			
		||||
## License Text
 | 
			
		||||
    MIT License
 | 
			
		||||
 | 
			
		||||
    Copyright (c) Microsoft Corporation. All rights reserved.
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
 
 | 
			
		||||
 | 
			
		||||
# @types/uuid
 | 
			
		||||
**Author**: undefined
 | 
			
		||||
**Repo**: [object Object]
 | 
			
		||||
@@ -934,6 +1037,35 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
SOFTWARE.
 | 
			
		||||
 
 | 
			
		||||
 | 
			
		||||
# release-it
 | 
			
		||||
**Author**: [object Object]
 | 
			
		||||
**Repo**: [object Object]
 | 
			
		||||
**License**: MIT
 | 
			
		||||
**Description**: Generic CLI tool to automate versioning and package publishing related tasks.
 | 
			
		||||
## License Text
 | 
			
		||||
MIT License
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2018 Lars Kappert
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
 
 | 
			
		||||
 | 
			
		||||
# rimraf
 | 
			
		||||
**Author**: Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)
 | 
			
		||||
**Repo**: git://github.com/isaacs/rimraf.git
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										41
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								package.json
									
									
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "@odit/lfk-backend",
 | 
			
		||||
  "version": "0.0.11",
 | 
			
		||||
  "version": "0.4.4",
 | 
			
		||||
  "main": "src/app.ts",
 | 
			
		||||
  "repository": "https://git.odit.services/lfk/backend",
 | 
			
		||||
  "author": {
 | 
			
		||||
@@ -22,11 +22,11 @@
 | 
			
		||||
  ],
 | 
			
		||||
  "license": "CC-BY-NC-SA-4.0",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@odit/class-validator-jsonschema": "2.1.1",
 | 
			
		||||
    "argon2": "^0.27.1",
 | 
			
		||||
    "body-parser": "^1.19.0",
 | 
			
		||||
    "class-transformer": "^0.3.1",
 | 
			
		||||
    "class-validator": "^0.12.2",
 | 
			
		||||
    "class-validator-jsonschema": "^2.1.0",
 | 
			
		||||
    "class-transformer": "0.3.1",
 | 
			
		||||
    "class-validator": "^0.13.1",
 | 
			
		||||
    "consola": "^2.15.0",
 | 
			
		||||
    "cookie": "^0.4.1",
 | 
			
		||||
    "cookie-parser": "^1.4.5",
 | 
			
		||||
@@ -35,13 +35,15 @@
 | 
			
		||||
    "dotenv": "^8.2.0",
 | 
			
		||||
    "express": "^4.17.1",
 | 
			
		||||
    "jsonwebtoken": "^8.5.1",
 | 
			
		||||
    "libphonenumber-js": "^1.9.9",
 | 
			
		||||
    "mysql": "^2.18.1",
 | 
			
		||||
    "nodemailer": "^6.4.17",
 | 
			
		||||
    "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.29",
 | 
			
		||||
    "typeorm": "^0.2.30",
 | 
			
		||||
    "typeorm-routing-controllers-extensions": "^0.2.0",
 | 
			
		||||
    "typeorm-seeding": "^1.6.1",
 | 
			
		||||
    "uuid": "^8.3.2",
 | 
			
		||||
@@ -51,10 +53,11 @@
 | 
			
		||||
    "@odit/license-exporter": "^0.0.9",
 | 
			
		||||
    "@types/cors": "^2.8.9",
 | 
			
		||||
    "@types/csvtojson": "^1.1.5",
 | 
			
		||||
    "@types/express": "^4.17.9",
 | 
			
		||||
    "@types/jest": "^26.0.16",
 | 
			
		||||
    "@types/express": "^4.17.11",
 | 
			
		||||
    "@types/jest": "^26.0.20",
 | 
			
		||||
    "@types/jsonwebtoken": "^8.5.0",
 | 
			
		||||
    "@types/node": "^14.14.20",
 | 
			
		||||
    "@types/node": "^14.14.22",
 | 
			
		||||
    "@types/nodemailer": "^6.4.0",
 | 
			
		||||
    "@types/uuid": "^8.3.0",
 | 
			
		||||
    "axios": "^0.21.1",
 | 
			
		||||
    "cp-cli": "^2.0.0",
 | 
			
		||||
@@ -63,9 +66,9 @@
 | 
			
		||||
    "release-it": "^14.2.2",
 | 
			
		||||
    "rimraf": "^3.0.2",
 | 
			
		||||
    "start-server-and-test": "^1.11.7",
 | 
			
		||||
    "ts-jest": "^26.4.4",
 | 
			
		||||
    "ts-jest": "^26.5.0",
 | 
			
		||||
    "ts-node": "^9.1.1",
 | 
			
		||||
    "typedoc": "^0.20.14",
 | 
			
		||||
    "typedoc": "^0.20.19",
 | 
			
		||||
    "typescript": "^4.1.3"
 | 
			
		||||
  },
 | 
			
		||||
  "scripts": {
 | 
			
		||||
@@ -74,20 +77,22 @@
 | 
			
		||||
    "docs": "typedoc --out docs src",
 | 
			
		||||
    "test": "jest",
 | 
			
		||||
    "test:watch": "jest --watchAll",
 | 
			
		||||
    "test:ci": "start-server-and-test dev http://localhost:4010/api/docs/openapi.json test",
 | 
			
		||||
    "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 --md",
 | 
			
		||||
    "release": "release-it"
 | 
			
		||||
    "licenses:export": "license-exporter --markdown",
 | 
			
		||||
    "release": "release-it --only-version"
 | 
			
		||||
  },
 | 
			
		||||
  "release-it": {
 | 
			
		||||
    "git": {
 | 
			
		||||
      "commit": true,
 | 
			
		||||
      "requireCleanWorkingDir": false,
 | 
			
		||||
      "requireBranch": "main",
 | 
			
		||||
      "commitMessage": "🚀Bumped version to v${version}",
 | 
			
		||||
      "requireBranch": "dev",
 | 
			
		||||
      "push": false,
 | 
			
		||||
      "tag": true,
 | 
			
		||||
      "tagName": "v${version}",
 | 
			
		||||
      "tagAnnotation": "v${version}"
 | 
			
		||||
      "tag": false
 | 
			
		||||
    },
 | 
			
		||||
    "npm": {
 | 
			
		||||
      "publish": false
 | 
			
		||||
@@ -99,4 +104,4 @@
 | 
			
		||||
      "docs/*"
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										37
									
								
								scripts/create_testenv.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								scripts/create_testenv.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
import consola from "consola";
 | 
			
		||||
import fs from "fs";
 | 
			
		||||
import nodemailer from "nodemailer";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
nodemailer.createTestAccount((err, account) => {
 | 
			
		||||
    if (err) {
 | 
			
		||||
        console.error('Failed to create a testing account. ' + err.message);
 | 
			
		||||
        return process.exit(1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const env = `
 | 
			
		||||
APP_PORT=4010
 | 
			
		||||
DB_TYPE=sqlite
 | 
			
		||||
DB_HOST=bla
 | 
			
		||||
DB_PORT=bla
 | 
			
		||||
DB_USER=bla
 | 
			
		||||
DB_PASSWORD=bla
 | 
			
		||||
DB_NAME=./test.sqlite
 | 
			
		||||
NODE_ENV=dev
 | 
			
		||||
POSTALCODE_COUNTRYCODE=DE
 | 
			
		||||
SEED_TEST_DATA=true
 | 
			
		||||
MAIL_SERVER=${account.smtp.host}
 | 
			
		||||
MAIL_PORT=${account.smtp.port}
 | 
			
		||||
MAIL_USER=${account.user}
 | 
			
		||||
MAIL_PASSWORD=${account.pass}
 | 
			
		||||
MAIL_FROM=${account.user}`
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
        fs.writeFileSync("./.env", env, { encoding: "utf-8" });
 | 
			
		||||
        consola.success("Exported ci env to .env");
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        consola.error("Couldn't export the ci env");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { validationMetadatasToSchemas } from 'class-validator-jsonschema';
 | 
			
		||||
import { validationMetadatasToSchemas } from '@odit/class-validator-jsonschema';
 | 
			
		||||
import consola from "consola";
 | 
			
		||||
import fs from "fs";
 | 
			
		||||
import "reflect-metadata";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import { MetadataArgsStorage } from 'routing-controllers';
 | 
			
		||||
import { routingControllersToSpec } from 'routing-controllers-openapi';
 | 
			
		||||
import { config } from './config';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This function generates a the openapi spec from route metadata and type schemas.
 | 
			
		||||
@@ -41,9 +42,9 @@ export function generateSpec(storage: MetadataArgsStorage, schemas) {
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            info: {
 | 
			
		||||
                description: "The the backend API for the LfK! runner system.",
 | 
			
		||||
                description: `The the backend API for the LfK! runner system. <br>[Imprint](${config.imprint_url}) & [Privacy](${config.privacy_url})`,
 | 
			
		||||
                title: "LfK! Backend API",
 | 
			
		||||
                version: process.env.npm_package_version
 | 
			
		||||
                version: config.version
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -5,10 +5,12 @@ import { config, e as errors } from './config';
 | 
			
		||||
import loaders from "./loaders/index";
 | 
			
		||||
import authchecker from "./middlewares/authchecker";
 | 
			
		||||
import { ErrorHandler } from './middlewares/ErrorHandler';
 | 
			
		||||
import UserChecker from './middlewares/UserChecker';
 | 
			
		||||
 | 
			
		||||
const CONTROLLERS_FILE_EXTENSION = process.env.NODE_ENV === 'production' ? 'js' : 'ts';
 | 
			
		||||
const app = createExpressServer({
 | 
			
		||||
  authorizationChecker: authchecker,
 | 
			
		||||
  currentUserChecker: UserChecker,
 | 
			
		||||
  middlewares: [ErrorHandler],
 | 
			
		||||
  development: config.development,
 | 
			
		||||
  cors: true,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
import { config as configDotenv } from 'dotenv';
 | 
			
		||||
import { CountryCode } from 'libphonenumber-js';
 | 
			
		||||
import ValidatorJS from 'validator';
 | 
			
		||||
 | 
			
		||||
configDotenv();
 | 
			
		||||
@@ -6,22 +7,29 @@ export const config = {
 | 
			
		||||
    internal_port: parseInt(process.env.APP_PORT) || 4010,
 | 
			
		||||
    development: process.env.NODE_ENV === "production",
 | 
			
		||||
    jwt_secret: process.env.JWT_SECRET || "secretjwtsecret",
 | 
			
		||||
    phone_validation_countrycode: process.env.PHONE_COUNTRYCODE || "ZZ",
 | 
			
		||||
    postalcode_validation_countrycode: getPostalCodeLocale()
 | 
			
		||||
    phone_validation_countrycode: getPhoneCodeLocale(),
 | 
			
		||||
    postalcode_validation_countrycode: getPostalCodeLocale(),
 | 
			
		||||
    version: process.env.VERSION || require('../package.json').version,
 | 
			
		||||
    seedTestData: getDataSeeding(),
 | 
			
		||||
    app_url: process.env.APP_URL || "http://localhost:8080",
 | 
			
		||||
    mail_server: process.env.MAIL_SERVER,
 | 
			
		||||
    mail_port: Number(process.env.MAIL_PORT) || 25,
 | 
			
		||||
    mail_user: process.env.MAIL_USER,
 | 
			
		||||
    mail_password: process.env.MAIL_PASSWORD,
 | 
			
		||||
    mail_from: process.env.MAIL_FROM,
 | 
			
		||||
    privacy_url: process.env.PRIVACY_URL || "/privacy",
 | 
			
		||||
    imprint_url: process.env.IMPRINT_URL || "/imprint"
 | 
			
		||||
}
 | 
			
		||||
let errors = 0
 | 
			
		||||
if (typeof config.internal_port !== "number") {
 | 
			
		||||
    errors++
 | 
			
		||||
}
 | 
			
		||||
if (typeof config.phone_validation_countrycode !== "string") {
 | 
			
		||||
    errors++
 | 
			
		||||
}
 | 
			
		||||
if (config.phone_validation_countrycode.length !== 2) {
 | 
			
		||||
    errors++
 | 
			
		||||
}
 | 
			
		||||
if (typeof config.development !== "boolean") {
 | 
			
		||||
    errors++
 | 
			
		||||
}
 | 
			
		||||
function getPhoneCodeLocale(): CountryCode {
 | 
			
		||||
    return (process.env.PHONE_COUNTRYCODE as CountryCode);
 | 
			
		||||
}
 | 
			
		||||
function getPostalCodeLocale(): any {
 | 
			
		||||
    try {
 | 
			
		||||
        const stringArray: String[] = ValidatorJS.isPostalCodeLocales;
 | 
			
		||||
@@ -31,4 +39,11 @@ function getPostalCodeLocale(): any {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
function getDataSeeding(): Boolean {
 | 
			
		||||
    try {
 | 
			
		||||
        return JSON.parse(process.env.SEED_TEST_DATA);
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
export let e = errors
 | 
			
		||||
@@ -1,103 +1,110 @@
 | 
			
		||||
import { Body, CookieParam, JsonController, Param, Post, Req, Res } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { IllegalJWTError, InvalidCredentialsError, JwtNotProvidedError, PasswordNeededError, RefreshTokenCountInvalidError, UsernameOrEmailNeededError } from '../errors/AuthError';
 | 
			
		||||
import { UserNotFoundError } from '../errors/UserErrors';
 | 
			
		||||
import { CreateAuth } from '../models/actions/create/CreateAuth';
 | 
			
		||||
import { CreateResetToken } from '../models/actions/create/CreateResetToken';
 | 
			
		||||
import { HandleLogout } from '../models/actions/HandleLogout';
 | 
			
		||||
import { RefreshAuth } from '../models/actions/RefreshAuth';
 | 
			
		||||
import { ResetPassword } from '../models/actions/ResetPassword';
 | 
			
		||||
import { ResponseAuth } from '../models/responses/ResponseAuth';
 | 
			
		||||
import { Logout } from '../models/responses/ResponseLogout';
 | 
			
		||||
 | 
			
		||||
@JsonController('/auth')
 | 
			
		||||
export class AuthController {
 | 
			
		||||
	constructor() {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post("/login")
 | 
			
		||||
	@ResponseSchema(ResponseAuth)
 | 
			
		||||
	@ResponseSchema(InvalidCredentialsError)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError)
 | 
			
		||||
	@ResponseSchema(UsernameOrEmailNeededError)
 | 
			
		||||
	@ResponseSchema(PasswordNeededError)
 | 
			
		||||
	@ResponseSchema(InvalidCredentialsError)
 | 
			
		||||
	@OpenAPI({ description: 'Login with your username/email and password. <br> You will receive: \n * access token (use it as a bearer token) \n * refresh token (will also be sent as a cookie)' })
 | 
			
		||||
	async login(@Body({ validate: true }) createAuth: CreateAuth, @Res() response: any) {
 | 
			
		||||
		let auth;
 | 
			
		||||
		try {
 | 
			
		||||
			auth = await createAuth.toAuth();
 | 
			
		||||
			response.cookie('lfk_backend__refresh_token', auth.refresh_token, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
 | 
			
		||||
			response.cookie('lfk_backend__refresh_token_expires_at', auth.refresh_token_expires_at, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
 | 
			
		||||
			return response.send(auth)
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post("/logout")
 | 
			
		||||
	@ResponseSchema(Logout)
 | 
			
		||||
	@ResponseSchema(InvalidCredentialsError)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError)
 | 
			
		||||
	@ResponseSchema(UsernameOrEmailNeededError)
 | 
			
		||||
	@ResponseSchema(PasswordNeededError)
 | 
			
		||||
	@ResponseSchema(InvalidCredentialsError)
 | 
			
		||||
	@OpenAPI({ description: 'Logout using your refresh token. <br> This instantly invalidates all your access and refresh tokens.', security: [{ "RefreshTokenCookie": [] }] })
 | 
			
		||||
	async logout(@Body({ validate: true }) handleLogout: HandleLogout, @CookieParam("lfk_backend__refresh_token") refresh_token: string, @Res() response: any) {
 | 
			
		||||
		if (refresh_token && refresh_token.length != 0 && handleLogout.token == undefined) {
 | 
			
		||||
			handleLogout.token = refresh_token;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		let logout;
 | 
			
		||||
		try {
 | 
			
		||||
			logout = await handleLogout.logout()
 | 
			
		||||
			await response.cookie('lfk_backend__refresh_token', "expired", { expires: new Date(Date.now()), httpOnly: true });
 | 
			
		||||
			response.cookie('lfk_backend__refresh_token_expires_at', "expired", { expires: new Date(Date.now()), httpOnly: true });
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
		return response.send(logout)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post("/refresh")
 | 
			
		||||
	@ResponseSchema(ResponseAuth)
 | 
			
		||||
	@ResponseSchema(JwtNotProvidedError)
 | 
			
		||||
	@ResponseSchema(IllegalJWTError)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError)
 | 
			
		||||
	@ResponseSchema(RefreshTokenCountInvalidError)
 | 
			
		||||
	@OpenAPI({ description: 'Refresh your access and refresh tokens using a valid refresh token. <br> You will receive: \n * access token (use it as a bearer token) \n * refresh token (will also be sent as a cookie)', security: [{ "RefreshTokenCookie": [] }] })
 | 
			
		||||
	async refresh(@Body({ validate: true }) refreshAuth: RefreshAuth, @CookieParam("lfk_backend__refresh_token") refresh_token: string, @Res() response: any, @Req() req: any) {
 | 
			
		||||
		if (refresh_token && refresh_token.length != 0 && refreshAuth.token == undefined) {
 | 
			
		||||
			refreshAuth.token = refresh_token;
 | 
			
		||||
		}
 | 
			
		||||
		let auth;
 | 
			
		||||
		try {
 | 
			
		||||
			auth = await refreshAuth.toAuth();
 | 
			
		||||
			response.cookie('lfk_backend__refresh_token', auth.refresh_token, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
 | 
			
		||||
			response.cookie('lfk_backend__refresh_token_expires_at', auth.refresh_token_expires_at, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
		return response.send(auth)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post("/reset")
 | 
			
		||||
	@ResponseSchema(ResponseAuth)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError)
 | 
			
		||||
	@ResponseSchema(UsernameOrEmailNeededError)
 | 
			
		||||
	@OpenAPI({ description: "Request a password reset token. <br> This will provide you with a reset token that you can use by posting to /api/auth/reset/{token}." })
 | 
			
		||||
	async getResetToken(@Body({ validate: true }) passwordReset: CreateResetToken) {
 | 
			
		||||
		//This really shouldn't just get returned, but sent via mail or sth like that. But for dev only this is fine.
 | 
			
		||||
		return { "resetToken": await passwordReset.toResetToken() };
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post("/reset/:token")
 | 
			
		||||
	@ResponseSchema(ResponseAuth)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError)
 | 
			
		||||
	@ResponseSchema(UsernameOrEmailNeededError)
 | 
			
		||||
	@OpenAPI({ description: "Reset a user's utilising a valid password reset token. <br> This will set the user's password to the one you provided in the body. <br> To get a reset token post to /api/auth/reset with your username." })
 | 
			
		||||
	async resetPassword(@Param("token") token: string, @Body({ validate: true }) passwordReset: ResetPassword) {
 | 
			
		||||
		passwordReset.resetToken = token;
 | 
			
		||||
		return await passwordReset.resetPassword();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
import { Body, CookieParam, JsonController, Param, Post, Req, Res } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { IllegalJWTError, InvalidCredentialsError, JwtNotProvidedError, PasswordNeededError, RefreshTokenCountInvalidError, UsernameOrEmailNeededError } from '../errors/AuthError';
 | 
			
		||||
import { UserNotFoundError } from '../errors/UserErrors';
 | 
			
		||||
import { Mailer } from '../mailer';
 | 
			
		||||
import { CreateAuth } from '../models/actions/create/CreateAuth';
 | 
			
		||||
import { CreateResetToken } from '../models/actions/create/CreateResetToken';
 | 
			
		||||
import { HandleLogout } from '../models/actions/HandleLogout';
 | 
			
		||||
import { RefreshAuth } from '../models/actions/RefreshAuth';
 | 
			
		||||
import { ResetPassword } from '../models/actions/ResetPassword';
 | 
			
		||||
import { ResponseAuth } from '../models/responses/ResponseAuth';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
import { Logout } from '../models/responses/ResponseLogout';
 | 
			
		||||
 | 
			
		||||
@JsonController('/auth')
 | 
			
		||||
export class AuthController {
 | 
			
		||||
 | 
			
		||||
	private mailer: Mailer;
 | 
			
		||||
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.mailer = new Mailer();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post("/login")
 | 
			
		||||
	@ResponseSchema(ResponseAuth)
 | 
			
		||||
	@ResponseSchema(InvalidCredentialsError)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError)
 | 
			
		||||
	@ResponseSchema(UsernameOrEmailNeededError)
 | 
			
		||||
	@ResponseSchema(PasswordNeededError)
 | 
			
		||||
	@ResponseSchema(InvalidCredentialsError)
 | 
			
		||||
	@OpenAPI({ description: 'Login with your username/email and password. <br> You will receive: \n * access token (use it as a bearer token) \n * refresh token (will also be sent as a cookie)' })
 | 
			
		||||
	async login(@Body({ validate: true }) createAuth: CreateAuth, @Res() response: any) {
 | 
			
		||||
		let auth;
 | 
			
		||||
		try {
 | 
			
		||||
			auth = await createAuth.toAuth();
 | 
			
		||||
			response.cookie('lfk_backend__refresh_token', auth.refresh_token, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
 | 
			
		||||
			response.cookie('lfk_backend__refresh_token_expires_at', auth.refresh_token_expires_at, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
 | 
			
		||||
			return response.send(auth)
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post("/logout")
 | 
			
		||||
	@ResponseSchema(Logout)
 | 
			
		||||
	@ResponseSchema(InvalidCredentialsError)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError)
 | 
			
		||||
	@ResponseSchema(UsernameOrEmailNeededError)
 | 
			
		||||
	@ResponseSchema(PasswordNeededError)
 | 
			
		||||
	@ResponseSchema(InvalidCredentialsError)
 | 
			
		||||
	@OpenAPI({ description: 'Logout using your refresh token. <br> This instantly invalidates all your access and refresh tokens.', security: [{ "RefreshTokenCookie": [] }] })
 | 
			
		||||
	async logout(@Body({ validate: true }) handleLogout: HandleLogout, @CookieParam("lfk_backend__refresh_token") refresh_token: string, @Res() response: any) {
 | 
			
		||||
		if (refresh_token && refresh_token.length != 0 && handleLogout.token == undefined) {
 | 
			
		||||
			handleLogout.token = refresh_token;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		let logout;
 | 
			
		||||
		try {
 | 
			
		||||
			logout = await handleLogout.logout()
 | 
			
		||||
			await response.cookie('lfk_backend__refresh_token', "expired", { expires: new Date(Date.now()), httpOnly: true });
 | 
			
		||||
			response.cookie('lfk_backend__refresh_token_expires_at', "expired", { expires: new Date(Date.now()), httpOnly: true });
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
		return response.send(logout)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post("/refresh")
 | 
			
		||||
	@ResponseSchema(ResponseAuth)
 | 
			
		||||
	@ResponseSchema(JwtNotProvidedError)
 | 
			
		||||
	@ResponseSchema(IllegalJWTError)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError)
 | 
			
		||||
	@ResponseSchema(RefreshTokenCountInvalidError)
 | 
			
		||||
	@OpenAPI({ description: 'Refresh your access and refresh tokens using a valid refresh token. <br> You will receive: \n * access token (use it as a bearer token) \n * refresh token (will also be sent as a cookie)', security: [{ "RefreshTokenCookie": [] }] })
 | 
			
		||||
	async refresh(@Body({ validate: true }) refreshAuth: RefreshAuth, @CookieParam("lfk_backend__refresh_token") refresh_token: string, @Res() response: any, @Req() req: any) {
 | 
			
		||||
		if (refresh_token && refresh_token.length != 0 && refreshAuth.token == undefined) {
 | 
			
		||||
			refreshAuth.token = refresh_token;
 | 
			
		||||
		}
 | 
			
		||||
		let auth;
 | 
			
		||||
		try {
 | 
			
		||||
			auth = await refreshAuth.toAuth();
 | 
			
		||||
			response.cookie('lfk_backend__refresh_token', auth.refresh_token, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
 | 
			
		||||
			response.cookie('lfk_backend__refresh_token_expires_at', auth.refresh_token_expires_at, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
		return response.send(auth)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post("/reset")
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 200 })
 | 
			
		||||
	@ResponseSchema(UserNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(UsernameOrEmailNeededError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Request a password reset token. <br> This will provide you with a reset token that you can use by posting to /api/auth/reset/{token}." })
 | 
			
		||||
	async getResetToken(@Body({ validate: true }) passwordReset: CreateResetToken) {
 | 
			
		||||
		const reset_token: string = await passwordReset.toResetToken();
 | 
			
		||||
		await this.mailer.sendResetMail(passwordReset.email, reset_token);
 | 
			
		||||
		return new ResponseEmpty();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post("/reset/:token")
 | 
			
		||||
	@ResponseSchema(ResponseAuth)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError)
 | 
			
		||||
	@ResponseSchema(UsernameOrEmailNeededError)
 | 
			
		||||
	@OpenAPI({ description: "Reset a user's utilising a valid password reset token. <br> This will set the user's password to the one you provided in the body. <br> To get a reset token post to /api/auth/reset with your username." })
 | 
			
		||||
	async resetPassword(@Param("token") token: string, @Body({ validate: true }) passwordReset: ResetPassword) {
 | 
			
		||||
		passwordReset.resetToken = token;
 | 
			
		||||
		return await passwordReset.resetPassword();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										145
									
								
								src/controllers/DonationController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								src/controllers/DonationController.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,145 @@
 | 
			
		||||
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 { DonationIdsNotMatchingError, DonationNotFoundError } from '../errors/DonationErrors';
 | 
			
		||||
import { DonorNotFoundError } from '../errors/DonorErrors';
 | 
			
		||||
import { RunnerNotFoundError } from '../errors/RunnerErrors';
 | 
			
		||||
import { CreateDistanceDonation } from '../models/actions/create/CreateDistanceDonation';
 | 
			
		||||
import { CreateFixedDonation } from '../models/actions/create/CreateFixedDonation';
 | 
			
		||||
import { UpdateDistanceDonation } from '../models/actions/update/UpdateDistanceDonation';
 | 
			
		||||
import { UpdateFixedDonation } from '../models/actions/update/UpdateFixedDonation';
 | 
			
		||||
import { DistanceDonation } from '../models/entities/DistanceDonation';
 | 
			
		||||
import { Donation } from '../models/entities/Donation';
 | 
			
		||||
import { FixedDonation } from '../models/entities/FixedDonation';
 | 
			
		||||
import { ResponseDistanceDonation } from '../models/responses/ResponseDistanceDonation';
 | 
			
		||||
import { ResponseDonation } from '../models/responses/ResponseDonation';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
 | 
			
		||||
@JsonController('/donations')
 | 
			
		||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
 | 
			
		||||
export class DonationController {
 | 
			
		||||
	private donationRepository: Repository<Donation>;
 | 
			
		||||
	private distanceDonationRepository: Repository<DistanceDonation>;
 | 
			
		||||
	private fixedDonationRepository: Repository<FixedDonation>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets the repository of this controller's model/entity.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.donationRepository = getConnectionManager().get().getRepository(Donation);
 | 
			
		||||
		this.distanceDonationRepository = getConnectionManager().get().getRepository(DistanceDonation);
 | 
			
		||||
		this.fixedDonationRepository = getConnectionManager().get().getRepository(FixedDonation);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get()
 | 
			
		||||
	@Authorized("DONATION:GET")
 | 
			
		||||
	@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() {
 | 
			
		||||
		let responseDonations: ResponseDonation[] = new Array<ResponseDonation>();
 | 
			
		||||
		const donations = await this.donationRepository.find({ relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] });
 | 
			
		||||
		donations.forEach(donation => {
 | 
			
		||||
			responseDonations.push(donation.toResponse());
 | 
			
		||||
		});
 | 
			
		||||
		return responseDonations;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id')
 | 
			
		||||
	@Authorized("DONATION:GET")
 | 
			
		||||
	@ResponseSchema(ResponseDonation)
 | 
			
		||||
	@ResponseSchema(ResponseDistanceDonation)
 | 
			
		||||
	@ResponseSchema(DonationNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(DonationNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Lists all information about the donation whose id got provided. This includes the donation\'s runner\'s distance ran (if distance donation).' })
 | 
			
		||||
	async getOne(@Param('id') id: number) {
 | 
			
		||||
		let donation = await this.donationRepository.findOne({ id: id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] })
 | 
			
		||||
		if (!donation) { throw new DonationNotFoundError(); }
 | 
			
		||||
		return donation.toResponse();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post('/fixed')
 | 
			
		||||
	@Authorized("DONATION:CREATE")
 | 
			
		||||
	@ResponseSchema(ResponseDonation)
 | 
			
		||||
	@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OpenAPI({ description: 'Create a fixed donation (not distance donation - use /donations/distance instead). <br> Please rmemember to provide the donation\'s donors\'s id and amount.' })
 | 
			
		||||
	async postFixed(@Body({ validate: true }) createDonation: CreateFixedDonation) {
 | 
			
		||||
		let donation = await createDonation.toEntity();
 | 
			
		||||
		donation = await this.fixedDonationRepository.save(donation);
 | 
			
		||||
		return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['donor'] })).toResponse();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post('/distance')
 | 
			
		||||
	@Authorized("DONATION:CREATE")
 | 
			
		||||
	@ResponseSchema(ResponseDistanceDonation)
 | 
			
		||||
	@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OpenAPI({ description: 'Create a distance donation (not fixed donation - use /donations/fixed instead). <br> Please rmemember to provide the donation\'s donors\'s and runner\s ids and amount per distance (kilometer).' })
 | 
			
		||||
	async postDistance(@Body({ validate: true }) createDonation: CreateDistanceDonation) {
 | 
			
		||||
		let donation = await createDonation.toEntity();
 | 
			
		||||
		donation = await this.distanceDonationRepository.save(donation);
 | 
			
		||||
		return (await this.distanceDonationRepository.findOne({ id: donation.id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] })).toResponse();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/fixed/:id')
 | 
			
		||||
	@Authorized("DONATION:UPDATE")
 | 
			
		||||
	@ResponseSchema(ResponseDonation)
 | 
			
		||||
	@ResponseSchema(DonationNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(DonationIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Update the fixed donation (not distance donation - use /donations/distance instead) whose id you provided. <br> Please remember that ids can't be changed and amounts must be positive." })
 | 
			
		||||
	async putFixed(@Param('id') id: number, @Body({ validate: true }) donation: UpdateFixedDonation) {
 | 
			
		||||
		let oldDonation = await this.fixedDonationRepository.findOne({ id: id });
 | 
			
		||||
 | 
			
		||||
		if (!oldDonation) {
 | 
			
		||||
			throw new DonationNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (oldDonation.id != donation.id) {
 | 
			
		||||
			throw new DonationIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.fixedDonationRepository.save(await donation.update(oldDonation));
 | 
			
		||||
		return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['donor'] })).toResponse();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/distance/:id')
 | 
			
		||||
	@Authorized("DONATION:UPDATE")
 | 
			
		||||
	@ResponseSchema(ResponseDonation)
 | 
			
		||||
	@ResponseSchema(DonationNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(DonationIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Update the distance donation (not fixed donation - use /donations/fixed instead) whose id you provided. <br> Please remember that ids can't be changed and amountPerDistance must be positive." })
 | 
			
		||||
	async putDistance(@Param('id') id: number, @Body({ validate: true }) donation: UpdateDistanceDonation) {
 | 
			
		||||
		let oldDonation = await this.distanceDonationRepository.findOne({ id: id });
 | 
			
		||||
 | 
			
		||||
		if (!oldDonation) {
 | 
			
		||||
			throw new DonationNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (oldDonation.id != donation.id) {
 | 
			
		||||
			throw new DonationIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.distanceDonationRepository.save(await donation.update(oldDonation));
 | 
			
		||||
		return (await this.distanceDonationRepository.findOne({ id: donation.id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] })).toResponse();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@Authorized("DONATION:DELETE")
 | 
			
		||||
	@ResponseSchema(ResponseDonation)
 | 
			
		||||
	@ResponseSchema(ResponseDistanceDonation)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@OpenAPI({ description: 'Delete the donation whose id you provided. <br> If no donation with this id exists it will just return 204(no content).' })
 | 
			
		||||
	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
 | 
			
		||||
		let donation = await this.donationRepository.findOne({ id: id });
 | 
			
		||||
		if (!donation) { return null; }
 | 
			
		||||
		const responseScan = await this.donationRepository.findOne({ id: donation.id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] });
 | 
			
		||||
 | 
			
		||||
		await this.donationRepository.delete(donation);
 | 
			
		||||
		return responseScan.toResponse();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +1,13 @@
 | 
			
		||||
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 { DonorIdsNotMatchingError, DonorNotFoundError } from '../errors/DonorErrors';
 | 
			
		||||
import { DonorHasDonationsError, DonorIdsNotMatchingError, DonorNotFoundError } from '../errors/DonorErrors';
 | 
			
		||||
import { CreateDonor } from '../models/actions/create/CreateDonor';
 | 
			
		||||
import { UpdateDonor } from '../models/actions/update/UpdateDonor';
 | 
			
		||||
import { Donor } from '../models/entities/Donor';
 | 
			
		||||
import { ResponseDonor } from '../models/responses/ResponseDonor';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
import { DonationController } from './DonationController';
 | 
			
		||||
 | 
			
		||||
@JsonController('/donors')
 | 
			
		||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
 | 
			
		||||
@@ -23,10 +24,10 @@ export class DonorController {
 | 
			
		||||
	@Get()
 | 
			
		||||
	@Authorized("DONOR:GET")
 | 
			
		||||
	@ResponseSchema(ResponseDonor, { isArray: true })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all runners from all teams/orgs. <br> This includes the runner\'s group and distance ran.' })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all donor. <br> This includes the donor\'s current donation amount.' })
 | 
			
		||||
	async getAll() {
 | 
			
		||||
		let responseDonors: ResponseDonor[] = new Array<ResponseDonor>();
 | 
			
		||||
		const donors = await this.donorRepository.find();
 | 
			
		||||
		const donors = await this.donorRepository.find({ relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] });
 | 
			
		||||
		donors.forEach(donor => {
 | 
			
		||||
			responseDonors.push(new ResponseDonor(donor));
 | 
			
		||||
		});
 | 
			
		||||
@@ -38,9 +39,9 @@ export class DonorController {
 | 
			
		||||
	@ResponseSchema(ResponseDonor)
 | 
			
		||||
	@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(DonorNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Lists all information about the runner whose id got provided.' })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all information about the donor whose id got provided. <br> This includes the donor\'s current donation amount.' })
 | 
			
		||||
	async getOne(@Param('id') id: number) {
 | 
			
		||||
		let donor = await this.donorRepository.findOne({ id: id })
 | 
			
		||||
		let donor = await this.donorRepository.findOne({ id: id }, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] })
 | 
			
		||||
		if (!donor) { throw new DonorNotFoundError(); }
 | 
			
		||||
		return new ResponseDonor(donor);
 | 
			
		||||
	}
 | 
			
		||||
@@ -48,7 +49,7 @@ export class DonorController {
 | 
			
		||||
	@Post()
 | 
			
		||||
	@Authorized("DONOR:CREATE")
 | 
			
		||||
	@ResponseSchema(ResponseDonor)
 | 
			
		||||
	@OpenAPI({ description: 'Create a new runner. <br> Please remeber to provide the runner\'s group\'s id.' })
 | 
			
		||||
	@OpenAPI({ description: 'Create a new donor.' })
 | 
			
		||||
	async post(@Body({ validate: true }) createRunner: CreateDonor) {
 | 
			
		||||
		let donor;
 | 
			
		||||
		try {
 | 
			
		||||
@@ -58,7 +59,7 @@ export class DonorController {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		donor = await this.donorRepository.save(donor)
 | 
			
		||||
		return new ResponseDonor(await this.donorRepository.findOne(donor));
 | 
			
		||||
		return new ResponseDonor(await this.donorRepository.findOne(donor, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] }));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
@@ -66,7 +67,7 @@ export class DonorController {
 | 
			
		||||
	@ResponseSchema(ResponseDonor)
 | 
			
		||||
	@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(DonorIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Update the runner whose id you provided. <br> Please remember that ids can't be changed." })
 | 
			
		||||
	@OpenAPI({ description: "Update the donor whose id you provided. <br> Please remember that ids can't be changed." })
 | 
			
		||||
	async put(@Param('id') id: number, @Body({ validate: true }) donor: UpdateDonor) {
 | 
			
		||||
		let oldDonor = await this.donorRepository.findOne({ id: id });
 | 
			
		||||
 | 
			
		||||
@@ -79,7 +80,7 @@ export class DonorController {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.donorRepository.save(await donor.update(oldDonor));
 | 
			
		||||
		return new ResponseDonor(await this.donorRepository.findOne({ id: id }));
 | 
			
		||||
		return new ResponseDonor(await this.donorRepository.findOne({ id: id }, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] }));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
@@ -87,17 +88,24 @@ export class DonorController {
 | 
			
		||||
	@ResponseSchema(ResponseDonor)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@OpenAPI({ description: 'Delete the runner whose id you provided. <br> If no runner with this id exists it will just return 204(no content).' })
 | 
			
		||||
	@OpenAPI({ description: 'Delete the donor whose id you provided. <br> If no donor with this id exists it will just return 204(no content). <br> If the donor still has donations associated this will fail, please provide the query param ?force=true to delete the donor with all associated donations.' })
 | 
			
		||||
	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
 | 
			
		||||
		let donor = await this.donorRepository.findOne({ id: id });
 | 
			
		||||
		if (!donor) { return null; }
 | 
			
		||||
		const responseDonor = await this.donorRepository.findOne(donor);
 | 
			
		||||
		const responseDonor = await this.donorRepository.findOne(donor, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] });
 | 
			
		||||
 | 
			
		||||
		if (!donor) {
 | 
			
		||||
			throw new DonorNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//TODO: DELETE DONATIONS AND WARN FOR FORCE (https://git.odit.services/lfk/backend/issues/66)
 | 
			
		||||
		const donorDonations = (await this.donorRepository.findOne({ id: donor.id }, { relations: ["donations"] })).donations;
 | 
			
		||||
		if (donorDonations.length > 0 && !force) {
 | 
			
		||||
			throw new DonorHasDonationsError();
 | 
			
		||||
		}
 | 
			
		||||
		const donationController = new DonationController();
 | 
			
		||||
		for (let donation of donorDonations) {
 | 
			
		||||
			await donationController.remove(donation.id, force);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.donorRepository.delete(donor);
 | 
			
		||||
		return new ResponseDonor(responseDonor);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										107
									
								
								src/controllers/GroupContactController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/controllers/GroupContactController.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
			
		||||
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 { GroupContactIdsNotMatchingError, GroupContactNotFoundError } from '../errors/GroupContactErrors';
 | 
			
		||||
import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors';
 | 
			
		||||
import { CreateGroupContact } from '../models/actions/create/CreateGroupContact';
 | 
			
		||||
import { UpdateGroupContact } from '../models/actions/update/UpdateGroupContact';
 | 
			
		||||
import { GroupContact } from '../models/entities/GroupContact';
 | 
			
		||||
import { RunnerGroup } from '../models/entities/RunnerGroup';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
import { ResponseGroupContact } from '../models/responses/ResponseGroupContact';
 | 
			
		||||
 | 
			
		||||
@JsonController('/contacts')
 | 
			
		||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
 | 
			
		||||
export class GroupContactController {
 | 
			
		||||
	private contactRepository: Repository<GroupContact>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets the repository of this controller's model/entity.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.contactRepository = getConnectionManager().get().getRepository(GroupContact);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get()
 | 
			
		||||
	@Authorized("CONTACT:GET")
 | 
			
		||||
	@ResponseSchema(ResponseGroupContact, { isArray: true })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all contacts. <br> This includes the contact\'s associated groups.' })
 | 
			
		||||
	async getAll() {
 | 
			
		||||
		let responseContacts: ResponseGroupContact[] = new Array<ResponseGroupContact>();
 | 
			
		||||
		const contacts = await this.contactRepository.find({ relations: ['groups', 'groups.parentGroup'] });
 | 
			
		||||
		contacts.forEach(contact => {
 | 
			
		||||
			responseContacts.push(contact.toResponse());
 | 
			
		||||
		});
 | 
			
		||||
		return responseContacts;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id')
 | 
			
		||||
	@Authorized("CONTACT:GET")
 | 
			
		||||
	@ResponseSchema(ResponseGroupContact)
 | 
			
		||||
	@ResponseSchema(GroupContactNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(GroupContactNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Lists all information about the contact whose id got provided. <br> This includes the contact\'s associated groups.' })
 | 
			
		||||
	async getOne(@Param('id') id: number) {
 | 
			
		||||
		let contact = await this.contactRepository.findOne({ id: id }, { relations: ['groups', 'groups.parentGroup'] })
 | 
			
		||||
		if (!contact) { throw new GroupContactNotFoundError(); }
 | 
			
		||||
		return contact.toResponse();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post()
 | 
			
		||||
	@Authorized("CONTACT:CREATE")
 | 
			
		||||
	@ResponseSchema(ResponseGroupContact)
 | 
			
		||||
	@ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OpenAPI({ description: 'Create a new contact.' })
 | 
			
		||||
	async post(@Body({ validate: true }) createContact: CreateGroupContact) {
 | 
			
		||||
		let contact;
 | 
			
		||||
		try {
 | 
			
		||||
			contact = await createContact.toEntity();
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		contact = await this.contactRepository.save(contact)
 | 
			
		||||
		return (await this.contactRepository.findOne({ id: contact.id }, { relations: ['groups', 'groups.parentGroup'] })).toResponse();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
	@Authorized("CONTACT:UPDATE")
 | 
			
		||||
	@ResponseSchema(ResponseGroupContact)
 | 
			
		||||
	@ResponseSchema(GroupContactNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(GroupContactIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OpenAPI({ description: "Update the contact whose id you provided. <br> Please remember that ids can't be changed." })
 | 
			
		||||
	async put(@Param('id') id: number, @Body({ validate: true }) contact: UpdateGroupContact) {
 | 
			
		||||
		let oldContact = await this.contactRepository.findOne({ id: id });
 | 
			
		||||
 | 
			
		||||
		if (!oldContact) {
 | 
			
		||||
			throw new GroupContactNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (oldContact.id != contact.id) {
 | 
			
		||||
			throw new GroupContactIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.contactRepository.save(await contact.update(oldContact));
 | 
			
		||||
		return (await this.contactRepository.findOne({ id: contact.id }, { relations: ['groups', 'groups.parentGroup'] })).toResponse();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@Authorized("CONTACT:DELETE")
 | 
			
		||||
	@ResponseSchema(ResponseGroupContact)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@OpenAPI({ description: 'Delete the contact whose id you provided. <br> If no contact with this id exists it will just return 204(no content). <br> This won\'t delete any groups associated with the contact.' })
 | 
			
		||||
	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
 | 
			
		||||
		let contact = await this.contactRepository.findOne({ id: id });
 | 
			
		||||
		if (!contact) { return null; }
 | 
			
		||||
		const responseContact = await this.contactRepository.findOne(contact, { relations: ['groups', 'groups.parentGroup'] });
 | 
			
		||||
		for (let group of responseContact.groups) {
 | 
			
		||||
			group.contact = null;
 | 
			
		||||
			await getConnection().getRepository(RunnerGroup).save(group);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.contactRepository.delete(contact);
 | 
			
		||||
		return responseContact.toResponse();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -36,7 +36,7 @@ export class ImportController {
 | 
			
		||||
        return responseRunners;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Post('/organisations/:id/import')
 | 
			
		||||
    @Post('/organizations/:id/import')
 | 
			
		||||
    @ContentType("application/json")
 | 
			
		||||
    @ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 })
 | 
			
		||||
    @ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
 | 
			
		||||
@@ -78,7 +78,7 @@ export class ImportController {
 | 
			
		||||
        return await this.postJSON(importRunners, groupID);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Post('/organisations/:id/import/csv')
 | 
			
		||||
    @Post('/organizations/:id/import/csv')
 | 
			
		||||
    @ContentType("application/json")
 | 
			
		||||
    @UseBefore(RawBodyMiddleware)
 | 
			
		||||
    @ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 })
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										26
									
								
								src/controllers/MailController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/controllers/MailController.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
import { Authorized, JsonController, Post } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { config } from '../config';
 | 
			
		||||
import { Mailer } from '../mailer';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@JsonController('/mails')
 | 
			
		||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
 | 
			
		||||
export class MailController {
 | 
			
		||||
 | 
			
		||||
	private mailer: Mailer;
 | 
			
		||||
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.mailer = new Mailer();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post('/test')
 | 
			
		||||
	@Authorized(["MAIL:CREATE"])
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 200 })
 | 
			
		||||
	@OpenAPI({ description: 'Sends a test email to the configured from-address.' })
 | 
			
		||||
	async get() {
 | 
			
		||||
		await this.mailer.sendTestMail(config.mail_from);
 | 
			
		||||
		return new ResponseEmpty();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										86
									
								
								src/controllers/MeController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/controllers/MeController.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,86 @@
 | 
			
		||||
import { Body, CurrentUser, Delete, Get, JsonController, OnUndefined, Put, QueryParam } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { getConnectionManager, Repository } from 'typeorm';
 | 
			
		||||
import { UserDeletionNotConfirmedError, UserIdsNotMatchingError, UsernameContainsIllegalCharacterError, UserNotFoundError } from '../errors/UserErrors';
 | 
			
		||||
import { UpdateUser } from '../models/actions/update/UpdateUser';
 | 
			
		||||
import { User } from '../models/entities/User';
 | 
			
		||||
import { ResponseUser } from '../models/responses/ResponseUser';
 | 
			
		||||
import { ResponseUserPermissions } from '../models/responses/ResponseUserPermissions';
 | 
			
		||||
import { PermissionController } from './PermissionController';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@JsonController('/users/me')
 | 
			
		||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
 | 
			
		||||
export class MeController {
 | 
			
		||||
	private userRepository: Repository<User>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets the repository of this controller's model/entity.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.userRepository = getConnectionManager().get().getRepository(User);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/')
 | 
			
		||||
	@ResponseSchema(ResponseUser)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(UserNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Lists all information about yourself.' })
 | 
			
		||||
	async get(@CurrentUser() currentUser: User) {
 | 
			
		||||
		let user = await this.userRepository.findOne({ id: currentUser.id }, { relations: ['permissions', 'groups', 'groups.permissions', 'permissions.principal', 'groups.permissions.principal'] })
 | 
			
		||||
		if (!user) { throw new UserNotFoundError(); }
 | 
			
		||||
		return new ResponseUser(user);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/')
 | 
			
		||||
	@ResponseSchema(ResponseUserPermissions)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(UserNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Lists all permissions granted to the you sorted into directly granted and inherited as permission response objects.' })
 | 
			
		||||
	async getPermissions(@CurrentUser() currentUser: User) {
 | 
			
		||||
		let user = await this.userRepository.findOne({ id: currentUser.id }, { relations: ['permissions', 'groups', 'groups.permissions', 'permissions.principal', 'groups.permissions.principal'] })
 | 
			
		||||
		if (!user) { throw new UserNotFoundError(); }
 | 
			
		||||
		return new ResponseUserPermissions(user);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/')
 | 
			
		||||
	@ResponseSchema(ResponseUser)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@ResponseSchema(UsernameContainsIllegalCharacterError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Update the yourself. <br> You can't edit your own permissions or group memberships here - Please use the /api/users/:id enpoint instead. <br> Please remember that ids can't be changed." })
 | 
			
		||||
	async put(@CurrentUser() currentUser: User, @Body({ validate: true }) updateUser: UpdateUser) {
 | 
			
		||||
		let oldUser = await this.userRepository.findOne({ id: currentUser.id }, { relations: ['groups'] });
 | 
			
		||||
		updateUser.groups = oldUser.groups.map(g => g.id);
 | 
			
		||||
 | 
			
		||||
		if (!oldUser) {
 | 
			
		||||
			throw new UserNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (oldUser.id != updateUser.id) {
 | 
			
		||||
			throw new UserIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
		await this.userRepository.save(await updateUser.update(oldUser));
 | 
			
		||||
 | 
			
		||||
		return new ResponseUser(await this.userRepository.findOne({ id: currentUser.id }, { relations: ['permissions', 'groups', 'groups.permissions'] }));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/')
 | 
			
		||||
	@ResponseSchema(ResponseUser)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(UserDeletionNotConfirmedError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: 'Delete yourself. <br> You have to confirm your decision by providing the ?force=true query param. <br> If there are any permissions directly granted to you they will get deleted as well.' })
 | 
			
		||||
	async remove(@CurrentUser() currentUser: User, @QueryParam("force") force: boolean) {
 | 
			
		||||
		if (!force) { throw new UserDeletionNotConfirmedError; }
 | 
			
		||||
		if (!currentUser) { return UserNotFoundError; }
 | 
			
		||||
		const responseUser = await this.userRepository.findOne({ id: currentUser.id }, { relations: ['permissions', 'groups', 'groups.permissions'] });;
 | 
			
		||||
 | 
			
		||||
		const permissionControler = new PermissionController();
 | 
			
		||||
		for (let permission of responseUser.permissions) {
 | 
			
		||||
			await permissionControler.remove(permission.id, true);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.userRepository.delete(currentUser);
 | 
			
		||||
		return new ResponseUser(responseUser);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -90,7 +90,7 @@ export class PermissionController {
 | 
			
		||||
        if (oldPermission.id != permission.id) {
 | 
			
		||||
            throw new PermissionIdsNotMatchingError();
 | 
			
		||||
        }
 | 
			
		||||
        let existingPermission = await this.permissionRepository.findOne({ target: permission.target, action: permission.action, principal: permission.principal }, { relations: ['principal'] });
 | 
			
		||||
        let existingPermission = await this.permissionRepository.findOne({ target: permission.target, action: permission.action, principal: await permission.getPrincipal() }, { relations: ['principal'] });
 | 
			
		||||
        if (existingPermission) {
 | 
			
		||||
            await this.remove(permission.id, true);
 | 
			
		||||
            return new ResponsePermission(existingPermission);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,16 @@
 | 
			
		||||
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 { RunnerGroupNeededError, RunnerIdsNotMatchingError, RunnerNotFoundError } from '../errors/RunnerErrors';
 | 
			
		||||
import { RunnerGroupNeededError, RunnerHasDistanceDonationsError, RunnerIdsNotMatchingError, RunnerNotFoundError } from '../errors/RunnerErrors';
 | 
			
		||||
import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors';
 | 
			
		||||
import { CreateRunner } from '../models/actions/create/CreateRunner';
 | 
			
		||||
import { UpdateRunner } from '../models/actions/update/UpdateRunner';
 | 
			
		||||
import { Runner } from '../models/entities/Runner';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
import { ResponseRunner } from '../models/responses/ResponseRunner';
 | 
			
		||||
import { ResponseScan } from '../models/responses/ResponseScan';
 | 
			
		||||
import { ResponseTrackScan } from '../models/responses/ResponseTrackScan';
 | 
			
		||||
import { DonationController } from './DonationController';
 | 
			
		||||
import { RunnerCardController } from './RunnerCardController';
 | 
			
		||||
import { ScanController } from './ScanController';
 | 
			
		||||
 | 
			
		||||
@@ -29,7 +32,7 @@ export class RunnerController {
 | 
			
		||||
	@OpenAPI({ description: 'Lists all runners from all teams/orgs. <br> This includes the runner\'s group and distance ran.' })
 | 
			
		||||
	async getAll() {
 | 
			
		||||
		let responseRunners: ResponseRunner[] = new Array<ResponseRunner>();
 | 
			
		||||
		const runners = await this.runnerRepository.find({ relations: ['scans', 'group', 'scans.track', 'cards'] });
 | 
			
		||||
		const runners = await this.runnerRepository.find({ relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] });
 | 
			
		||||
		runners.forEach(runner => {
 | 
			
		||||
			responseRunners.push(new ResponseRunner(runner));
 | 
			
		||||
		});
 | 
			
		||||
@@ -43,11 +46,36 @@ export class RunnerController {
 | 
			
		||||
	@OnUndefined(RunnerNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Lists all information about the runner whose id got provided.' })
 | 
			
		||||
	async getOne(@Param('id') id: number) {
 | 
			
		||||
		let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'scans.track', 'cards'] })
 | 
			
		||||
		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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id/scans')
 | 
			
		||||
	@Authorized(["RUNNER:GET", "SCAN:GET"])
 | 
			
		||||
	@ResponseSchema(ResponseScan, { isArray: true })
 | 
			
		||||
	@ResponseSchema(ResponseTrackScan, { isArray: true })
 | 
			
		||||
	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all scans of the runner whose id got provided. <br> If you only want the valid scans just add the ?onlyValid=true query param.' })
 | 
			
		||||
	async getScans(@Param('id') id: number, onlyValid?: boolean) {
 | 
			
		||||
		let responseScans: ResponseScan[] = new Array<ResponseScan>();
 | 
			
		||||
		let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'scans.track', 'scans.station', 'scans.runner'] })
 | 
			
		||||
		if (!runner) { throw new RunnerNotFoundError(); }
 | 
			
		||||
 | 
			
		||||
		if (!onlyValid) {
 | 
			
		||||
			for (let scan of runner.scans) {
 | 
			
		||||
				responseScans.push(scan.toResponse());
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			for (let scan of runner.validScans) {
 | 
			
		||||
				responseScans.push(scan.toResponse());
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return responseScans;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post()
 | 
			
		||||
	@Authorized("RUNNER:CREATE")
 | 
			
		||||
	@ResponseSchema(ResponseRunner)
 | 
			
		||||
@@ -63,7 +91,7 @@ export class RunnerController {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		runner = await this.runnerRepository.save(runner)
 | 
			
		||||
		return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'scans.track', 'cards'] }));
 | 
			
		||||
		return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] }));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
@@ -84,28 +112,38 @@ export class RunnerController {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.runnerRepository.save(await runner.update(oldRunner));
 | 
			
		||||
		return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'scans.track', 'cards'] }));
 | 
			
		||||
		return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] }));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@Authorized("RUNNER:DELETE")
 | 
			
		||||
	@ResponseSchema(ResponseRunner)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@ResponseSchema(RunnerHasDistanceDonationsError, { statusCode: 406 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@OpenAPI({ description: 'Delete the runner whose id you provided. <br> This will also delete all scans and cards associated with the runner. <br> If no runner with this id exists it will just return 204(no content).' })
 | 
			
		||||
	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', 'scans.track', 'cards'] });
 | 
			
		||||
		const responseRunner = await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] });
 | 
			
		||||
 | 
			
		||||
		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 scan of runnerCards) {
 | 
			
		||||
			await cardController.remove(scan.id, force);
 | 
			
		||||
		for (let card of runnerCards) {
 | 
			
		||||
			await cardController.remove(card.id, force);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const runnerScans = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["scans"] })).scans;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,127 +0,0 @@
 | 
			
		||||
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 { RunnerOrganisationHasRunnersError, RunnerOrganisationHasTeamsError, RunnerOrganisationIdsNotMatchingError, RunnerOrganisationNotFoundError } from '../errors/RunnerOrganisationErrors';
 | 
			
		||||
import { CreateRunnerOrganisation } from '../models/actions/create/CreateRunnerOrganisation';
 | 
			
		||||
import { UpdateRunnerOrganisation } from '../models/actions/update/UpdateRunnerOrganisation';
 | 
			
		||||
import { RunnerOrganisation } from '../models/entities/RunnerOrganisation';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
import { ResponseRunnerOrganisation } from '../models/responses/ResponseRunnerOrganisation';
 | 
			
		||||
import { RunnerController } from './RunnerController';
 | 
			
		||||
import { RunnerTeamController } from './RunnerTeamController';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@JsonController('/organisations')
 | 
			
		||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
 | 
			
		||||
export class RunnerOrganisationController {
 | 
			
		||||
	private runnerOrganisationRepository: Repository<RunnerOrganisation>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets the repository of this controller's model/entity.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.runnerOrganisationRepository = getConnectionManager().get().getRepository(RunnerOrganisation);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get()
 | 
			
		||||
	@Authorized("ORGANISATION:GET")
 | 
			
		||||
	@ResponseSchema(ResponseRunnerOrganisation, { isArray: true })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all organisations. <br> This includes their address, contact and teams (if existing/associated).' })
 | 
			
		||||
	async getAll() {
 | 
			
		||||
		let responseTeams: ResponseRunnerOrganisation[] = new Array<ResponseRunnerOrganisation>();
 | 
			
		||||
		const runners = await this.runnerOrganisationRepository.find({ relations: ['address', 'contact', 'teams'] });
 | 
			
		||||
		runners.forEach(runner => {
 | 
			
		||||
			responseTeams.push(new ResponseRunnerOrganisation(runner));
 | 
			
		||||
		});
 | 
			
		||||
		return responseTeams;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id')
 | 
			
		||||
	@Authorized("ORGANISATION:GET")
 | 
			
		||||
	@ResponseSchema(ResponseRunnerOrganisation)
 | 
			
		||||
	@ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(RunnerOrganisationNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Lists all information about the organisation whose id got provided.' })
 | 
			
		||||
	async getOne(@Param('id') id: number) {
 | 
			
		||||
		let runnerOrg = await this.runnerOrganisationRepository.findOne({ id: id }, { relations: ['address', 'contact', 'teams'] });
 | 
			
		||||
		if (!runnerOrg) { throw new RunnerOrganisationNotFoundError(); }
 | 
			
		||||
		return new ResponseRunnerOrganisation(runnerOrg);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post()
 | 
			
		||||
	@Authorized("ORGANISATION:CREATE")
 | 
			
		||||
	@ResponseSchema(ResponseRunnerOrganisation)
 | 
			
		||||
	@OpenAPI({ description: 'Create a new organsisation.' })
 | 
			
		||||
	async post(@Body({ validate: true }) createRunnerOrganisation: CreateRunnerOrganisation) {
 | 
			
		||||
		let runnerOrganisation;
 | 
			
		||||
		try {
 | 
			
		||||
			runnerOrganisation = await createRunnerOrganisation.toEntity();
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		runnerOrganisation = await this.runnerOrganisationRepository.save(runnerOrganisation);
 | 
			
		||||
 | 
			
		||||
		return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(runnerOrganisation, { relations: ['address', 'contact', 'teams'] }));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
	@Authorized("ORGANISATION:UPDATE")
 | 
			
		||||
	@ResponseSchema(ResponseRunnerOrganisation)
 | 
			
		||||
	@ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(RunnerOrganisationIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Update the organisation whose id you provided. <br> Please remember that ids can't be changed." })
 | 
			
		||||
	async put(@Param('id') id: number, @Body({ validate: true }) updateOrganisation: UpdateRunnerOrganisation) {
 | 
			
		||||
		let oldRunnerOrganisation = await this.runnerOrganisationRepository.findOne({ id: id });
 | 
			
		||||
 | 
			
		||||
		if (!oldRunnerOrganisation) {
 | 
			
		||||
			throw new RunnerOrganisationNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (oldRunnerOrganisation.id != updateOrganisation.id) {
 | 
			
		||||
			throw new RunnerOrganisationIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.runnerOrganisationRepository.save(await updateOrganisation.update(oldRunnerOrganisation));
 | 
			
		||||
 | 
			
		||||
		return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(id, { relations: ['address', 'contact', 'teams'] }));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@Authorized("ORGANISATION:DELETE")
 | 
			
		||||
	@ResponseSchema(ResponseRunnerOrganisation)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@ResponseSchema(RunnerOrganisationHasTeamsError, { statusCode: 406 })
 | 
			
		||||
	@ResponseSchema(RunnerOrganisationHasRunnersError, { statusCode: 406 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@OpenAPI({ description: 'Delete the organsisation whose id you provided. <br> If the organisation still has runners and/or teams associated this will fail. <br> To delete the organisation with all associated runners and teams set the force QueryParam to true (cascading deletion might take a while). <br> If no organisation with this id exists it will just return 204(no content).' })
 | 
			
		||||
	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
 | 
			
		||||
		let organisation = await this.runnerOrganisationRepository.findOne({ id: id });
 | 
			
		||||
		if (!organisation) { return null; }
 | 
			
		||||
		let runnerOrganisation = await this.runnerOrganisationRepository.findOne(organisation, { relations: ['address', 'contact', 'runners', 'teams'] });
 | 
			
		||||
 | 
			
		||||
		if (!force) {
 | 
			
		||||
			if (runnerOrganisation.teams.length != 0) {
 | 
			
		||||
				throw new RunnerOrganisationHasTeamsError();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		const teamController = new RunnerTeamController()
 | 
			
		||||
		for (let team of runnerOrganisation.teams) {
 | 
			
		||||
			await teamController.remove(team.id, true);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (!force) {
 | 
			
		||||
			if (runnerOrganisation.runners.length != 0) {
 | 
			
		||||
				throw new RunnerOrganisationHasRunnersError();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		const runnerController = new RunnerController()
 | 
			
		||||
		for (let runner of runnerOrganisation.runners) {
 | 
			
		||||
			await runnerController.remove(runner.id, true);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const responseOrganisation = new ResponseRunnerOrganisation(runnerOrganisation);
 | 
			
		||||
		await this.runnerOrganisationRepository.delete(organisation);
 | 
			
		||||
		return responseOrganisation;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										145
									
								
								src/controllers/RunnerOrganizationController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								src/controllers/RunnerOrganizationController.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,145 @@
 | 
			
		||||
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 { RunnerOrganizationHasRunnersError, RunnerOrganizationHasTeamsError, RunnerOrganizationIdsNotMatchingError, RunnerOrganizationNotFoundError } from '../errors/RunnerOrganizationErrors';
 | 
			
		||||
import { CreateRunnerOrganization } from '../models/actions/create/CreateRunnerOrganization';
 | 
			
		||||
import { UpdateRunnerOrganization } from '../models/actions/update/UpdateRunnerOrganization';
 | 
			
		||||
import { Runner } from '../models/entities/Runner';
 | 
			
		||||
import { RunnerOrganization } from '../models/entities/RunnerOrganization';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
import { ResponseRunner } from '../models/responses/ResponseRunner';
 | 
			
		||||
import { ResponseRunnerOrganization } from '../models/responses/ResponseRunnerOrganization';
 | 
			
		||||
import { RunnerController } from './RunnerController';
 | 
			
		||||
import { RunnerTeamController } from './RunnerTeamController';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@JsonController('/organizations')
 | 
			
		||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
 | 
			
		||||
export class RunnerOrganizationController {
 | 
			
		||||
	private runnerOrganizationRepository: Repository<RunnerOrganization>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets the repository of this controller's model/entity.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.runnerOrganizationRepository = getConnectionManager().get().getRepository(RunnerOrganization);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get()
 | 
			
		||||
	@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));
 | 
			
		||||
		});
 | 
			
		||||
		return responseTeams;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id')
 | 
			
		||||
	@Authorized("ORGANIZATION:GET")
 | 
			
		||||
	@ResponseSchema(ResponseRunnerOrganization)
 | 
			
		||||
	@ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@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'] });
 | 
			
		||||
		if (!runnerOrg) { throw new RunnerOrganizationNotFoundError(); }
 | 
			
		||||
		return new ResponseRunnerOrganization(runnerOrg);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id/runners')
 | 
			
		||||
	@Authorized(["RUNNER:GET", "SCAN:GET"])
 | 
			
		||||
	@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) {
 | 
			
		||||
		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));
 | 
			
		||||
		});
 | 
			
		||||
		return responseRunners;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post()
 | 
			
		||||
	@Authorized("ORGANIZATION:CREATE")
 | 
			
		||||
	@ResponseSchema(ResponseRunnerOrganization)
 | 
			
		||||
	@OpenAPI({ description: 'Create a new organsisation.' })
 | 
			
		||||
	async post(@Body({ validate: true }) createRunnerOrganization: CreateRunnerOrganization) {
 | 
			
		||||
		let runnerOrganization;
 | 
			
		||||
		try {
 | 
			
		||||
			runnerOrganization = await createRunnerOrganization.toEntity();
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		runnerOrganization = await this.runnerOrganizationRepository.save(runnerOrganization);
 | 
			
		||||
 | 
			
		||||
		return new ResponseRunnerOrganization(await this.runnerOrganizationRepository.findOne(runnerOrganization, { relations: ['contact', 'teams'] }));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
	@Authorized("ORGANIZATION:UPDATE")
 | 
			
		||||
	@ResponseSchema(ResponseRunnerOrganization)
 | 
			
		||||
	@ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(RunnerOrganizationIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Update the organization whose id you provided. <br> Please remember that ids can't be changed." })
 | 
			
		||||
	async put(@Param('id') id: number, @Body({ validate: true }) updateOrganization: UpdateRunnerOrganization) {
 | 
			
		||||
		let oldRunnerOrganization = await this.runnerOrganizationRepository.findOne({ id: id });
 | 
			
		||||
 | 
			
		||||
		if (!oldRunnerOrganization) {
 | 
			
		||||
			throw new RunnerOrganizationNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (oldRunnerOrganization.id != updateOrganization.id) {
 | 
			
		||||
			throw new RunnerOrganizationIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.runnerOrganizationRepository.save(await updateOrganization.update(oldRunnerOrganization));
 | 
			
		||||
 | 
			
		||||
		return new ResponseRunnerOrganization(await this.runnerOrganizationRepository.findOne(id, { relations: ['contact', 'teams'] }));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@Authorized("ORGANIZATION:DELETE")
 | 
			
		||||
	@ResponseSchema(ResponseRunnerOrganization)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@ResponseSchema(RunnerOrganizationHasTeamsError, { statusCode: 406 })
 | 
			
		||||
	@ResponseSchema(RunnerOrganizationHasRunnersError, { statusCode: 406 })
 | 
			
		||||
	@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) {
 | 
			
		||||
		let organization = await this.runnerOrganizationRepository.findOne({ id: id });
 | 
			
		||||
		if (!organization) { return null; }
 | 
			
		||||
		let runnerOrganization = await this.runnerOrganizationRepository.findOne(organization, { relations: ['contact', 'runners', 'teams'] });
 | 
			
		||||
 | 
			
		||||
		if (!force) {
 | 
			
		||||
			if (runnerOrganization.teams.length != 0) {
 | 
			
		||||
				throw new RunnerOrganizationHasTeamsError();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		const teamController = new RunnerTeamController()
 | 
			
		||||
		for (let team of runnerOrganization.teams) {
 | 
			
		||||
			await teamController.remove(team.id, true);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (!force) {
 | 
			
		||||
			if (runnerOrganization.runners.length != 0) {
 | 
			
		||||
				throw new RunnerOrganizationHasRunnersError();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		const runnerController = new RunnerController()
 | 
			
		||||
		for (let runner of runnerOrganization.runners) {
 | 
			
		||||
			await runnerController.remove(runner.id, true);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const responseOrganization = new ResponseRunnerOrganization(runnerOrganization);
 | 
			
		||||
		await this.runnerOrganizationRepository.delete(organization);
 | 
			
		||||
		return responseOrganization;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										98
									
								
								src/controllers/RunnerSelfServiceController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/controllers/RunnerSelfServiceController.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
			
		||||
import * as jwt from "jsonwebtoken";
 | 
			
		||||
import { Body, Get, JsonController, OnUndefined, Param, Post } 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 { RunnerEmailNeededError, RunnerNotFoundError } from '../errors/RunnerErrors';
 | 
			
		||||
import { RunnerOrganizationNotFoundError } from '../errors/RunnerOrganizationErrors';
 | 
			
		||||
import { JwtCreator } from '../jwtcreator';
 | 
			
		||||
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 { ResponseSelfServiceRunner } from '../models/responses/ResponseSelfServiceRunner';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@JsonController('/runners')
 | 
			
		||||
export class RunnerSelfServiceController {
 | 
			
		||||
	private runnerRepository: Repository<Runner>;
 | 
			
		||||
	private orgRepository: Repository<RunnerOrganization>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets the repository of this controller's model/entity.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.runnerRepository = getConnectionManager().get().getRepository(Runner);
 | 
			
		||||
		this.orgRepository = getConnectionManager().get().getRepository(RunnerOrganization);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/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 contact support.' })
 | 
			
		||||
	async get(@Param('jwt') token: string) {
 | 
			
		||||
		return (new ResponseSelfServiceRunner(await this.getRunner(token)));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post('/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);
 | 
			
		||||
		return response;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post('/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);
 | 
			
		||||
		return response;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 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;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -6,6 +6,7 @@ import { CreateRunnerTeam } from '../models/actions/create/CreateRunnerTeam';
 | 
			
		||||
import { UpdateRunnerTeam } from '../models/actions/update/UpdateRunnerTeam';
 | 
			
		||||
import { RunnerTeam } from '../models/entities/RunnerTeam';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
import { ResponseRunner } from '../models/responses/ResponseRunner';
 | 
			
		||||
import { ResponseRunnerTeam } from '../models/responses/ResponseRunnerTeam';
 | 
			
		||||
import { RunnerController } from './RunnerController';
 | 
			
		||||
 | 
			
		||||
@@ -25,7 +26,7 @@ export class RunnerTeamController {
 | 
			
		||||
	@Get()
 | 
			
		||||
	@Authorized("TEAM:GET")
 | 
			
		||||
	@ResponseSchema(ResponseRunnerTeam, { isArray: true })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all teams. <br> This includes their parent organisation and contact (if existing/associated).' })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all teams. <br> This includes their parent organization and contact (if existing/associated).' })
 | 
			
		||||
	async getAll() {
 | 
			
		||||
		let responseTeams: ResponseRunnerTeam[] = new Array<ResponseRunnerTeam>();
 | 
			
		||||
		const runners = await this.runnerTeamRepository.find({ relations: ['parentGroup', 'contact'] });
 | 
			
		||||
@@ -47,6 +48,20 @@ export class RunnerTeamController {
 | 
			
		||||
		return new ResponseRunnerTeam(runnerTeam);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id/runners')
 | 
			
		||||
	@Authorized(["RUNNER:GET", "SCAN:GET"])
 | 
			
		||||
	@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) {
 | 
			
		||||
		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));
 | 
			
		||||
		});
 | 
			
		||||
		return responseRunners;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post()
 | 
			
		||||
	@Authorized("TEAM:CREATE")
 | 
			
		||||
	@ResponseSchema(ResponseRunnerTeam)
 | 
			
		||||
@@ -93,7 +108,7 @@ export class RunnerTeamController {
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@ResponseSchema(RunnerTeamHasRunnersError, { statusCode: 406 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@OpenAPI({ description: 'Delete the team whose id you provided. <br> If the team still has runners associated this will fail. <br> To delete the team with all associated runners set the force QueryParam to true (cascading deletion might take a while). <br> If no team with this id exists it will just return 204(no content).' })
 | 
			
		||||
	@OpenAPI({ description: 'Delete the team whose id you provided. <br> If the team still has runners associated this will fail. <br> To delete the team with all associated runners set the force QueryParam to true (cascading deletion might take a while). <br> This won\'t delete the associated contact.<br> If no team with this id exists it will just return 204(no content).' })
 | 
			
		||||
	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
 | 
			
		||||
		let team = await this.runnerTeamRepository.findOne({ id: id });
 | 
			
		||||
		if (!team) { return null; }
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ export class ScanController {
 | 
			
		||||
	@OpenAPI({ description: 'Lists all scans (normal or track) from all runners. <br> This includes the scan\'s runner\'s distance ran.' })
 | 
			
		||||
	async getAll() {
 | 
			
		||||
		let responseScans: ResponseScan[] = new Array<ResponseScan>();
 | 
			
		||||
		const scans = await this.scanRepository.find({ relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] });
 | 
			
		||||
		const scans = await this.scanRepository.find({ relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] });
 | 
			
		||||
		scans.forEach(scan => {
 | 
			
		||||
			responseScans.push(scan.toResponse());
 | 
			
		||||
		});
 | 
			
		||||
@@ -51,7 +51,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.scans.track', 'card', 'station'] })
 | 
			
		||||
		let scan = await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] })
 | 
			
		||||
		if (!scan) { throw new ScanNotFoundError(); }
 | 
			
		||||
		return scan.toResponse();
 | 
			
		||||
	}
 | 
			
		||||
@@ -64,7 +64,7 @@ export class ScanController {
 | 
			
		||||
	async post(@Body({ validate: true }) createScan: CreateScan) {
 | 
			
		||||
		let scan = await createScan.toEntity();
 | 
			
		||||
		scan = await this.scanRepository.save(scan);
 | 
			
		||||
		return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse();
 | 
			
		||||
		return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] })).toResponse();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post("/trackscans")
 | 
			
		||||
@@ -75,7 +75,7 @@ export class ScanController {
 | 
			
		||||
	async postTrackScans(@Body({ validate: true }) createScan: CreateTrackScan) {
 | 
			
		||||
		let scan = await createScan.toEntity();
 | 
			
		||||
		scan = await this.trackScanRepository.save(scan);
 | 
			
		||||
		return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse();
 | 
			
		||||
		return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] })).toResponse();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
@@ -97,7 +97,7 @@ export class ScanController {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.scanRepository.save(await scan.update(oldScan));
 | 
			
		||||
		return (await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse();
 | 
			
		||||
		return (await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] })).toResponse();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/trackscans/:id')
 | 
			
		||||
@@ -120,7 +120,7 @@ export class ScanController {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.trackScanRepository.save(await scan.update(oldScan));
 | 
			
		||||
		return (await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse();
 | 
			
		||||
		return (await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] })).toResponse();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
@@ -132,7 +132,7 @@ export class ScanController {
 | 
			
		||||
	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
 | 
			
		||||
		let scan = await this.scanRepository.findOne({ id: id });
 | 
			
		||||
		if (!scan) { return null; }
 | 
			
		||||
		const responseScan = await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] });
 | 
			
		||||
		const responseScan = await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] });
 | 
			
		||||
 | 
			
		||||
		await this.scanRepository.delete(scan);
 | 
			
		||||
		return responseScan.toResponse();
 | 
			
		||||
 
 | 
			
		||||
@@ -4,12 +4,12 @@ import { getConnection } from 'typeorm';
 | 
			
		||||
import StatsAuth from '../middlewares/StatsAuth';
 | 
			
		||||
import { Donation } from '../models/entities/Donation';
 | 
			
		||||
import { Runner } from '../models/entities/Runner';
 | 
			
		||||
import { RunnerOrganisation } from '../models/entities/RunnerOrganisation';
 | 
			
		||||
import { RunnerOrganization } from '../models/entities/RunnerOrganization';
 | 
			
		||||
import { RunnerTeam } from '../models/entities/RunnerTeam';
 | 
			
		||||
import { Scan } from '../models/entities/Scan';
 | 
			
		||||
import { User } from '../models/entities/User';
 | 
			
		||||
import { ResponseStats } from '../models/responses/ResponseStats';
 | 
			
		||||
import { ResponseStatsOrgnisation } from '../models/responses/ResponseStatsOrganisation';
 | 
			
		||||
import { ResponseStatsOrgnisation } from '../models/responses/ResponseStatsOrganization';
 | 
			
		||||
import { ResponseStatsRunner } from '../models/responses/ResponseStatsRunner';
 | 
			
		||||
import { ResponseStatsTeam } from '../models/responses/ResponseStatsTeam';
 | 
			
		||||
 | 
			
		||||
@@ -23,7 +23,7 @@ export class StatsController {
 | 
			
		||||
        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(RunnerOrganisation).find();
 | 
			
		||||
        let orgs = await connection.getRepository(RunnerOrganization).find();
 | 
			
		||||
        let users = await connection.getRepository(User).find();
 | 
			
		||||
        let scans = await connection.getRepository(Scan).find();
 | 
			
		||||
        let donations = await connection.getRepository(Donation).find({ relations: ['runner', 'runner.scans', 'runner.scans.track'] });
 | 
			
		||||
@@ -94,12 +94,12 @@ export class StatsController {
 | 
			
		||||
        return responseTeams;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Get("/organisations/distance")
 | 
			
		||||
    @Get("/organizations/distance")
 | 
			
		||||
    @UseBefore(StatsAuth)
 | 
			
		||||
    @ResponseSchema(ResponseStatsOrgnisation, { isArray: true })
 | 
			
		||||
    @OpenAPI({ description: "Returns the top ten organisations by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
 | 
			
		||||
    @OpenAPI({ description: "Returns the top ten organizations by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
 | 
			
		||||
    async getTopOrgsByDistance() {
 | 
			
		||||
        let orgs = await getConnection().getRepository(RunnerOrganisation).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track', 'teams', 'teams.runners', 'teams.runners.scans', 'teams.runners.distanceDonations', 'teams.runners.scans.track'] });
 | 
			
		||||
        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);
 | 
			
		||||
        let responseOrgs: ResponseStatsOrgnisation[] = new Array<ResponseStatsOrgnisation>();
 | 
			
		||||
        topOrgs.forEach(org => {
 | 
			
		||||
@@ -108,12 +108,12 @@ export class StatsController {
 | 
			
		||||
        return responseOrgs;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Get("/organisations/donations")
 | 
			
		||||
    @Get("/organizations/donations")
 | 
			
		||||
    @UseBefore(StatsAuth)
 | 
			
		||||
    @ResponseSchema(ResponseStatsOrgnisation, { isArray: true })
 | 
			
		||||
    @OpenAPI({ description: "Returns the top ten organisations by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
 | 
			
		||||
    @OpenAPI({ description: "Returns the top ten organizations by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
 | 
			
		||||
    async getTopOrgsByDonations() {
 | 
			
		||||
        let orgs = await getConnection().getRepository(RunnerOrganisation).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track', 'teams', 'teams.runners', 'teams.runners.scans', 'teams.runners.distanceDonations', 'teams.runners.scans.track'] });
 | 
			
		||||
        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 responseOrgs: ResponseStatsOrgnisation[] = new Array<ResponseStatsOrgnisation>();
 | 
			
		||||
        topOrgs.forEach(org => {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,12 @@
 | 
			
		||||
import { Get, JsonController } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI } from 'routing-controllers-openapi';
 | 
			
		||||
import { getConnection } from 'typeorm';
 | 
			
		||||
import { config } from '../config';
 | 
			
		||||
 | 
			
		||||
@JsonController('/status')
 | 
			
		||||
@JsonController()
 | 
			
		||||
export class StatusController {
 | 
			
		||||
 | 
			
		||||
    @Get()
 | 
			
		||||
    @Get('/status')
 | 
			
		||||
    @OpenAPI({ description: "A very basic status/health endpoint that just checks if the database connection is available. <br> The available information depth will be expanded later." })
 | 
			
		||||
    get() {
 | 
			
		||||
        let connection;
 | 
			
		||||
@@ -19,4 +20,12 @@ export class StatusController {
 | 
			
		||||
            "database connection": "✔"
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Get('/version')
 | 
			
		||||
    @OpenAPI({ description: "A very basic endpoint that just returns the curent package version." })
 | 
			
		||||
    getVersion() {
 | 
			
		||||
        return {
 | 
			
		||||
            "version": config.version
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,13 +1,14 @@
 | 
			
		||||
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 { UserIdsNotMatchingError, UserNotFoundError } from '../errors/UserErrors';
 | 
			
		||||
import { UserDeletionNotConfirmedError, UserIdsNotMatchingError, UsernameContainsIllegalCharacterError, UserNotFoundError } from '../errors/UserErrors';
 | 
			
		||||
import { UserGroupNotFoundError } from '../errors/UserGroupErrors';
 | 
			
		||||
import { CreateUser } from '../models/actions/create/CreateUser';
 | 
			
		||||
import { UpdateUser } from '../models/actions/update/UpdateUser';
 | 
			
		||||
import { User } from '../models/entities/User';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
import { ResponseUser } from '../models/responses/ResponseUser';
 | 
			
		||||
import { ResponseUserPermissions } from '../models/responses/ResponseUserPermissions';
 | 
			
		||||
import { PermissionController } from './PermissionController';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -26,7 +27,7 @@ export class UserController {
 | 
			
		||||
	@Get()
 | 
			
		||||
	@Authorized("USER:GET")
 | 
			
		||||
	@ResponseSchema(ResponseUser, { isArray: true })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all users. <br> This includes their groups and permissions directly granted to them (if existing/associated).' })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all users. <br> This includes their groups and permissions granted to them.' })
 | 
			
		||||
	async getAll() {
 | 
			
		||||
		let responseUsers: ResponseUser[] = new Array<ResponseUser>();
 | 
			
		||||
		const users = await this.userRepository.find({ relations: ['permissions', 'groups', 'groups.permissions'] });
 | 
			
		||||
@@ -41,17 +42,30 @@ export class UserController {
 | 
			
		||||
	@ResponseSchema(ResponseUser)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(UserNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Lists all information about the user whose id got provided. <br> Please remember that only permissions granted directly to the user will show up here, not permissions inherited from groups.' })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all information about the user whose id got provided. <br> Please remember that all permissions granted to the user will show up here.' })
 | 
			
		||||
	async getOne(@Param('id') id: number) {
 | 
			
		||||
		let user = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions'] })
 | 
			
		||||
		if (!user) { throw new UserNotFoundError(); }
 | 
			
		||||
		return new ResponseUser(user);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id/permissions')
 | 
			
		||||
	@Authorized("USER:GET")
 | 
			
		||||
	@ResponseSchema(ResponseUser)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(UserNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Lists all permissions granted to the user sorted into directly granted and inherited as permission response objects.' })
 | 
			
		||||
	async getPermissions(@Param('id') id: number) {
 | 
			
		||||
		let user = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions', 'permissions.principal', 'groups.permissions.principal'] })
 | 
			
		||||
		if (!user) { throw new UserNotFoundError(); }
 | 
			
		||||
		return new ResponseUserPermissions(user);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post()
 | 
			
		||||
	@Authorized("USER:CREATE")
 | 
			
		||||
	@ResponseSchema(ResponseUser)
 | 
			
		||||
	@ResponseSchema(UserGroupNotFoundError)
 | 
			
		||||
	@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(UsernameContainsIllegalCharacterError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: 'Create a new user. <br> If you want to grant permissions to the user you have to create them seperately by posting to /api/permissions after creating the user.' })
 | 
			
		||||
	async post(@Body({ validate: true }) createUser: CreateUser) {
 | 
			
		||||
		let user;
 | 
			
		||||
@@ -62,7 +76,7 @@ export class UserController {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		user = await this.userRepository.save(user)
 | 
			
		||||
		return new ResponseUser(await this.userRepository.findOne({ id: user.id }, { relations: ['permissions', 'groups'] }));
 | 
			
		||||
		return new ResponseUser(await this.userRepository.findOne({ id: user.id }, { relations: ['permissions', 'groups', 'groups.permissions'] }));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
@@ -70,6 +84,7 @@ export class UserController {
 | 
			
		||||
	@ResponseSchema(ResponseUser)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@ResponseSchema(UsernameContainsIllegalCharacterError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Update the user whose id you provided. <br> To change the permissions directly granted to the user please use /api/permissions instead. <br> Please remember that ids can't be changed." })
 | 
			
		||||
	async put(@Param('id') id: number, @Body({ validate: true }) updateUser: UpdateUser) {
 | 
			
		||||
		let oldUser = await this.userRepository.findOne({ id: id });
 | 
			
		||||
@@ -83,19 +98,21 @@ export class UserController {
 | 
			
		||||
		}
 | 
			
		||||
		await this.userRepository.save(await updateUser.update(oldUser));
 | 
			
		||||
 | 
			
		||||
		return new ResponseUser(await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] }));
 | 
			
		||||
		return new ResponseUser(await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions'] }));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@Authorized("USER:DELETE")
 | 
			
		||||
	@ResponseSchema(ResponseUser)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@ResponseSchema(UserDeletionNotConfirmedError, { statusCode: 406 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@OpenAPI({ description: 'Delete the user whose id you provided. <br> If there are any permissions directly granted to the user they will get deleted as well. <br> If no user with this id exists it will just return 204(no content).' })
 | 
			
		||||
	@OpenAPI({ description: 'Delete the user whose id you provided. <br> You have to confirm your decision by providing the ?force=true query param. <br> If there are any permissions directly granted to the user they will get deleted as well. <br> If no user with this id exists it will just return 204(no content).' })
 | 
			
		||||
	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
 | 
			
		||||
		if (!force) { throw new UserDeletionNotConfirmedError; }
 | 
			
		||||
		let user = await this.userRepository.findOne({ id: id });
 | 
			
		||||
		if (!user) { return null; }
 | 
			
		||||
		const responseUser = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] });;
 | 
			
		||||
		const responseUser = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions'] });;
 | 
			
		||||
 | 
			
		||||
		const permissionControler = new PermissionController();
 | 
			
		||||
		for (let permission of responseUser.permissions) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,57 @@
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
 | 
			
		||||
import { BadRequestError } from 'routing-controllers';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw, when to provided address doesn't belong to the accepted types.
 | 
			
		||||
 * Error to throw when an address's postal code fails validation.
 | 
			
		||||
 */
 | 
			
		||||
export class AddressWrongTypeError extends NotAcceptableError {
 | 
			
		||||
export class AddressPostalCodeInvalidError extends BadRequestError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "AddressWrongTypeError"
 | 
			
		||||
	name = "AddressPostalCodeInvalidError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The address must be an existing address's id. \n You provided a object of another type."
 | 
			
		||||
	message = "The postal code you provided is invalid. \n Please check if your postal code follows the postal code validation guidelines."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw, when a non-existent address get's loaded.
 | 
			
		||||
 * Error to throw when an non-empty address's first line isn't set.
 | 
			
		||||
 */
 | 
			
		||||
export class AddressNotFoundError extends NotFoundError {
 | 
			
		||||
export class AddressFirstLineEmptyError extends BadRequestError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "AddressNotFoundError"
 | 
			
		||||
	name = "AddressFirstLineEmptyError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The address you provided couldn't be located in the system. \n Please check your request."
 | 
			
		||||
	message = "You provided a empty first address line. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when an non-empty address's postal code isn't set.
 | 
			
		||||
 */
 | 
			
		||||
export class AddressPostalCodeEmptyError extends BadRequestError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "AddressPostalCodeEmptyError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "You provided a empty postal code. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when an non-empty address's city isn't set.
 | 
			
		||||
 */
 | 
			
		||||
export class AddressCityEmptyError extends BadRequestError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "AddressCityEmptyError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "You provided a empty city. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when an non-empty address's country isn't set.
 | 
			
		||||
 */
 | 
			
		||||
export class AddressCountryEmptyError extends BadRequestError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "AddressCountryEmptyError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "You provided a empty country. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								src/errors/DonationErrors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/errors/DonationErrors.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a Donation couldn't be found.
 | 
			
		||||
 */
 | 
			
		||||
export class DonationNotFoundError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "DonationNotFoundError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "Donation not found!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when two Donations' ids don't match.
 | 
			
		||||
 * Usually occurs when a user tries to change a Donation's id.
 | 
			
		||||
 */
 | 
			
		||||
export class DonationIdsNotMatchingError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "DonationIdsNotMatchingError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The ids don't match! \n And if you wanted to change a Donation's id: This isn't allowed!"
 | 
			
		||||
}
 | 
			
		||||
@@ -33,4 +33,15 @@ export class DonorReceiptAddressNeededError extends NotAcceptableError {
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "An address is needed to create a receipt for a donor. \n You didn't provide one."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
* Error to throw when a donor still has donations associated.
 | 
			
		||||
*/
 | 
			
		||||
export class DonorHasDonationsError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "DonorHasDonationsError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "This donor still has donations associated with it. \n If you want to delete this donor with all it's donations and teams add `?force` to your query."
 | 
			
		||||
}
 | 
			
		||||
@@ -2,18 +2,7 @@ import { IsString } from 'class-validator';
 | 
			
		||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw, when a provided groupContact doesn't belong to the accepted types.
 | 
			
		||||
 */
 | 
			
		||||
export class GroupContactWrongTypeError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "GroupContactWrongTypeError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The groupContact must be an existing groupContact's id. \n You provided a object of another type."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw, when a non-existent groupContact get's loaded.
 | 
			
		||||
 * Error to throw, when a non-existent contact get's requested.
 | 
			
		||||
 */
 | 
			
		||||
export class GroupContactNotFoundError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
@@ -21,4 +10,16 @@ export class GroupContactNotFoundError extends NotFoundError {
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The groupContact you provided couldn't be located in the system. \n Please check your request."
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when two contacts' ids don't match.
 | 
			
		||||
 * Usually occurs when a user tries to change a contact's id.
 | 
			
		||||
 */
 | 
			
		||||
export class GroupContactIdsNotMatchingError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "GroupContactIdsNotMatchingError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The ids don't match! \n And if you wanted to change a contact's id: This isn't allowed!"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								src/errors/MailErrors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/errors/MailErrors.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
import { IsString } from 'class-validator'
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a permission couldn't be found.
 | 
			
		||||
 */
 | 
			
		||||
export class MailServerConfigError extends Error {
 | 
			
		||||
    @IsString()
 | 
			
		||||
    name = "MailServerConfigError"
 | 
			
		||||
 | 
			
		||||
    @IsString()
 | 
			
		||||
    message = "The SMTP server you provided couldn't be reached!"
 | 
			
		||||
}
 | 
			
		||||
@@ -32,5 +32,27 @@ export class RunnerGroupNeededError extends NotAcceptableError {
 | 
			
		||||
	name = "RunnerGroupNeededError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "Runner's need to be part of one group (team or organisation)! \n You provided neither."
 | 
			
		||||
	message = "Runner's need to be part of one group (team or organization)! \n You provided neither."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a citizen runner has no mail-address.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerEmailNeededError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "RunnerEmailNeededError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "Citizenrunners have to provide an email address for verification and contacting."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
* Error to throw when a runner still has distance donations associated.
 | 
			
		||||
*/
 | 
			
		||||
export class RunnerHasDistanceDonationsError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "RunnerHasDistanceDonationsError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "This runner still has distance donations associated with it. \n If you want to delete this runner with all it's donations and teams add `?force` to your query."
 | 
			
		||||
}
 | 
			
		||||
@@ -1,58 +0,0 @@
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a runner organisation couldn't be found.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerOrganisationNotFoundError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "RunnerOrganisationNotFoundError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "RunnerOrganisation not found!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when two runner organisation's ids don't match.
 | 
			
		||||
 * Usually occurs when a user tries to change a runner organisation's id.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerOrganisationIdsNotMatchingError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "RunnerOrganisationIdsNotMatchingError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The ids don't match! \n And if you wanted to change a runner organisation's id: This isn't allowed!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a organisation still has runners associated.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerOrganisationHasRunnersError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "RunnerOrganisationHasRunnersError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "This organisation still has runners associated with it. \n If you want to delete this organisation with all it's runners and teams add `?force` to your query."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a organisation still has teams associated.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerOrganisationHasTeamsError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "RunnerOrganisationHasTeamsError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "This organisation still has teams associated with it. \n If you want to delete this organisation with all it's runners and teams add `?force` to your query."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw, when a provided runnerOrganisation doesn't belong to the accepted types.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerOrganisationWrongTypeError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "RunnerOrganisationWrongTypeError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The runner organisation must be an existing organisation's id. \n You provided a object of another type."
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										58
									
								
								src/errors/RunnerOrganizationErrors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/errors/RunnerOrganizationErrors.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a runner organization couldn't be found.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerOrganizationNotFoundError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "RunnerOrganizationNotFoundError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "RunnerOrganization not found!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when two runner organization's ids don't match.
 | 
			
		||||
 * Usually occurs when a user tries to change a runner organization's id.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerOrganizationIdsNotMatchingError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "RunnerOrganizationIdsNotMatchingError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The ids don't match! \n And if you wanted to change a runner organization's id: This isn't allowed!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a organization still has runners associated.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerOrganizationHasRunnersError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "RunnerOrganizationHasRunnersError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "This organization still has runners associated with it. \n If you want to delete this organization with all it's runners and teams add `?force` to your query."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a organization still has teams associated.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerOrganizationHasTeamsError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "RunnerOrganizationHasTeamsError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "This organization still has teams associated with it. \n If you want to delete this organization with all it's runners and teams add `?force` to your query."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw, when a provided runnerOrganization doesn't belong to the accepted types.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerOrganizationWrongTypeError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "RunnerOrganizationWrongTypeError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The runner organization must be an existing organization's id. \n You provided a object of another type."
 | 
			
		||||
}
 | 
			
		||||
@@ -43,5 +43,5 @@ export class RunnerTeamNeedsParentError extends NotAcceptableError {
 | 
			
		||||
	name = "RunnerTeamNeedsParentError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "You provided no runner organisation as this team's parent group."
 | 
			
		||||
	message = "You provided no runner organization as this team's parent group."
 | 
			
		||||
}
 | 
			
		||||
@@ -4,7 +4,7 @@ import { NotAcceptableError, NotFoundError } from 'routing-controllers';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when no username or email is set.
 | 
			
		||||
 * We somehow need to identify you :)
 | 
			
		||||
 * We somehow need to identify you on login.
 | 
			
		||||
 */
 | 
			
		||||
export class UsernameOrEmailNeededError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
@@ -14,6 +14,30 @@ export class UsernameOrEmailNeededError extends NotFoundError {
 | 
			
		||||
	message = "No username or email is set!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when no username contains illegal characters.
 | 
			
		||||
 * Right now the only one is "@" but this could change in the future.
 | 
			
		||||
 */
 | 
			
		||||
export class UsernameContainsIllegalCharacterError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "UsernameContainsIllegalCharacterError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The provided username contains illegal characters! \n Right now the following characters are considered illegal: '@'"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when no email is set.
 | 
			
		||||
 * We somehow need to identify you :)
 | 
			
		||||
 */
 | 
			
		||||
export class UserEmailNeededError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "UserEmailNeededError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "No email is set! \n You have to provide email addresses for users (used for password reset among others)."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a user couldn't be found.
 | 
			
		||||
 */
 | 
			
		||||
@@ -35,4 +59,16 @@ export class UserIdsNotMatchingError extends NotAcceptableError {
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The ids don't match!! \n And if you wanted to change a user's id: This isn't allowed!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when two users' ids don't match.
 | 
			
		||||
 * Usually occurs when a user tries to change a user's id.
 | 
			
		||||
 */
 | 
			
		||||
export class UserDeletionNotConfirmedError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "UserDeletionNotConfirmedError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "You are trying to delete a user! \n If you're sure about doing this: provide the ?force=true query param."
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
 | 
			
		||||
import * as jsonwebtoken from "jsonwebtoken";
 | 
			
		||||
import { config } from './config';
 | 
			
		||||
import { Runner } from './models/entities/Runner';
 | 
			
		||||
import { User } from './models/entities/User';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -34,6 +35,19 @@ export class JwtCreator {
 | 
			
		||||
        }, config.jwt_secret)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new selfservice token for a given runner.
 | 
			
		||||
     * @param runner Runner entity that the access token shall be created for.
 | 
			
		||||
     * @param expiry_timestamp Timestamp for the token expiry. Will be set about 9999 years if none provided.
 | 
			
		||||
     */
 | 
			
		||||
    public static createSelfService(runner: Runner, expiry_timestamp?: number) {
 | 
			
		||||
        if (!expiry_timestamp) { expiry_timestamp = Math.floor(Date.now() / 1000) + 36000 * 60 * 24 * 365 * 9999; }
 | 
			
		||||
        return jsonwebtoken.sign({
 | 
			
		||||
            id: runner.id,
 | 
			
		||||
            exp: expiry_timestamp
 | 
			
		||||
        }, config.jwt_secret)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new password reset token for a given user.
 | 
			
		||||
     * The token is valid for 15 minutes or 1 use - whatever comes first.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,9 @@
 | 
			
		||||
import { createConnection } from "typeorm";
 | 
			
		||||
import { runSeeder } from 'typeorm-seeding';
 | 
			
		||||
import { User } from '../models/entities/User';
 | 
			
		||||
import { config } from '../config';
 | 
			
		||||
import { ConfigFlag } from '../models/entities/ConfigFlags';
 | 
			
		||||
import SeedPublicOrg from '../seeds/SeedPublicOrg';
 | 
			
		||||
import SeedTestRunners from '../seeds/SeedTestRunners';
 | 
			
		||||
import SeedUsers from '../seeds/SeedUsers';
 | 
			
		||||
/**
 | 
			
		||||
 * Loader for the database that creates the database connection and initializes the database tabels.
 | 
			
		||||
@@ -9,8 +12,20 @@ import SeedUsers from '../seeds/SeedUsers';
 | 
			
		||||
export default async () => {
 | 
			
		||||
    const connection = await createConnection();
 | 
			
		||||
    await connection.synchronize();
 | 
			
		||||
    if (await connection.getRepository(User).count() === 0) {
 | 
			
		||||
 | 
			
		||||
    //The data seeding part
 | 
			
		||||
    if (!(await connection.getRepository(ConfigFlag).findOne({ option: "seeded:user", value: "true" }))) {
 | 
			
		||||
        await runSeeder(SeedUsers);
 | 
			
		||||
        await connection.getRepository(ConfigFlag).save({ option: "seeded:user", value: "true" });
 | 
			
		||||
    }
 | 
			
		||||
    if (!(await connection.getRepository(ConfigFlag).findOne({ option: "seeded:citizenorg", value: "true" }))) {
 | 
			
		||||
        await runSeeder(SeedPublicOrg);
 | 
			
		||||
        await connection.getRepository(ConfigFlag).save({ option: "seeded:citizenorg", value: "true" });
 | 
			
		||||
    }
 | 
			
		||||
    if (!(await connection.getRepository(ConfigFlag).findOne({ option: "seeded:testdata", value: "true" })) && config.seedTestData == true) {
 | 
			
		||||
        await runSeeder(SeedTestRunners);
 | 
			
		||||
        await connection.getRepository(ConfigFlag).save({ option: "seeded:testdata", value: "true" });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return connection;
 | 
			
		||||
};
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { validationMetadatasToSchemas } from "class-validator-jsonschema";
 | 
			
		||||
import { validationMetadatasToSchemas } from "@odit/class-validator-jsonschema";
 | 
			
		||||
import express, { Application } from "express";
 | 
			
		||||
import path from 'path';
 | 
			
		||||
import { getMetadataArgsStorage } from "routing-controllers";
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										79
									
								
								src/mailer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/mailer.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
			
		||||
import fs from "fs";
 | 
			
		||||
import nodemailer from 'nodemailer';
 | 
			
		||||
import { MailOptions } from 'nodemailer/lib/json-transport';
 | 
			
		||||
import Mail from 'nodemailer/lib/mailer';
 | 
			
		||||
import { config } from './config';
 | 
			
		||||
import { MailServerConfigError } from './errors/MailErrors';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class is responsible for all things mail sending.
 | 
			
		||||
 * This uses the mail emplates from src/static/mail_templates
 | 
			
		||||
 */
 | 
			
		||||
export class Mailer {
 | 
			
		||||
    private transport: Mail;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The class's default constructor.
 | 
			
		||||
     * Creates the transporter and tests the connection.
 | 
			
		||||
     */
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.transport = nodemailer.createTransport({
 | 
			
		||||
            host: config.mail_server,
 | 
			
		||||
            port: config.mail_port,
 | 
			
		||||
            auth: {
 | 
			
		||||
                user: config.mail_user,
 | 
			
		||||
                pass: config.mail_password
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.transport.verify(function (error, success) {
 | 
			
		||||
            if (error) {
 | 
			
		||||
                throw new MailServerConfigError();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Function for sending a test mail from the test mail template.
 | 
			
		||||
     * @param to_address The address the mail will be sent to. Should always get pulled from a user object.
 | 
			
		||||
     * @param token The requested password reset token - will be combined with the app_url to generate a password reset link.
 | 
			
		||||
     */
 | 
			
		||||
    public async sendResetMail(to_address: string, token: string) {
 | 
			
		||||
        const reset_link = `${config.app_url}/reset/${(Buffer.from(token)).toString("base64")}`
 | 
			
		||||
        const body_html = fs.readFileSync(__dirname + '/static/mail_templates/pw-reset.html', { encoding: 'utf8' }).replace("{{reset_link}}", reset_link).replace("{{recipient_mail}}", to_address).replace("{{copyright_owner}}", "LfK!").replace("{{link_imprint}}", `${config.app_url}/imprint`).replace("{{link_privacy}}", `${config.app_url}/privacy`);
 | 
			
		||||
        const body_txt = fs.readFileSync(__dirname + '/static/mail_templates/pw-reset.html', { encoding: 'utf8' }).replace("{{reset_link}}", reset_link).replace("{{recipient_mail}}", to_address).replace("{{copyright_owner}}", "LfK!").replace("{{link_imprint}}", `${config.app_url}/imprint`).replace("{{link_privacy}}", `${config.app_url}/privacy`);
 | 
			
		||||
 | 
			
		||||
        const mail: MailOptions = {
 | 
			
		||||
            to: to_address,
 | 
			
		||||
            subject: "LfK! Password Reset",
 | 
			
		||||
            text: body_txt,
 | 
			
		||||
            html: body_html
 | 
			
		||||
        };
 | 
			
		||||
        await this.sendMail(mail);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Function for sending a test mail from the test mail template.
 | 
			
		||||
     * @param to_address The address the test mail will be sent to - this is the configured from-address by default.
 | 
			
		||||
     */
 | 
			
		||||
    public async sendTestMail(to_address: string = config.mail_from) {
 | 
			
		||||
        const body_html = fs.readFileSync(__dirname + '/static/mail_templates/test.html', { encoding: 'utf8' }).replace("{{recipient_mail}}", to_address).replace("{{copyright_owner}}", "LfK!").replace("{{link_imprint}}", `${config.app_url}/imprint`).replace("{{link_privacy}}", `${config.app_url}/privacy`);
 | 
			
		||||
        const body_txt = fs.readFileSync(__dirname + '/static/mail_templates/test.txt', { encoding: 'utf8' }).replace("{{recipient_mail}}", to_address).replace("{{copyright_owner}}", "LfK!").replace("{{link_imprint}}", `${config.app_url}/imprint`).replace("{{link_privacy}}", `${config.app_url}/privacy`);
 | 
			
		||||
        const mail: MailOptions = {
 | 
			
		||||
            to: to_address,
 | 
			
		||||
            subject: "LfK! Test Mail",
 | 
			
		||||
            text: body_txt,
 | 
			
		||||
            html: body_html
 | 
			
		||||
        };
 | 
			
		||||
        await this.sendMail(mail);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Wrapper function for sending a mail via this object's transporter.
 | 
			
		||||
     * @param mail MailOptions object containing the 
 | 
			
		||||
     */
 | 
			
		||||
    public async sendMail(mail: MailOptions) {
 | 
			
		||||
        mail.from = config.mail_from;
 | 
			
		||||
        await this.transport.sendMail(mail);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -42,7 +42,7 @@ const StatsAuth = async (req: Request, res: Response, next: () => void) => {
 | 
			
		||||
        let user_authorized = false;
 | 
			
		||||
        try {
 | 
			
		||||
            let action = { request: req, response: res, context: null, next: next }
 | 
			
		||||
            user_authorized = await authchecker(action, ["RUNNER:GET", "TEAM:GET", "ORGANISATION:GET"]);
 | 
			
		||||
            user_authorized = await authchecker(action, ["RUNNER:GET", "TEAM:GET", "ORGANIZATION:GET"]);
 | 
			
		||||
        }
 | 
			
		||||
        finally {
 | 
			
		||||
            if (user_authorized == false) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										58
									
								
								src/middlewares/UserChecker.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/middlewares/UserChecker.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
import cookie from "cookie";
 | 
			
		||||
import * as jwt from "jsonwebtoken";
 | 
			
		||||
import { Action } from 'routing-controllers';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { config } from '../config';
 | 
			
		||||
import { IllegalJWTError, UserDisabledError, UserNonexistantOrRefreshtokenInvalidError } from '../errors/AuthError';
 | 
			
		||||
import { JwtCreator, JwtUser } from '../jwtcreator';
 | 
			
		||||
import { User } from '../models/entities/User';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * TODO:
 | 
			
		||||
 */
 | 
			
		||||
const UserChecker = async (action: Action) => {
 | 
			
		||||
    let jwtPayload = undefined
 | 
			
		||||
    try {
 | 
			
		||||
        let provided_token = "" + action.request.headers["authorization"].replace("Bearer ", "");
 | 
			
		||||
        jwtPayload = <any>jwt.verify(provided_token, config.jwt_secret);
 | 
			
		||||
        jwtPayload = jwtPayload["userdetails"];
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        jwtPayload = await refresh(action);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const user = await getConnectionManager().get().getRepository(User).findOne({ id: jwtPayload["id"], refreshTokenCount: jwtPayload["refreshTokenCount"] })
 | 
			
		||||
    if (!user) { throw new UserNonexistantOrRefreshtokenInvalidError() }
 | 
			
		||||
    if (user.enabled == false) { throw new UserDisabledError(); }
 | 
			
		||||
    return user;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handles soft-refreshing of access-tokens.
 | 
			
		||||
 * @param action Routing-Controllers action object that provides request and response objects among other stuff.
 | 
			
		||||
 */
 | 
			
		||||
const refresh = async (action: Action) => {
 | 
			
		||||
    let refresh_token = undefined;
 | 
			
		||||
    try {
 | 
			
		||||
        refresh_token = cookie.parse(action.request.headers["cookie"])["lfk_backend__refresh_token"];
 | 
			
		||||
    }
 | 
			
		||||
    catch {
 | 
			
		||||
        throw new IllegalJWTError();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let jwtPayload = undefined;
 | 
			
		||||
    try {
 | 
			
		||||
        jwtPayload = <any>jwt.verify(refresh_token, config.jwt_secret);
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        throw new IllegalJWTError();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const user = await getConnectionManager().get().getRepository(User).findOne({ id: jwtPayload["id"], refreshTokenCount: jwtPayload["refreshTokenCount"] }, { relations: ['permissions', 'groups', 'groups.permissions'] })
 | 
			
		||||
    if (!user) { throw new UserNonexistantOrRefreshtokenInvalidError() }
 | 
			
		||||
    if (user.enabled == false) { throw new UserDisabledError(); }
 | 
			
		||||
 | 
			
		||||
    let newAccess = JwtCreator.createAccess(user);
 | 
			
		||||
    action.response.header("authorization", "Bearer " + newAccess);
 | 
			
		||||
 | 
			
		||||
    return await new JwtUser(user);
 | 
			
		||||
}
 | 
			
		||||
export default UserChecker;
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { RunnerGroupNeededError } from '../../errors/RunnerErrors';
 | 
			
		||||
import { RunnerOrganisationNotFoundError } from '../../errors/RunnerOrganisationErrors';
 | 
			
		||||
import { RunnerOrganizationNotFoundError } from '../../errors/RunnerOrganizationErrors';
 | 
			
		||||
import { RunnerGroup } from '../entities/RunnerGroup';
 | 
			
		||||
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
 | 
			
		||||
import { RunnerOrganization } from '../entities/RunnerOrganization';
 | 
			
		||||
import { RunnerTeam } from '../entities/RunnerTeam';
 | 
			
		||||
import { CreateRunner } from './create/CreateRunner';
 | 
			
		||||
 | 
			
		||||
@@ -78,9 +78,9 @@ export class ImportRunner {
 | 
			
		||||
        let team = await getConnectionManager().get().getRepository(RunnerTeam).findOne({ id: groupID });
 | 
			
		||||
        if (team) { return team; }
 | 
			
		||||
 | 
			
		||||
        let org = await getConnectionManager().get().getRepository(RunnerOrganisation).findOne({ id: groupID });
 | 
			
		||||
        let org = await getConnectionManager().get().getRepository(RunnerOrganization).findOne({ id: groupID });
 | 
			
		||||
        if (!org) {
 | 
			
		||||
            throw new RunnerOrganisationNotFoundError();
 | 
			
		||||
            throw new RunnerOrganizationNotFoundError();
 | 
			
		||||
        }
 | 
			
		||||
        if (this.team === undefined) { return org; }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,70 +0,0 @@
 | 
			
		||||
import { IsNotEmpty, IsOptional, IsPostalCode, IsString } from 'class-validator';
 | 
			
		||||
import { config } from '../../../config';
 | 
			
		||||
import { Address } from '../../entities/Address';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This classed is used to create a new Address entity from a json body (post request).
 | 
			
		||||
 */
 | 
			
		||||
export class CreateAddress {
 | 
			
		||||
    /**
 | 
			
		||||
     * The newaddress's description.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    description?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new address's first line.
 | 
			
		||||
     * Containing the street and house number.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    address1: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new address's second line.
 | 
			
		||||
     * Containing optional information.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    address2?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new address's postal code.
 | 
			
		||||
     * This will get checked against the postal code syntax for the configured country.
 | 
			
		||||
     * TODO: Implement the config option. 
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    @IsPostalCode(config.postalcode_validation_countrycode)
 | 
			
		||||
    postalcode: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new address's city.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    city: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new address's country.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    country: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new Address entity from this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toEntity(): Promise<Address> {
 | 
			
		||||
        let newAddress: Address = new Address();
 | 
			
		||||
 | 
			
		||||
        newAddress.address1 = this.address1;
 | 
			
		||||
        newAddress.address2 = this.address2;
 | 
			
		||||
        newAddress.postalcode = this.postalcode;
 | 
			
		||||
        newAddress.city = this.city;
 | 
			
		||||
        newAddress.country = this.country;
 | 
			
		||||
 | 
			
		||||
        return newAddress;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										52
									
								
								src/models/actions/create/CreateDistanceDonation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/models/actions/create/CreateDistanceDonation.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
import { IsInt, IsPositive } from 'class-validator';
 | 
			
		||||
import { getConnection } from 'typeorm';
 | 
			
		||||
import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
 | 
			
		||||
import { DistanceDonation } from '../../entities/DistanceDonation';
 | 
			
		||||
import { Runner } from '../../entities/Runner';
 | 
			
		||||
import { CreateDonation } from './CreateDonation';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class is used to create a new FixedDonation entity from a json body (post request).
 | 
			
		||||
 */
 | 
			
		||||
export class CreateDistanceDonation extends CreateDonation {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The donation's associated runner's id.
 | 
			
		||||
     * This is important to link the runner's distance ran to the donation.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsPositive()
 | 
			
		||||
    runner: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The donation's amount per distance (full kilometer aka 1000 meters).
 | 
			
		||||
     * The unit is your currency's smallest unit (default: euro cent).
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsPositive()
 | 
			
		||||
    amountPerDistance: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new FixedDonation entity from this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toEntity(): Promise<DistanceDonation> {
 | 
			
		||||
        let newDonation = new DistanceDonation;
 | 
			
		||||
 | 
			
		||||
        newDonation.amountPerDistance = this.amountPerDistance;
 | 
			
		||||
        newDonation.donor = await this.getDonor();
 | 
			
		||||
        newDonation.runner = await this.getRunner();
 | 
			
		||||
 | 
			
		||||
        return newDonation;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets a runner based on the runner id provided via this.runner.
 | 
			
		||||
     */
 | 
			
		||||
    public async getRunner(): Promise<Runner> {
 | 
			
		||||
        const runner = await getConnection().getRepository(Runner).findOne({ id: this.runner });
 | 
			
		||||
        if (!runner) {
 | 
			
		||||
            throw new RunnerNotFoundError();
 | 
			
		||||
        }
 | 
			
		||||
        return runner;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								src/models/actions/create/CreateDonation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/models/actions/create/CreateDonation.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
import { IsInt, IsPositive } from 'class-validator';
 | 
			
		||||
import { getConnection } from 'typeorm';
 | 
			
		||||
import { DonorNotFoundError } from '../../../errors/DonorErrors';
 | 
			
		||||
import { Donation } from '../../entities/Donation';
 | 
			
		||||
import { Donor } from '../../entities/Donor';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class is used to create a new Donation entity from a json body (post request).
 | 
			
		||||
 */
 | 
			
		||||
export abstract class CreateDonation {
 | 
			
		||||
    /**
 | 
			
		||||
     * The donation's associated donor's id.
 | 
			
		||||
     * This is important to link donations to donors.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsPositive()
 | 
			
		||||
    donor: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new Donation entity from this.
 | 
			
		||||
     */
 | 
			
		||||
    public abstract toEntity(): Promise<Donation>;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets a donor based on the donor id provided via this.donor.
 | 
			
		||||
     */
 | 
			
		||||
    public async getDonor(): Promise<Donor> {
 | 
			
		||||
        const donor = await getConnection().getRepository(Donor).findOne({ id: this.donor });
 | 
			
		||||
        if (!donor) {
 | 
			
		||||
            throw new DonorNotFoundError();
 | 
			
		||||
        }
 | 
			
		||||
        return donor;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import { IsBoolean, IsOptional } from 'class-validator';
 | 
			
		||||
import { DonorReceiptAddressNeededError } from '../../../errors/DonorErrors';
 | 
			
		||||
import { Address } from '../../entities/Address';
 | 
			
		||||
import { Donor } from '../../entities/Donor';
 | 
			
		||||
import { CreateParticipant } from './CreateParticipant';
 | 
			
		||||
 | 
			
		||||
@@ -26,10 +27,10 @@ export class CreateDonor extends CreateParticipant {
 | 
			
		||||
        newDonor.lastname = this.lastname;
 | 
			
		||||
        newDonor.phone = this.phone;
 | 
			
		||||
        newDonor.email = this.email;
 | 
			
		||||
        newDonor.address = await this.getAddress();
 | 
			
		||||
        newDonor.receiptNeeded = this.receiptNeeded;
 | 
			
		||||
 | 
			
		||||
        if (this.receiptNeeded == true && this.address == null) {
 | 
			
		||||
        newDonor.address = this.address;
 | 
			
		||||
        Address.validate(newDonor.address);
 | 
			
		||||
        if (this.receiptNeeded == true && Address.isValidAddress(newDonor.address) == false) {
 | 
			
		||||
            throw new DonorReceiptAddressNeededError()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										28
									
								
								src/models/actions/create/CreateFixedDonation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/models/actions/create/CreateFixedDonation.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
import { IsInt, IsPositive } from 'class-validator';
 | 
			
		||||
import { FixedDonation } from '../../entities/FixedDonation';
 | 
			
		||||
import { CreateDonation } from './CreateDonation';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class is used to create a new FixedDonation entity from a json body (post request).
 | 
			
		||||
 */
 | 
			
		||||
export class CreateFixedDonation extends CreateDonation {
 | 
			
		||||
    /**
 | 
			
		||||
     * The donation's amount.
 | 
			
		||||
     * The unit is your currency's smallest unit (default: euro cent).
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsPositive()
 | 
			
		||||
    amount: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new FixedDonation entity from this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toEntity(): Promise<FixedDonation> {
 | 
			
		||||
        let newDonation = new FixedDonation;
 | 
			
		||||
 | 
			
		||||
        newDonation.amount = this.amount;
 | 
			
		||||
        newDonation.donor = await this.getDonor();
 | 
			
		||||
 | 
			
		||||
        return newDonation;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +1,13 @@
 | 
			
		||||
import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
 | 
			
		||||
import { IsEmail, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { config } from '../../../config';
 | 
			
		||||
import { AddressNotFoundError, AddressWrongTypeError } from '../../../errors/AddressErrors';
 | 
			
		||||
import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
 | 
			
		||||
import { Address } from '../../entities/Address';
 | 
			
		||||
import { GroupContact } from '../../entities/GroupContact';
 | 
			
		||||
import { RunnerGroup } from '../../entities/RunnerGroup';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This classed is used to create a new Group entity from a json body (post request).
 | 
			
		||||
 * This classed is used to create a new GroupContact entity from a json body (post request).
 | 
			
		||||
 */
 | 
			
		||||
export class CreateGroupContact {
 | 
			
		||||
    /**
 | 
			
		||||
@@ -32,11 +33,10 @@ export class CreateGroupContact {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new contact's address.
 | 
			
		||||
     * Must be the address's id.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    address?: number;
 | 
			
		||||
    @IsObject()
 | 
			
		||||
    address?: Address;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The contact's phone number.
 | 
			
		||||
@@ -47,39 +47,51 @@ export class CreateGroupContact {
 | 
			
		||||
    phone?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The contact's email address.
 | 
			
		||||
     * The new contact's email address.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsEmail()
 | 
			
		||||
    email?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the new contact's address by it's id.
 | 
			
		||||
     * The new contacts's groups' ids.
 | 
			
		||||
     * You can provide either one groupId or an array of groupIDs.
 | 
			
		||||
     */
 | 
			
		||||
    public async getAddress(): Promise<Address> {
 | 
			
		||||
        if (this.address === undefined || this.address === null) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.address)) {
 | 
			
		||||
            let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
 | 
			
		||||
            if (!address) { throw new AddressNotFoundError; }
 | 
			
		||||
            return address;
 | 
			
		||||
        }
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    groups?: number[] | number
 | 
			
		||||
 | 
			
		||||
        throw new AddressWrongTypeError;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get's all groups for this contact by their id's;
 | 
			
		||||
     */
 | 
			
		||||
    public async getGroups(): Promise<RunnerGroup[]> {
 | 
			
		||||
        if (!this.groups) { return null; }
 | 
			
		||||
        let groups = new Array<RunnerGroup>();
 | 
			
		||||
        if (!Array.isArray(this.groups)) {
 | 
			
		||||
            this.groups = [this.groups]
 | 
			
		||||
        }
 | 
			
		||||
        for (let group of this.groups) {
 | 
			
		||||
            let found = await getConnectionManager().get().getRepository(RunnerGroup).findOne({ id: group });
 | 
			
		||||
            if (!found) { throw new RunnerGroupNotFoundError(); }
 | 
			
		||||
            groups.push(found);
 | 
			
		||||
        }
 | 
			
		||||
        return groups;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new Address entity from this.
 | 
			
		||||
     * Creates a new GroupContact entity from this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toEntity(): Promise<GroupContact> {
 | 
			
		||||
        let contact: GroupContact = new GroupContact();
 | 
			
		||||
        contact.firstname = this.firstname;
 | 
			
		||||
        contact.middlename = this.middlename;
 | 
			
		||||
        contact.lastname = this.lastname;
 | 
			
		||||
        contact.email = this.email;
 | 
			
		||||
        contact.phone = this.phone;
 | 
			
		||||
        contact.address = await this.getAddress();
 | 
			
		||||
        return null;
 | 
			
		||||
        let newContact: GroupContact = new GroupContact();
 | 
			
		||||
        newContact.firstname = this.firstname;
 | 
			
		||||
        newContact.middlename = this.middlename;
 | 
			
		||||
        newContact.lastname = this.lastname;
 | 
			
		||||
        newContact.email = this.email;
 | 
			
		||||
        newContact.phone = this.phone;
 | 
			
		||||
        newContact.address = this.address;
 | 
			
		||||
        Address.validate(newContact.address);
 | 
			
		||||
        newContact.groups = await this.getGroups();
 | 
			
		||||
 | 
			
		||||
        return newContact;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +1,5 @@
 | 
			
		||||
import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { IsEmail, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
 | 
			
		||||
import { config } from '../../../config';
 | 
			
		||||
import { AddressNotFoundError, AddressWrongTypeError } from '../../../errors/AddressErrors';
 | 
			
		||||
import { Address } from '../../entities/Address';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -48,25 +46,8 @@ export abstract class CreateParticipant {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new participant's address.
 | 
			
		||||
     * Must be of type number (address id).
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    address?: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the new participant's address by it's address.
 | 
			
		||||
     */
 | 
			
		||||
    public async getAddress(): Promise<Address> {
 | 
			
		||||
        if (this.address === undefined || this.address === null) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.address)) {
 | 
			
		||||
            let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
 | 
			
		||||
            if (!address) { throw new AddressNotFoundError; }
 | 
			
		||||
            return address;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new AddressWrongTypeError;
 | 
			
		||||
    }
 | 
			
		||||
    @IsObject()
 | 
			
		||||
    address?: Address;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,39 +1,33 @@
 | 
			
		||||
import { IsEmail, IsOptional, IsString } from 'class-validator';
 | 
			
		||||
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { ResetAlreadyRequestedError, UserDisabledError, UserNotFoundError } from '../../../errors/AuthError';
 | 
			
		||||
import { UsernameOrEmailNeededError } from '../../../errors/UserErrors';
 | 
			
		||||
import { UserEmailNeededError } from '../../../errors/UserErrors';
 | 
			
		||||
import { JwtCreator } from '../../../jwtcreator';
 | 
			
		||||
import { User } from '../../entities/User';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This calss is used to create password reset tokens for users.
 | 
			
		||||
 * This class is used to create password reset tokens for users.
 | 
			
		||||
 * These password reset token can be used to set a new password for the user for the next 15mins.
 | 
			
		||||
 */
 | 
			
		||||
export class CreateResetToken {
 | 
			
		||||
    /**
 | 
			
		||||
     * The username of the user that wants to reset their password.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    username?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The email address of the user that wants to reset their password.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    @IsEmail()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    email?: string;
 | 
			
		||||
    email: string;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a password reset token based on this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toResetToken(): Promise<any> {
 | 
			
		||||
        if (this.email === undefined && this.username === undefined) {
 | 
			
		||||
            throw new UsernameOrEmailNeededError();
 | 
			
		||||
    public async toResetToken(): Promise<string> {
 | 
			
		||||
        if (!this.email) {
 | 
			
		||||
            throw new UserEmailNeededError();
 | 
			
		||||
        }
 | 
			
		||||
        let found_user = await getConnectionManager().get().getRepository(User).findOne({ where: [{ username: this.username }, { email: this.email }] });
 | 
			
		||||
        let found_user = await getConnectionManager().get().getRepository(User).findOne({ where: [{ email: this.email }] });
 | 
			
		||||
        if (!found_user) { throw new UserNotFoundError(); }
 | 
			
		||||
        if (found_user.enabled == false) { throw new UserDisabledError(); }
 | 
			
		||||
        if (found_user.resetRequestedTimestamp > (Math.floor(Date.now() / 1000) - 15 * 60)) { throw new ResetAlreadyRequestedError(); }
 | 
			
		||||
@@ -43,7 +37,7 @@ export class CreateResetToken {
 | 
			
		||||
        await getConnectionManager().get().getRepository(User).save(found_user);
 | 
			
		||||
 | 
			
		||||
        //Create the reset token
 | 
			
		||||
        let reset_token = JwtCreator.createReset(found_user);
 | 
			
		||||
        let reset_token: string = JwtCreator.createReset(found_user);
 | 
			
		||||
 | 
			
		||||
        return reset_token;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,9 @@
 | 
			
		||||
import { IsInt } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
 | 
			
		||||
import { RunnerOrganisationWrongTypeError } from '../../../errors/RunnerOrganisationErrors';
 | 
			
		||||
import { RunnerOrganizationWrongTypeError } from '../../../errors/RunnerOrganizationErrors';
 | 
			
		||||
import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors';
 | 
			
		||||
import { Address } from '../../entities/Address';
 | 
			
		||||
import { Runner } from '../../entities/Runner';
 | 
			
		||||
import { RunnerGroup } from '../../entities/RunnerGroup';
 | 
			
		||||
import { CreateParticipant } from './CreateParticipant';
 | 
			
		||||
@@ -30,7 +31,8 @@ export class CreateRunner extends CreateParticipant {
 | 
			
		||||
        newRunner.phone = this.phone;
 | 
			
		||||
        newRunner.email = this.email;
 | 
			
		||||
        newRunner.group = await this.getGroup();
 | 
			
		||||
        newRunner.address = await this.getAddress();
 | 
			
		||||
        newRunner.address = this.address;
 | 
			
		||||
        Address.validate(newRunner.address);
 | 
			
		||||
 | 
			
		||||
        return newRunner;
 | 
			
		||||
    }
 | 
			
		||||
@@ -48,6 +50,6 @@ export class CreateRunner extends CreateParticipant {
 | 
			
		||||
            return group;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new RunnerOrganisationWrongTypeError;
 | 
			
		||||
        throw new RunnerOrganizationWrongTypeError;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -9,7 +9,7 @@ import { RunnerCard } from '../../entities/RunnerCard';
 | 
			
		||||
 */
 | 
			
		||||
export class CreateRunnerCard {
 | 
			
		||||
    /**
 | 
			
		||||
     * The card's associated runner.
 | 
			
		||||
     * The card's associated runner's id.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import { IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { GroupContactNotFoundError, GroupContactWrongTypeError } from '../../../errors/GroupContactErrors';
 | 
			
		||||
import { GroupContactNotFoundError } from '../../../errors/GroupContactErrors';
 | 
			
		||||
import { GroupContact } from '../../entities/GroupContact';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -15,7 +15,7 @@ export abstract class CreateRunnerGroup {
 | 
			
		||||
    name: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new group's contact.
 | 
			
		||||
     * The new group's contact's id.
 | 
			
		||||
     * Optional
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
@@ -26,15 +26,10 @@ export abstract class CreateRunnerGroup {
 | 
			
		||||
     * Gets the new group's contact by it's id.
 | 
			
		||||
     */
 | 
			
		||||
    public async getContact(): Promise<GroupContact> {
 | 
			
		||||
        if (this.contact === undefined || this.contact === null) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.contact)) {
 | 
			
		||||
            let contact = await getConnectionManager().get().getRepository(GroupContact).findOne({ id: this.contact });
 | 
			
		||||
            if (!contact) { throw new GroupContactNotFoundError; }
 | 
			
		||||
            return contact;
 | 
			
		||||
        }
 | 
			
		||||
        if (!this.contact) { return null; }
 | 
			
		||||
        let contact = await getConnectionManager().get().getRepository(GroupContact).findOne({ id: this.contact });
 | 
			
		||||
        if (!contact) { throw new GroupContactNotFoundError; }
 | 
			
		||||
        return contact;
 | 
			
		||||
 | 
			
		||||
        throw new GroupContactWrongTypeError;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,48 +0,0 @@
 | 
			
		||||
import { IsInt, IsOptional } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { AddressNotFoundError, AddressWrongTypeError } from '../../../errors/AddressErrors';
 | 
			
		||||
import { Address } from '../../entities/Address';
 | 
			
		||||
import { RunnerOrganisation } from '../../entities/RunnerOrganisation';
 | 
			
		||||
import { CreateRunnerGroup } from './CreateRunnerGroup';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This classed is used to create a new RunnerOrganisation entity from a json body (post request).
 | 
			
		||||
 */
 | 
			
		||||
export class CreateRunnerOrganisation extends CreateRunnerGroup {
 | 
			
		||||
    /**
 | 
			
		||||
     * The new organisation's address.
 | 
			
		||||
     * Must be of type number (address id).
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    address?: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the org's address by it's id.
 | 
			
		||||
     */
 | 
			
		||||
    public async getAddress(): Promise<Address> {
 | 
			
		||||
        if (this.address === undefined || this.address === null) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.address)) {
 | 
			
		||||
            let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
 | 
			
		||||
            if (!address) { throw new AddressNotFoundError; }
 | 
			
		||||
            return address;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new AddressWrongTypeError;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new RunnerOrganisation entity from this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toEntity(): Promise<RunnerOrganisation> {
 | 
			
		||||
        let newRunnerOrganisation: RunnerOrganisation = new RunnerOrganisation();
 | 
			
		||||
 | 
			
		||||
        newRunnerOrganisation.name = this.name;
 | 
			
		||||
        newRunnerOrganisation.contact = await this.getContact();
 | 
			
		||||
        // newRunnerOrganisation.address = await this.getAddress();
 | 
			
		||||
 | 
			
		||||
        return newRunnerOrganisation;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										43
									
								
								src/models/actions/create/CreateRunnerOrganization.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/models/actions/create/CreateRunnerOrganization.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
import { IsBoolean, IsObject, IsOptional } from 'class-validator';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import { Address } from '../../entities/Address';
 | 
			
		||||
import { RunnerOrganization } from '../../entities/RunnerOrganization';
 | 
			
		||||
import { CreateRunnerGroup } from './CreateRunnerGroup';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This classed is used to create a new RunnerOrganization entity from a json body (post request).
 | 
			
		||||
 */
 | 
			
		||||
export class CreateRunnerOrganization extends CreateRunnerGroup {
 | 
			
		||||
    /**
 | 
			
		||||
     * The new organization's address.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsObject()
 | 
			
		||||
    address?: Address;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Is registration enabled for the new organization?
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsBoolean()
 | 
			
		||||
    registrationEnabled?: boolean = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new RunnerOrganization entity from this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toEntity(): Promise<RunnerOrganization> {
 | 
			
		||||
        let newRunnerOrganization: RunnerOrganization = new RunnerOrganization();
 | 
			
		||||
 | 
			
		||||
        newRunnerOrganization.name = this.name;
 | 
			
		||||
        newRunnerOrganization.contact = await this.getContact();
 | 
			
		||||
        newRunnerOrganization.address = this.address;
 | 
			
		||||
        Address.validate(newRunnerOrganization.address);
 | 
			
		||||
 | 
			
		||||
        if (this.registrationEnabled) {
 | 
			
		||||
            newRunnerOrganization.key = uuid.v4().toUpperCase();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return newRunnerOrganization;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
import { IsInt, IsNotEmpty } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { RunnerOrganisationNotFoundError, RunnerOrganisationWrongTypeError } from '../../../errors/RunnerOrganisationErrors';
 | 
			
		||||
import { RunnerOrganizationNotFoundError } from '../../../errors/RunnerOrganizationErrors';
 | 
			
		||||
import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors';
 | 
			
		||||
import { RunnerOrganisation } from '../../entities/RunnerOrganisation';
 | 
			
		||||
import { RunnerOrganization } from '../../entities/RunnerOrganization';
 | 
			
		||||
import { RunnerTeam } from '../../entities/RunnerTeam';
 | 
			
		||||
import { CreateRunnerGroup } from './CreateRunnerGroup';
 | 
			
		||||
 | 
			
		||||
@@ -12,7 +12,7 @@ import { CreateRunnerGroup } from './CreateRunnerGroup';
 | 
			
		||||
export class CreateRunnerTeam extends CreateRunnerGroup {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new team's parent group (organisation).
 | 
			
		||||
     * The new team's parent org's id.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
@@ -21,17 +21,13 @@ export class CreateRunnerTeam extends CreateRunnerGroup {
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the new team's parent org based on it's id.
 | 
			
		||||
     */
 | 
			
		||||
    public async getParent(): Promise<RunnerOrganisation> {
 | 
			
		||||
    public async getParent(): Promise<RunnerOrganization> {
 | 
			
		||||
        if (this.parentGroup === undefined || this.parentGroup === null) {
 | 
			
		||||
            throw new RunnerTeamNeedsParentError();
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.parentGroup)) {
 | 
			
		||||
            let parentGroup = await getConnectionManager().get().getRepository(RunnerOrganisation).findOne({ id: this.parentGroup });
 | 
			
		||||
            if (!parentGroup) { throw new RunnerOrganisationNotFoundError();; }
 | 
			
		||||
            return parentGroup;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new RunnerOrganisationWrongTypeError;
 | 
			
		||||
        let parentGroup = await getConnectionManager().get().getRepository(RunnerOrganization).findOne({ id: this.parentGroup });
 | 
			
		||||
        if (!parentGroup) { throw new RunnerOrganizationNotFoundError();; }
 | 
			
		||||
        return parentGroup;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -42,7 +38,6 @@ export class CreateRunnerTeam extends CreateRunnerGroup {
 | 
			
		||||
 | 
			
		||||
        newRunnerTeam.name = this.name;
 | 
			
		||||
        newRunnerTeam.parentGroup = await this.getParent();
 | 
			
		||||
 | 
			
		||||
        newRunnerTeam.contact = await this.getContact()
 | 
			
		||||
 | 
			
		||||
        return newRunnerTeam;
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ import { Scan } from '../../entities/Scan';
 | 
			
		||||
 */
 | 
			
		||||
export abstract class CreateScan {
 | 
			
		||||
    /**
 | 
			
		||||
     * The scan's associated runner.
 | 
			
		||||
     * The scan's associated runner's id.
 | 
			
		||||
     * This is important to link ran distances to runners.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ export class CreateScanStation {
 | 
			
		||||
    description?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The station's associated track.
 | 
			
		||||
     * The station's associated track's id.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsPositive()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										52
									
								
								src/models/actions/create/CreateSelfServiceCitizenRunner.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/models/actions/create/CreateSelfServiceCitizenRunner.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
 | 
			
		||||
import { getConnection } from 'typeorm';
 | 
			
		||||
import { RunnerEmailNeededError } from '../../../errors/RunnerErrors';
 | 
			
		||||
import { Address } from '../../entities/Address';
 | 
			
		||||
import { Runner } from '../../entities/Runner';
 | 
			
		||||
import { RunnerOrganization } from '../../entities/RunnerOrganization';
 | 
			
		||||
import { CreateParticipant } from './CreateParticipant';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This classed is used to create a new Runner entity from a json body (post request).
 | 
			
		||||
 */
 | 
			
		||||
export class CreateSelfServiceCitizenRunner extends CreateParticipant {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new runners's e-mail address.
 | 
			
		||||
     * Must be provided for email-verification to work.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    @IsEmail()
 | 
			
		||||
    email: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new Runner entity from this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toEntity(): Promise<Runner> {
 | 
			
		||||
        let newRunner: Runner = new Runner();
 | 
			
		||||
 | 
			
		||||
        newRunner.firstname = this.firstname;
 | 
			
		||||
        newRunner.middlename = this.middlename;
 | 
			
		||||
        newRunner.lastname = this.lastname;
 | 
			
		||||
        newRunner.phone = this.phone;
 | 
			
		||||
        newRunner.email = this.email;
 | 
			
		||||
 | 
			
		||||
        if (!newRunner.email) {
 | 
			
		||||
            throw new RunnerEmailNeededError();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        newRunner.group = await this.getGroup();
 | 
			
		||||
        newRunner.address = this.address;
 | 
			
		||||
        Address.validate(newRunner.address);
 | 
			
		||||
 | 
			
		||||
        return newRunner;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the new runner's group by it's id.
 | 
			
		||||
     */
 | 
			
		||||
    public async getGroup(): Promise<RunnerOrganization> {
 | 
			
		||||
        return await getConnection().getRepository(RunnerOrganization).findOne({ id: 1 });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										55
									
								
								src/models/actions/create/CreateSelfServiceRunner.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/models/actions/create/CreateSelfServiceRunner.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
import { IsInt, IsOptional } from 'class-validator';
 | 
			
		||||
import { getConnection } from 'typeorm';
 | 
			
		||||
import { RunnerTeamNotFoundError } from '../../../errors/RunnerTeamErrors';
 | 
			
		||||
import { Address } from '../../entities/Address';
 | 
			
		||||
import { Runner } from '../../entities/Runner';
 | 
			
		||||
import { RunnerGroup } from '../../entities/RunnerGroup';
 | 
			
		||||
import { RunnerTeam } from '../../entities/RunnerTeam';
 | 
			
		||||
import { CreateParticipant } from './CreateParticipant';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This classed is used to create a new Runner entity from a json body (post request).
 | 
			
		||||
 */
 | 
			
		||||
export class CreateSelfServiceRunner extends CreateParticipant {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new runner's team's id.
 | 
			
		||||
     * The team has to be a part of the runner's org.
 | 
			
		||||
     * The team property may get ignored.
 | 
			
		||||
     * If no team get's provided the runner's group will be their org.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    team?: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new Runner entity from this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toEntity(group: RunnerGroup): Promise<Runner> {
 | 
			
		||||
        let newRunner: Runner = new Runner();
 | 
			
		||||
 | 
			
		||||
        newRunner.firstname = this.firstname;
 | 
			
		||||
        newRunner.middlename = this.middlename;
 | 
			
		||||
        newRunner.lastname = this.lastname;
 | 
			
		||||
        newRunner.phone = this.phone;
 | 
			
		||||
        newRunner.email = this.email;
 | 
			
		||||
        newRunner.group = await this.getGroup(group);
 | 
			
		||||
        newRunner.address = this.address;
 | 
			
		||||
        Address.validate(newRunner.address);
 | 
			
		||||
 | 
			
		||||
        return newRunner;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the new runner's group by it's id.
 | 
			
		||||
     */
 | 
			
		||||
    public async getGroup(group: RunnerGroup): Promise<RunnerGroup> {
 | 
			
		||||
        if (!this.team) {
 | 
			
		||||
            return group;
 | 
			
		||||
        }
 | 
			
		||||
        const team = await getConnection().getRepository(RunnerTeam).findOne({ id: this.team }, { relations: ["parentGroup"] });
 | 
			
		||||
        if (!team) { throw new RunnerTeamNotFoundError(); }
 | 
			
		||||
        if (team.parentGroup.id != group.id) { throw new RunnerTeamNotFoundError(); }
 | 
			
		||||
        return team;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -12,7 +12,7 @@ import { TrackScan } from '../../entities/TrackScan';
 | 
			
		||||
 */
 | 
			
		||||
export class CreateTrackScan {
 | 
			
		||||
    /**
 | 
			
		||||
     * The runnerCard associated with the scan.
 | 
			
		||||
     * The id of the runnerCard associated with the scan.
 | 
			
		||||
     * This get's saved for documentation and management purposes.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
@@ -20,8 +20,8 @@ export class CreateTrackScan {
 | 
			
		||||
    card: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The scanning station that created the scan.
 | 
			
		||||
     * Mainly used for logging and traceing back scans (or errors)
 | 
			
		||||
     * The scanning station's id that created the scan.
 | 
			
		||||
     * Mainly used for logging and traceing back scans (or errors).
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsPositive()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
import * as argon2 from "argon2";
 | 
			
		||||
import { IsBoolean, IsEmail, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator';
 | 
			
		||||
import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import { config } from '../../../config';
 | 
			
		||||
import { UsernameOrEmailNeededError } from '../../../errors/UserErrors';
 | 
			
		||||
import { UserEmailNeededError, UsernameContainsIllegalCharacterError } from '../../../errors/UserErrors';
 | 
			
		||||
import { UserGroupNotFoundError } from '../../../errors/UserGroupErrors';
 | 
			
		||||
import { User } from '../../entities/User';
 | 
			
		||||
import { UserGroup } from '../../entities/UserGroup';
 | 
			
		||||
@@ -33,7 +33,7 @@ export class CreateUser {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's username.
 | 
			
		||||
     * You have to provide at least one of: {email, username}.
 | 
			
		||||
     * You have to provide a email addres, so this is optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsString()
 | 
			
		||||
@@ -41,12 +41,11 @@ export class CreateUser {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's email address.
 | 
			
		||||
     * You have to provide at least one of: {email, username}.
 | 
			
		||||
     */
 | 
			
		||||
    @IsEmail()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    email?: string;
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    email: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's phone number.
 | 
			
		||||
@@ -72,7 +71,7 @@ export class CreateUser {
 | 
			
		||||
    enabled?: boolean = true;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's groups' id(s).
 | 
			
		||||
     * The new user's groups' ids.
 | 
			
		||||
     * You can provide either one groupId or an array of groupIDs.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
@@ -92,9 +91,10 @@ export class CreateUser {
 | 
			
		||||
    public async toEntity(): Promise<User> {
 | 
			
		||||
        let newUser: User = new User();
 | 
			
		||||
 | 
			
		||||
        if (this.email === undefined && this.username === undefined) {
 | 
			
		||||
            throw new UsernameOrEmailNeededError();
 | 
			
		||||
        if (!this.email) {
 | 
			
		||||
            throw new UserEmailNeededError();
 | 
			
		||||
        }
 | 
			
		||||
        if (this.username.includes("@")) { throw new UsernameContainsIllegalCharacterError(); }
 | 
			
		||||
 | 
			
		||||
        newUser.email = this.email
 | 
			
		||||
        newUser.username = this.username
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										51
									
								
								src/models/actions/update/UpdateDistanceDonation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/models/actions/update/UpdateDistanceDonation.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
import { IsInt, IsPositive } from 'class-validator';
 | 
			
		||||
import { getConnection } from 'typeorm';
 | 
			
		||||
import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
 | 
			
		||||
import { DistanceDonation } from '../../entities/DistanceDonation';
 | 
			
		||||
import { Runner } from '../../entities/Runner';
 | 
			
		||||
import { UpdateDonation } from './UpdateDonation';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class is used to update a DistanceDonation entity (via put request).
 | 
			
		||||
 */
 | 
			
		||||
export class UpdateDistanceDonation extends UpdateDonation {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The donation's associated runner's id.
 | 
			
		||||
     * This is important to link the runner's distance ran to the donation.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsPositive()
 | 
			
		||||
    runner: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The donation's amount per distance (full kilometer aka 1000 meters).
 | 
			
		||||
     * The unit is your currency's smallest unit (default: euro cent).
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsPositive()
 | 
			
		||||
    amountPerDistance: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update a DistanceDonation entity based on this.
 | 
			
		||||
     * @param donation The donation that shall be updated.
 | 
			
		||||
     */
 | 
			
		||||
    public async update(donation: DistanceDonation): Promise<DistanceDonation> {
 | 
			
		||||
        donation.amountPerDistance = this.amountPerDistance;
 | 
			
		||||
        donation.donor = await this.getDonor();
 | 
			
		||||
        donation.runner = await this.getRunner();
 | 
			
		||||
 | 
			
		||||
        return donation;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets a runner based on the runner id provided via this.runner.
 | 
			
		||||
     */
 | 
			
		||||
    public async getRunner(): Promise<Runner> {
 | 
			
		||||
        const runner = await getConnection().getRepository(Runner).findOne({ id: this.runner });
 | 
			
		||||
        if (!runner) {
 | 
			
		||||
            throw new RunnerNotFoundError();
 | 
			
		||||
        }
 | 
			
		||||
        return runner;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										41
									
								
								src/models/actions/update/UpdateDonation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/models/actions/update/UpdateDonation.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
import { IsInt, IsPositive } from 'class-validator';
 | 
			
		||||
import { getConnection } from 'typeorm';
 | 
			
		||||
import { DonorNotFoundError } from '../../../errors/DonorErrors';
 | 
			
		||||
import { Donation } from '../../entities/Donation';
 | 
			
		||||
import { Donor } from '../../entities/Donor';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class is used to update a Donation entity (via put request).
 | 
			
		||||
 */
 | 
			
		||||
export abstract class UpdateDonation {
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated donation's id.
 | 
			
		||||
     * This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    id: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated donation's associated donor's id.
 | 
			
		||||
     * This is important to link donations to donors.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsPositive()
 | 
			
		||||
    donor: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new Donation entity from this.
 | 
			
		||||
     */
 | 
			
		||||
    public abstract update(donation: Donation): Promise<Donation>;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets a donor based on the donor id provided via this.donor.
 | 
			
		||||
     */
 | 
			
		||||
    public async getDonor(): Promise<Donor> {
 | 
			
		||||
        const donor = await getConnection().getRepository(Donor).findOne({ id: this.donor });
 | 
			
		||||
        if (!donor) {
 | 
			
		||||
            throw new DonorNotFoundError();
 | 
			
		||||
        }
 | 
			
		||||
        return donor;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import { IsBoolean, IsInt, IsOptional } from 'class-validator';
 | 
			
		||||
import { DonorReceiptAddressNeededError } from '../../../errors/DonorErrors';
 | 
			
		||||
import { Address } from '../../entities/Address';
 | 
			
		||||
import { Donor } from '../../entities/Donor';
 | 
			
		||||
import { CreateParticipant } from '../create/CreateParticipant';
 | 
			
		||||
 | 
			
		||||
@@ -33,9 +34,10 @@ export class UpdateDonor extends CreateParticipant {
 | 
			
		||||
        donor.phone = this.phone;
 | 
			
		||||
        donor.email = this.email;
 | 
			
		||||
        donor.receiptNeeded = this.receiptNeeded;
 | 
			
		||||
        donor.address = await this.getAddress();
 | 
			
		||||
 | 
			
		||||
        if (this.receiptNeeded == true && this.address == null) {
 | 
			
		||||
        if (!this.address) { donor.address.reset(); }
 | 
			
		||||
        else { donor.address = this.address; }
 | 
			
		||||
        Address.validate(donor.address);
 | 
			
		||||
        if (this.receiptNeeded == true && Address.isValidAddress(donor.address) == false) {
 | 
			
		||||
            throw new DonorReceiptAddressNeededError()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								src/models/actions/update/UpdateFixedDonation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/models/actions/update/UpdateFixedDonation.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
import { IsInt, IsPositive } from 'class-validator';
 | 
			
		||||
import { FixedDonation } from '../../entities/FixedDonation';
 | 
			
		||||
import { UpdateDonation } from './UpdateDonation';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class is used to update a FixedDonation entity (via put request).
 | 
			
		||||
 */
 | 
			
		||||
export class UpdateFixedDonation extends UpdateDonation {
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated donation's amount.
 | 
			
		||||
     * The unit is your currency's smallest unit (default: euro cent).
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsPositive()
 | 
			
		||||
    amount: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update a FixedDonation entity based on this.
 | 
			
		||||
     * @param donation The donation that shall be updated.
 | 
			
		||||
     */
 | 
			
		||||
    public async update(donation: FixedDonation): Promise<FixedDonation> {
 | 
			
		||||
        donation.amount = this.amount;
 | 
			
		||||
        donation.donor = await this.getDonor();
 | 
			
		||||
 | 
			
		||||
        return donation;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										106
									
								
								src/models/actions/update/UpdateGroupContact.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								src/models/actions/update/UpdateGroupContact.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,106 @@
 | 
			
		||||
import { IsEmail, IsInt, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { config } from '../../../config';
 | 
			
		||||
import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
 | 
			
		||||
import { Address } from '../../entities/Address';
 | 
			
		||||
import { GroupContact } from '../../entities/GroupContact';
 | 
			
		||||
import { RunnerGroup } from '../../entities/RunnerGroup';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class is used to update a GroupContact entity (via put request).
 | 
			
		||||
 */
 | 
			
		||||
export class UpdateGroupContact {
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated contact's id.
 | 
			
		||||
     * This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    id: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated contact's first name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    firstname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated contact's middle name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    middlename?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated contact's last name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    lastname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated contact's address.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsObject()
 | 
			
		||||
    address?: Address;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated contact's phone number.
 | 
			
		||||
     * This will be validated against the configured country phone numer syntax (default: international).
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsPhoneNumber(config.phone_validation_countrycode)
 | 
			
		||||
    phone?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated contact's email address.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsEmail()
 | 
			
		||||
    email?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated contacts's groups' ids.
 | 
			
		||||
     * You can provide either one groupId or an array of groupIDs.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    groups?: number[] | number
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get's all groups for this contact by their id's;
 | 
			
		||||
     */
 | 
			
		||||
    public async getGroups(): Promise<RunnerGroup[]> {
 | 
			
		||||
        if (!this.groups) { return null; }
 | 
			
		||||
        let groups = new Array<RunnerGroup>();
 | 
			
		||||
        if (!Array.isArray(this.groups)) {
 | 
			
		||||
            this.groups = [this.groups]
 | 
			
		||||
        }
 | 
			
		||||
        for (let group of this.groups) {
 | 
			
		||||
            let found = await getConnectionManager().get().getRepository(RunnerGroup).findOne({ id: group });
 | 
			
		||||
            if (!found) { throw new RunnerGroupNotFoundError(); }
 | 
			
		||||
            groups.push(found);
 | 
			
		||||
        }
 | 
			
		||||
        return groups;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates a provided Donor entity based on this.
 | 
			
		||||
     * @param contact the contact you want to update.
 | 
			
		||||
     */
 | 
			
		||||
    public async update(contact: GroupContact): Promise<GroupContact> {
 | 
			
		||||
        contact.firstname = this.firstname; GroupContact
 | 
			
		||||
        contact.middlename = this.middlename;
 | 
			
		||||
        contact.lastname = this.lastname;
 | 
			
		||||
        contact.phone = this.phone;
 | 
			
		||||
        contact.email = this.email;
 | 
			
		||||
        if (!this.address) { contact.address.reset(); }
 | 
			
		||||
        else { contact.address = this.address; }
 | 
			
		||||
        Address.validate(contact.address);
 | 
			
		||||
        contact.groups = await this.getGroups();
 | 
			
		||||
 | 
			
		||||
        return contact;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import { IsInt, IsNotEmpty, IsObject } from 'class-validator';
 | 
			
		||||
import { IsInt, IsNotEmpty, IsPositive } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { PermissionNeedsPrincipalError } from '../../../errors/PermissionErrors';
 | 
			
		||||
import { PrincipalNotFoundError, PrincipalWrongTypeError } from '../../../errors/PrincipalErrors';
 | 
			
		||||
import { PrincipalNotFoundError } from '../../../errors/PrincipalErrors';
 | 
			
		||||
import { Permission } from '../../entities/Permission';
 | 
			
		||||
import { Principal } from '../../entities/Principal';
 | 
			
		||||
import { PermissionAction } from '../../enums/PermissionAction';
 | 
			
		||||
@@ -20,12 +20,11 @@ export class UpdatePermission {
 | 
			
		||||
    id: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated permissions's principal.
 | 
			
		||||
     * Just has to contain the principal's id -everything else won't be checked or changed.
 | 
			
		||||
     * The updated permissions's principal's id.
 | 
			
		||||
     */
 | 
			
		||||
    @IsObject()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    principal: Principal;
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsPositive()
 | 
			
		||||
    principal: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The permissions's target.
 | 
			
		||||
@@ -57,12 +56,8 @@ export class UpdatePermission {
 | 
			
		||||
        if (this.principal === undefined || this.principal === null) {
 | 
			
		||||
            throw new PermissionNeedsPrincipalError();
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.principal.id)) {
 | 
			
		||||
            let principal = await getConnectionManager().get().getRepository(Principal).findOne({ id: this.principal.id });
 | 
			
		||||
            if (!principal) { throw new PrincipalNotFoundError(); }
 | 
			
		||||
            return principal;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new PrincipalWrongTypeError();
 | 
			
		||||
        let principal = await getConnectionManager().get().getRepository(Principal).findOne({ id: this.principal });
 | 
			
		||||
        if (!principal) { throw new PrincipalNotFoundError(); }
 | 
			
		||||
        return principal;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
import { IsInt, IsObject } from 'class-validator';
 | 
			
		||||
import { IsInt, IsPositive } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
 | 
			
		||||
import { RunnerOrganisationWrongTypeError } from '../../../errors/RunnerOrganisationErrors';
 | 
			
		||||
import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors';
 | 
			
		||||
import { Address } from '../../entities/Address';
 | 
			
		||||
import { Runner } from '../../entities/Runner';
 | 
			
		||||
import { RunnerGroup } from '../../entities/RunnerGroup';
 | 
			
		||||
import { CreateParticipant } from '../create/CreateParticipant';
 | 
			
		||||
@@ -20,11 +20,11 @@ export class UpdateRunner extends CreateParticipant {
 | 
			
		||||
    id: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated runner's new team/org.
 | 
			
		||||
     * Just has to contain the group's id -everything else won't be checked or changed.
 | 
			
		||||
     * The updated runner's group's id.
 | 
			
		||||
     */
 | 
			
		||||
    @IsObject()
 | 
			
		||||
    group: RunnerGroup;
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsPositive()
 | 
			
		||||
    group: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates a provided Runner entity based on this.
 | 
			
		||||
@@ -36,7 +36,9 @@ export class UpdateRunner extends CreateParticipant {
 | 
			
		||||
        runner.phone = this.phone;
 | 
			
		||||
        runner.email = this.email;
 | 
			
		||||
        runner.group = await this.getGroup();
 | 
			
		||||
        runner.address = await this.getAddress();
 | 
			
		||||
        if (!this.address) { runner.address.reset(); }
 | 
			
		||||
        else { runner.address = this.address; }
 | 
			
		||||
        Address.validate(runner.address);
 | 
			
		||||
 | 
			
		||||
        return runner;
 | 
			
		||||
    }
 | 
			
		||||
@@ -48,12 +50,8 @@ export class UpdateRunner extends CreateParticipant {
 | 
			
		||||
        if (this.group === undefined || this.group === null) {
 | 
			
		||||
            throw new RunnerTeamNeedsParentError();
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.group.id)) {
 | 
			
		||||
            let group = await getConnectionManager().get().getRepository(RunnerGroup).findOne({ id: this.group.id });
 | 
			
		||||
            if (!group) { throw new RunnerGroupNotFoundError; }
 | 
			
		||||
            return group;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new RunnerOrganisationWrongTypeError;
 | 
			
		||||
        let group = await getConnectionManager().get().getRepository(RunnerGroup).findOne({ id: this.group });
 | 
			
		||||
        if (!group) { throw new RunnerGroupNotFoundError; }
 | 
			
		||||
        return group;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -17,7 +17,7 @@ export class UpdateRunnerCard {
 | 
			
		||||
    id?: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated card's associated runner.
 | 
			
		||||
     * The updated card's associated runner's id.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,52 +0,0 @@
 | 
			
		||||
import { IsInt, IsOptional } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { AddressNotFoundError } from '../../../errors/AddressErrors';
 | 
			
		||||
import { Address } from '../../entities/Address';
 | 
			
		||||
import { RunnerOrganisation } from '../../entities/RunnerOrganisation';
 | 
			
		||||
import { CreateRunnerGroup } from '../create/CreateRunnerGroup';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class is used to update a RunnerOrganisation entity (via put request).
 | 
			
		||||
 */
 | 
			
		||||
export class UpdateRunnerOrganisation extends CreateRunnerGroup {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated orgs's id.
 | 
			
		||||
     * This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    id: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated organisation's address.
 | 
			
		||||
     * Just has to contain the address's id - everything else won't be checked or changed.
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    address?: Address;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Loads the organisation's address based on it's id.
 | 
			
		||||
     */
 | 
			
		||||
    public async getAddress(): Promise<Address> {
 | 
			
		||||
        if (this.address === undefined || this.address === null) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address.id });
 | 
			
		||||
        if (!address) { throw new AddressNotFoundError; }
 | 
			
		||||
        return address;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates a provided RunnerOrganisation entity based on this.
 | 
			
		||||
     */
 | 
			
		||||
    public async update(organisation: RunnerOrganisation): Promise<RunnerOrganisation> {
 | 
			
		||||
 | 
			
		||||
        organisation.name = this.name;
 | 
			
		||||
        organisation.contact = await this.getContact();
 | 
			
		||||
        // organisation.address = await this.getAddress();
 | 
			
		||||
 | 
			
		||||
        return organisation;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										53
									
								
								src/models/actions/update/UpdateRunnerOrganization.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/models/actions/update/UpdateRunnerOrganization.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
import { IsBoolean, IsInt, IsObject, IsOptional } from 'class-validator';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import { Address } from '../../entities/Address';
 | 
			
		||||
import { RunnerOrganization } from '../../entities/RunnerOrganization';
 | 
			
		||||
import { CreateRunnerGroup } from '../create/CreateRunnerGroup';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class is used to update a RunnerOrganization entity (via put request).
 | 
			
		||||
 */
 | 
			
		||||
export class UpdateRunnerOrganization extends CreateRunnerGroup {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated orgs's id.
 | 
			
		||||
     * This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    id: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated organization's address.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsObject()
 | 
			
		||||
    address?: Address;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Is registration enabled for the updated organization?
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsBoolean()
 | 
			
		||||
    registrationEnabled?: boolean = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates a provided RunnerOrganization entity based on this.
 | 
			
		||||
     */
 | 
			
		||||
    public async update(organization: RunnerOrganization): Promise<RunnerOrganization> {
 | 
			
		||||
 | 
			
		||||
        organization.name = this.name;
 | 
			
		||||
        organization.contact = await this.getContact();
 | 
			
		||||
        if (!this.address) { organization.address.reset(); }
 | 
			
		||||
        else { organization.address = this.address; }
 | 
			
		||||
        Address.validate(organization.address);
 | 
			
		||||
 | 
			
		||||
        if (this.registrationEnabled && !organization.key) {
 | 
			
		||||
            organization.key = uuid.v4().toUpperCase();
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            organization.key = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return organization;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
import { IsInt, IsNotEmpty, IsObject } from 'class-validator';
 | 
			
		||||
import { IsInt, IsPositive } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { RunnerOrganisationNotFoundError, RunnerOrganisationWrongTypeError } from '../../../errors/RunnerOrganisationErrors';
 | 
			
		||||
import { RunnerOrganizationNotFoundError } from '../../../errors/RunnerOrganizationErrors';
 | 
			
		||||
import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors';
 | 
			
		||||
import { RunnerOrganisation } from '../../entities/RunnerOrganisation';
 | 
			
		||||
import { RunnerOrganization } from '../../entities/RunnerOrganization';
 | 
			
		||||
import { RunnerTeam } from '../../entities/RunnerTeam';
 | 
			
		||||
import { CreateRunnerGroup } from '../create/CreateRunnerGroup';
 | 
			
		||||
 | 
			
		||||
@@ -19,27 +19,22 @@ export class UpdateRunnerTeam extends CreateRunnerGroup {
 | 
			
		||||
    id: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated team's parentGroup.
 | 
			
		||||
     * Just has to contain the organisation's id - everything else won't be checked or changed.
 | 
			
		||||
     * The updated team's parentGroup's id.
 | 
			
		||||
     */
 | 
			
		||||
    @IsObject()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    parentGroup: RunnerOrganisation;
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsPositive()
 | 
			
		||||
    parentGroup: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Loads the updated teams's parentGroup based on it's id.
 | 
			
		||||
     */
 | 
			
		||||
    public async getParent(): Promise<RunnerOrganisation> {
 | 
			
		||||
    public async getParent(): Promise<RunnerOrganization> {
 | 
			
		||||
        if (this.parentGroup === undefined || this.parentGroup === null) {
 | 
			
		||||
            throw new RunnerTeamNeedsParentError();
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.parentGroup.id)) {
 | 
			
		||||
            let parentGroup = await getConnectionManager().get().getRepository(RunnerOrganisation).findOne({ id: this.parentGroup.id });
 | 
			
		||||
            if (!parentGroup) { throw new RunnerOrganisationNotFoundError();; }
 | 
			
		||||
            return parentGroup;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new RunnerOrganisationWrongTypeError;
 | 
			
		||||
        let parentGroup = await getConnectionManager().get().getRepository(RunnerOrganization).findOne({ id: this.parentGroup });
 | 
			
		||||
        if (!parentGroup) { throw new RunnerOrganizationNotFoundError();; }
 | 
			
		||||
        return parentGroup;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ export abstract class UpdateScan {
 | 
			
		||||
    id: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated scan's associated runner.
 | 
			
		||||
     * The updated scan's associated runner's id.
 | 
			
		||||
     * This is important to link ran distances to runners.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { IsBoolean, IsInt, IsOptional } from 'class-validator';
 | 
			
		||||
import { IsBoolean, IsInt, IsOptional, IsPositive } from 'class-validator';
 | 
			
		||||
import { getConnection } from 'typeorm';
 | 
			
		||||
import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
 | 
			
		||||
import { ScanStationNotFoundError } from '../../../errors/ScanStationErrors';
 | 
			
		||||
@@ -18,12 +18,12 @@ export abstract class UpdateTrackScan {
 | 
			
		||||
    id: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated scan's associated runner.
 | 
			
		||||
     * The updated scan's associated runner's id.
 | 
			
		||||
     * This is important to link ran distances to runners.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    runner?: number;
 | 
			
		||||
    @IsPositive()
 | 
			
		||||
    runner: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Is the updated scan valid (for fraud reasons).
 | 
			
		||||
@@ -33,12 +33,12 @@ export abstract class UpdateTrackScan {
 | 
			
		||||
    valid?: boolean = true;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated scan's associated station.
 | 
			
		||||
     * The updated scan's associated station's id.
 | 
			
		||||
     * This is important to link ran distances to runners.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    public station?: number;
 | 
			
		||||
    @IsPositive()
 | 
			
		||||
    public station: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update a TrackScan entity based on this.
 | 
			
		||||
@@ -46,12 +46,8 @@ export abstract class UpdateTrackScan {
 | 
			
		||||
     */
 | 
			
		||||
    public async update(scan: TrackScan): Promise<TrackScan> {
 | 
			
		||||
        scan.valid = this.valid;
 | 
			
		||||
        if (this.runner) {
 | 
			
		||||
            scan.runner = await this.getRunner();
 | 
			
		||||
        }
 | 
			
		||||
        if (this.station) {
 | 
			
		||||
            scan.station = await this.getStation();
 | 
			
		||||
        }
 | 
			
		||||
        scan.runner = await this.getRunner();
 | 
			
		||||
        scan.station = await this.getStation();
 | 
			
		||||
        scan.track = scan.station.track;
 | 
			
		||||
 | 
			
		||||
        return scan;
 | 
			
		||||
@@ -72,7 +68,7 @@ export abstract class UpdateTrackScan {
 | 
			
		||||
     * Gets a runner based on the runner id provided via this.runner.
 | 
			
		||||
     */
 | 
			
		||||
    public async getStation(): Promise<ScanStation> {
 | 
			
		||||
        const station = await getConnection().getRepository(ScanStation).findOne({ id: this.station });
 | 
			
		||||
        const station = await getConnection().getRepository(ScanStation).findOne({ id: this.station }, { relations: ['track'] });
 | 
			
		||||
        if (!station) {
 | 
			
		||||
            throw new ScanStationNotFoundError();
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
import * as argon2 from "argon2";
 | 
			
		||||
import { IsBoolean, IsEmail, IsInt, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator';
 | 
			
		||||
import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { config } from '../../../config';
 | 
			
		||||
import { UsernameOrEmailNeededError } from '../../../errors/AuthError';
 | 
			
		||||
import { UserEmailNeededError, UsernameContainsIllegalCharacterError } from '../../../errors/UserErrors';
 | 
			
		||||
import { UserGroupNotFoundError } from '../../../errors/UserGroupErrors';
 | 
			
		||||
import { User } from '../../entities/User';
 | 
			
		||||
import { UserGroup } from '../../entities/UserGroup';
 | 
			
		||||
@@ -40,7 +40,7 @@ export class UpdateUser {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated user's username.
 | 
			
		||||
     * You have to provide at least one of: {email, username}.
 | 
			
		||||
     * You have to provide a email addres, so this is optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsString()
 | 
			
		||||
@@ -48,12 +48,11 @@ export class UpdateUser {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated user's email address.
 | 
			
		||||
     * You have to provide at least one of: {email, username}.
 | 
			
		||||
     */
 | 
			
		||||
    @IsEmail()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    email?: string;
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    email: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated user's phone number.
 | 
			
		||||
@@ -77,14 +76,14 @@ export class UpdateUser {
 | 
			
		||||
     * Should the user be enabled?
 | 
			
		||||
     */
 | 
			
		||||
    @IsBoolean()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    enabled: boolean = true;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated user's groups.
 | 
			
		||||
     * This just has to contain the group's id - everything else won't be changed.
 | 
			
		||||
     * The updated user's groups' ids.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    groups?: UserGroup[]
 | 
			
		||||
    groups?: number | number[]
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
    * The user's profile pic (or rather a url pointing to it).
 | 
			
		||||
@@ -99,16 +98,18 @@ export class UpdateUser {
 | 
			
		||||
     * @param user The user that shall be updated.
 | 
			
		||||
     */
 | 
			
		||||
    public async update(user: User): Promise<User> {
 | 
			
		||||
        user.email = this.email;
 | 
			
		||||
        user.username = this.username;
 | 
			
		||||
        if ((user.email === undefined || user.email === null) && (user.username === undefined || user.username === null)) {
 | 
			
		||||
            throw new UsernameOrEmailNeededError();
 | 
			
		||||
        if (!this.email) {
 | 
			
		||||
            throw new UserEmailNeededError();
 | 
			
		||||
        }
 | 
			
		||||
        if (this.username.includes("@")) { throw new UsernameContainsIllegalCharacterError(); }
 | 
			
		||||
 | 
			
		||||
        if (this.password) {
 | 
			
		||||
            user.password = await argon2.hash(this.password + user.uuid);
 | 
			
		||||
            user.refreshTokenCount = user.refreshTokenCount + 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        user.email = this.email;
 | 
			
		||||
        user.username = this.username;
 | 
			
		||||
        user.enabled = this.enabled;
 | 
			
		||||
        user.firstname = this.firstname
 | 
			
		||||
        user.middlename = this.middlename
 | 
			
		||||
@@ -123,7 +124,7 @@ export class UpdateUser {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Loads the updated user's groups based on their ids.
 | 
			
		||||
     * Get's all groups for this user by their id's;
 | 
			
		||||
     */
 | 
			
		||||
    public async getGroups() {
 | 
			
		||||
        if (!this.groups) { return null; }
 | 
			
		||||
@@ -132,7 +133,7 @@ export class UpdateUser {
 | 
			
		||||
            this.groups = [this.groups]
 | 
			
		||||
        }
 | 
			
		||||
        for (let group of this.groups) {
 | 
			
		||||
            let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group.id });
 | 
			
		||||
            let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group });
 | 
			
		||||
            if (!found) { throw new UserGroupNotFoundError(); }
 | 
			
		||||
            groups.push(found);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,44 +1,24 @@
 | 
			
		||||
import {
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
  IsOptional,
 | 
			
		||||
  IsPostalCode,
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
 | 
			
		||||
import { Column } from "typeorm";
 | 
			
		||||
import ValidatorJS from 'validator';
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { IAddressUser } from './IAddressUser';
 | 
			
		||||
import { AddressCityEmptyError, AddressCountryEmptyError, AddressFirstLineEmptyError, AddressPostalCodeEmptyError, AddressPostalCodeInvalidError } from '../../errors/AddressErrors';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the Address entity.
 | 
			
		||||
 * Defines the Address class.
 | 
			
		||||
 * Implemented this way to prevent any formatting differences.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export class Address {
 | 
			
		||||
  /**
 | 
			
		||||
   * Autogenerated unique id (primary key).
 | 
			
		||||
   */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The address's description.
 | 
			
		||||
   * Optional and mostly for UX.
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  description?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The address's first line.
 | 
			
		||||
   * Containing the street and house number.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  address1: string;
 | 
			
		||||
  address1?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The address's second line.
 | 
			
		||||
@@ -46,45 +26,61 @@ export class Address {
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  address2?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The address's postal code.
 | 
			
		||||
   * This will get checked against the postal code syntax for the configured country.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsPostalCode(config.postalcode_validation_countrycode)
 | 
			
		||||
  postalcode: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The address's city.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  city: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The address's country.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  country: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to link the address to participants.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => IAddressUser, addressUser => addressUser.address, { nullable: true })
 | 
			
		||||
  addressUsers: IAddressUser[];
 | 
			
		||||
  public reset() {
 | 
			
		||||
    this.address1 = null;
 | 
			
		||||
    this.address2 = null;
 | 
			
		||||
    this.city = null;
 | 
			
		||||
    this.country = null;
 | 
			
		||||
    this.postalcode = null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Turns this entity into it's response class.
 | 
			
		||||
   * Checks if this is a valid address
 | 
			
		||||
   */
 | 
			
		||||
  public toResponse() {
 | 
			
		||||
    return new Error("NotImplemented");
 | 
			
		||||
  public static isValidAddress(address: Address): Boolean {
 | 
			
		||||
    if (address == null) { return false; }
 | 
			
		||||
    if (address.address1 == null || address.city == null || address.country == null || address.postalcode == null) { return false; }
 | 
			
		||||
    if (ValidatorJS.isPostalCode(address.postalcode, config.postalcode_validation_countrycode) == false) { return false; }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * This function validates addresses.
 | 
			
		||||
   * This is a workaround for non-existant class validation for embedded entities.
 | 
			
		||||
   * @param address The address that shall get validated.
 | 
			
		||||
   */
 | 
			
		||||
  public static validate(address: Address) {
 | 
			
		||||
    if (address == null) { return; }
 | 
			
		||||
    if (address.address1 == null && address.city == null && address.country == null && address.postalcode == null) { return; }
 | 
			
		||||
    if (address.address1 == null) { throw new AddressFirstLineEmptyError(); }
 | 
			
		||||
    if (address.postalcode == null) { throw new AddressPostalCodeEmptyError(); }
 | 
			
		||||
    if (address.city == null) { throw new AddressCityEmptyError(); }
 | 
			
		||||
    if (address.country == null) { throw new AddressCountryEmptyError(); }
 | 
			
		||||
    if (ValidatorJS.isPostalCode(address.postalcode.toString(), config.postalcode_validation_countrycode) == false) { throw new AddressPostalCodeInvalidError(); }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								src/models/entities/ConfigFlags.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/models/entities/ConfigFlags.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
import {
 | 
			
		||||
    IsNotEmpty,
 | 
			
		||||
    IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, PrimaryColumn } from "typeorm";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the ConfigFlag entity.
 | 
			
		||||
 * This entity can be used to set some flags on db init.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export class ConfigFlag {
 | 
			
		||||
    /**
 | 
			
		||||
     * The flag's name (primary).
 | 
			
		||||
     */
 | 
			
		||||
    @PrimaryColumn()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    option: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The flag's value.
 | 
			
		||||
     */
 | 
			
		||||
    @Column()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    value: string;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import { IsInt, IsNotEmpty, IsPositive } from "class-validator";
 | 
			
		||||
import { ChildEntity, Column, ManyToOne } from "typeorm";
 | 
			
		||||
import { ResponseDistanceDonation } from '../responses/ResponseDistanceDonation';
 | 
			
		||||
import { Donation } from "./Donation";
 | 
			
		||||
import { Runner } from "./Runner";
 | 
			
		||||
 | 
			
		||||
@@ -31,7 +32,7 @@ export class DistanceDonation extends Donation {
 | 
			
		||||
   * Get's calculated from the runner's distance ran and the amount donated per kilometer.
 | 
			
		||||
   */
 | 
			
		||||
  public get amount(): number {
 | 
			
		||||
    let calculatedAmount = -1;
 | 
			
		||||
    let calculatedAmount = 0;
 | 
			
		||||
    try {
 | 
			
		||||
      calculatedAmount = this.amountPerDistance * (this.runner.distance / 1000);
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
@@ -43,7 +44,7 @@ export class DistanceDonation extends Donation {
 | 
			
		||||
  /**
 | 
			
		||||
   * Turns this entity into it's response class.
 | 
			
		||||
   */
 | 
			
		||||
  public toResponse() {
 | 
			
		||||
    return new Error("NotImplemented");
 | 
			
		||||
  public toResponse(): ResponseDistanceDonation {
 | 
			
		||||
    return new ResponseDistanceDonation(this);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ import {
 | 
			
		||||
  IsNotEmpty
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
 | 
			
		||||
import { ResponseDonation } from '../responses/ResponseDonation';
 | 
			
		||||
import { Donor } from './Donor';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -31,12 +32,13 @@ export abstract class Donation {
 | 
			
		||||
   * The donation's amount in cents (or whatever your currency's smallest unit is.).
 | 
			
		||||
   * The exact implementation may differ for each type of donation.
 | 
			
		||||
   */
 | 
			
		||||
  abstract amount: number;
 | 
			
		||||
  public abstract get amount(): number;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Turns this entity into it's response class.
 | 
			
		||||
   */
 | 
			
		||||
  public toResponse() {
 | 
			
		||||
    return new Error("NotImplemented");
 | 
			
		||||
  public toResponse(): ResponseDonation {
 | 
			
		||||
    return new ResponseDonation(this);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { IsBoolean } from "class-validator";
 | 
			
		||||
import { IsBoolean, IsInt } from "class-validator";
 | 
			
		||||
import { ChildEntity, Column, OneToMany } from "typeorm";
 | 
			
		||||
import { ResponseDonor } from '../responses/ResponseDonor';
 | 
			
		||||
import { Donation } from './Donation';
 | 
			
		||||
@@ -24,6 +24,15 @@ export class Donor extends Participant {
 | 
			
		||||
  @OneToMany(() => Donation, donation => donation.donor, { nullable: true })
 | 
			
		||||
  donations: Donation[];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the total donations of a donor based on his linked donations.
 | 
			
		||||
  */
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  public get donationAmount(): number {
 | 
			
		||||
    if (!this.donations) { return 0; }
 | 
			
		||||
    return this.donations.reduce((sum, current) => sum + current.amount, 0);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Turns this entity into it's response class.
 | 
			
		||||
   */
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import { IsInt, IsPositive } from "class-validator";
 | 
			
		||||
import { ChildEntity, Column } from "typeorm";
 | 
			
		||||
import { ResponseDonation } from '../responses/ResponseDonation';
 | 
			
		||||
import { Donation } from "./Donation";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -11,16 +12,33 @@ export class FixedDonation extends Donation {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The donation's amount in cents (or whatever your currency's smallest unit is.).
 | 
			
		||||
   * This is the "real" value used by fixed donations.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  @IsPositive()
 | 
			
		||||
  amount: number;
 | 
			
		||||
  private _amount: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The donation's amount in cents (or whatever your currency's smallest unit is.).
 | 
			
		||||
   */
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  @IsPositive()
 | 
			
		||||
  public get amount(): number {
 | 
			
		||||
    return this._amount;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The donation's amount in cents (or whatever your currency's smallest unit is.).
 | 
			
		||||
   */
 | 
			
		||||
  public set amount(value: number) {
 | 
			
		||||
    this._amount = value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Turns this entity into it's response class.
 | 
			
		||||
   */
 | 
			
		||||
  public toResponse() {
 | 
			
		||||
    return new Error("NotImplemented");
 | 
			
		||||
  public toResponse(): ResponseDonation {
 | 
			
		||||
    return new ResponseDonation(this);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -7,10 +7,10 @@ import {
 | 
			
		||||
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
 | 
			
		||||
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { ResponseGroupContact } from '../responses/ResponseGroupContact';
 | 
			
		||||
import { Address } from "./Address";
 | 
			
		||||
import { IAddressUser } from './IAddressUser';
 | 
			
		||||
import { RunnerGroup } from "./RunnerGroup";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -18,7 +18,7 @@ import { RunnerGroup } from "./RunnerGroup";
 | 
			
		||||
 * Mainly it's own class to reduce duplicate code and enable contact's to be associated with multiple groups.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export class GroupContact implements IAddressUser {
 | 
			
		||||
export class GroupContact {
 | 
			
		||||
  /**
 | 
			
		||||
   * Autogenerated unique id (primary key).
 | 
			
		||||
   */
 | 
			
		||||
@@ -54,8 +54,7 @@ export class GroupContact implements IAddressUser {
 | 
			
		||||
   * The contact's address.
 | 
			
		||||
   * This is a address object to prevent any formatting differences.
 | 
			
		||||
   */
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @ManyToOne(() => Address, address => address.addressUsers, { nullable: true })
 | 
			
		||||
  @Column(type => Address)
 | 
			
		||||
  address?: Address;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@@ -85,7 +84,7 @@ export class GroupContact implements IAddressUser {
 | 
			
		||||
  /**
 | 
			
		||||
   * Turns this entity into it's response class.
 | 
			
		||||
   */
 | 
			
		||||
  public toResponse() {
 | 
			
		||||
    return new Error("NotImplemented");
 | 
			
		||||
  public toResponse(): ResponseGroupContact {
 | 
			
		||||
    return new ResponseGroupContact(this);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,20 +0,0 @@
 | 
			
		||||
import { Entity, ManyToOne, PrimaryColumn } from 'typeorm';
 | 
			
		||||
import { Address } from './Address';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The interface(tm) all entities using addresses have to implement.
 | 
			
		||||
 * This is a abstract class, because apparently typeorm can't really work with interfaces :/
 | 
			
		||||
 */
 | 
			
		||||
@Entity()
 | 
			
		||||
export abstract class IAddressUser {
 | 
			
		||||
    @PrimaryColumn()
 | 
			
		||||
    id: number;
 | 
			
		||||
 | 
			
		||||
    @ManyToOne(() => Address, address => address.addressUsers, { nullable: true })
 | 
			
		||||
    address?: Address
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
   * Turns this entity into it's response class.
 | 
			
		||||
   */
 | 
			
		||||
    public abstract toResponse();
 | 
			
		||||
}
 | 
			
		||||
@@ -7,11 +7,10 @@ import {
 | 
			
		||||
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
 | 
			
		||||
import { Column, Entity, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { ResponseParticipant } from '../responses/ResponseParticipant';
 | 
			
		||||
import { Address } from "./Address";
 | 
			
		||||
import { IAddressUser } from './IAddressUser';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the Participant entity.
 | 
			
		||||
@@ -19,7 +18,7 @@ import { IAddressUser } from './IAddressUser';
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
@TableInheritance({ column: { name: "type", type: "varchar" } })
 | 
			
		||||
export abstract class Participant implements IAddressUser {
 | 
			
		||||
export abstract class Participant {
 | 
			
		||||
  /**
 | 
			
		||||
   * Autogenerated unique id (primary key).
 | 
			
		||||
   */
 | 
			
		||||
@@ -55,7 +54,7 @@ export abstract class Participant implements IAddressUser {
 | 
			
		||||
   * The participant's address.
 | 
			
		||||
   * This is a address object to prevent any formatting differences.
 | 
			
		||||
   */
 | 
			
		||||
  @ManyToOne(() => Address, address => address.addressUsers, { nullable: true })
 | 
			
		||||
  @Column(type => Address)
 | 
			
		||||
  address?: Address;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ import { Scan } from "./Scan";
 | 
			
		||||
export class Runner extends Participant {
 | 
			
		||||
  /**
 | 
			
		||||
   * The runner's associated group.
 | 
			
		||||
   * Can be a runner team or organisation.
 | 
			
		||||
   * Can be a runner team or organization.
 | 
			
		||||
   */
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @ManyToOne(() => RunnerGroup, group => group.runners)
 | 
			
		||||
@@ -65,7 +65,7 @@ export class Runner extends Participant {
 | 
			
		||||
  */
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  public get distanceDonationAmount(): number {
 | 
			
		||||
    return this.distanceDonations.reduce((sum, current) => sum + current.amountPerDistance, 0) * this.distance;
 | 
			
		||||
    return this.distanceDonations.reduce((sum, current) => sum + current.amount, 0);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
 
 | 
			
		||||
@@ -1,35 +1,44 @@
 | 
			
		||||
import { IsInt, IsOptional } from "class-validator";
 | 
			
		||||
import { ChildEntity, ManyToOne, OneToMany } from "typeorm";
 | 
			
		||||
import { ResponseRunnerOrganisation } from '../responses/ResponseRunnerOrganisation';
 | 
			
		||||
import { IsInt, IsOptional, IsString } from "class-validator";
 | 
			
		||||
import { ChildEntity, Column, OneToMany } from "typeorm";
 | 
			
		||||
import { ResponseRunnerOrganization } from '../responses/ResponseRunnerOrganization';
 | 
			
		||||
import { Address } from './Address';
 | 
			
		||||
import { IAddressUser } from './IAddressUser';
 | 
			
		||||
import { Runner } from './Runner';
 | 
			
		||||
import { RunnerGroup } from "./RunnerGroup";
 | 
			
		||||
import { RunnerTeam } from "./RunnerTeam";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the RunnerOrganisation entity.
 | 
			
		||||
 * Defines the RunnerOrganization entity.
 | 
			
		||||
 * This usually is a school, club or company.
 | 
			
		||||
*/
 | 
			
		||||
@ChildEntity()
 | 
			
		||||
export class RunnerOrganisation extends RunnerGroup implements IAddressUser {
 | 
			
		||||
export class RunnerOrganization extends RunnerGroup {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The organisations's address.
 | 
			
		||||
   * The organizations's address.
 | 
			
		||||
   */
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @ManyToOne(() => Address, address => address.addressUsers, { nullable: true })
 | 
			
		||||
  @Column(type => Address)
 | 
			
		||||
  address?: Address;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The organisation's teams.
 | 
			
		||||
   * Used to link teams to a organisation.
 | 
			
		||||
   * The organization's teams.
 | 
			
		||||
   * Used to link teams to a organization.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => RunnerTeam, team => team.parentGroup, { nullable: true })
 | 
			
		||||
  teams: RunnerTeam[];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns all runners associated with this organisation (directly or indirectly via teams).
 | 
			
		||||
   * The organization's api key for self-service registration.
 | 
			
		||||
   * The api key can be used for the /runners/register/:token endpoint.
 | 
			
		||||
   * Is has to be base64 encoded if used via the api (to keep url-safety).
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true, unique: true })
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  key?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns all runners associated with this organization (directly or indirectly via teams).
 | 
			
		||||
   */
 | 
			
		||||
  public get allRunners(): Runner[] {
 | 
			
		||||
    let returnRunners: Runner[] = new Array<Runner>();
 | 
			
		||||
@@ -59,7 +68,7 @@ export class RunnerOrganisation extends RunnerGroup implements IAddressUser {
 | 
			
		||||
  /**
 | 
			
		||||
   * Turns this entity into it's response class.
 | 
			
		||||
   */
 | 
			
		||||
  public toResponse(): ResponseRunnerOrganisation {
 | 
			
		||||
    return new ResponseRunnerOrganisation(this);
 | 
			
		||||
  public toResponse(): ResponseRunnerOrganization {
 | 
			
		||||
    return new ResponseRunnerOrganization(this);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,7 +2,7 @@ import { IsNotEmpty } from "class-validator";
 | 
			
		||||
import { ChildEntity, ManyToOne } from "typeorm";
 | 
			
		||||
import { ResponseRunnerTeam } from '../responses/ResponseRunnerTeam';
 | 
			
		||||
import { RunnerGroup } from "./RunnerGroup";
 | 
			
		||||
import { RunnerOrganisation } from "./RunnerOrganisation";
 | 
			
		||||
import { RunnerOrganization } from "./RunnerOrganization";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the RunnerTeam entity.
 | 
			
		||||
@@ -13,11 +13,11 @@ export class RunnerTeam extends RunnerGroup {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The team's parent group.
 | 
			
		||||
   * Every team has to be part of a runnerOrganisation - this get's checked on creation and update.
 | 
			
		||||
   * Every team has to be part of a runnerOrganization - this get's checked on creation and update.
 | 
			
		||||
   */
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @ManyToOne(() => RunnerOrganisation, org => org.teams, { nullable: true })
 | 
			
		||||
  parentGroup?: RunnerOrganisation;
 | 
			
		||||
  @ManyToOne(() => RunnerOrganization, org => org.teams, { nullable: true })
 | 
			
		||||
  parentGroup?: RunnerOrganization;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Turns this entity into it's response class.
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ import { ChildEntity, Column, JoinTable, ManyToMany, OneToMany } from "typeorm";
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { ResponsePrincipal } from '../responses/ResponsePrincipal';
 | 
			
		||||
import { ResponseUser } from '../responses/ResponseUser';
 | 
			
		||||
import { Permission } from './Permission';
 | 
			
		||||
import { Principal } from './Principal';
 | 
			
		||||
import { UserAction } from './UserAction';
 | 
			
		||||
import { UserGroup } from './UserGroup';
 | 
			
		||||
@@ -25,9 +26,10 @@ export class User extends Principal {
 | 
			
		||||
  * The user's e-mail address.
 | 
			
		||||
  * Either username or email has to be set (otherwise the user couldn't log in).
 | 
			
		||||
  */
 | 
			
		||||
  @Column({ nullable: true, unique: true })
 | 
			
		||||
  @Column({ nullable: false, unique: true })
 | 
			
		||||
  @IsEmail()
 | 
			
		||||
  email?: string;
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  email: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * The user's phone number.
 | 
			
		||||
@@ -128,8 +130,26 @@ export class User extends Principal {
 | 
			
		||||
  @OneToMany(() => UserAction, action => action.user, { nullable: true })
 | 
			
		||||
  actions: UserAction[]
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Resolves all permissions granted to this user through groups.
 | 
			
		||||
   */
 | 
			
		||||
  public get inheritedPermissions(): Permission[] {
 | 
			
		||||
    let returnPermissions: Permission[] = new Array<Permission>();
 | 
			
		||||
 | 
			
		||||
    if (!this.groups) { return returnPermissions; }
 | 
			
		||||
    for (let group of this.groups) {
 | 
			
		||||
      if (group.permissions) {
 | 
			
		||||
        for (let permission of group.permissions) {
 | 
			
		||||
          returnPermissions.push(permission);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return returnPermissions;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Resolves all permissions granted to this user through groups or directly to the string enum format.
 | 
			
		||||
   * Also deduplicates the array.
 | 
			
		||||
   */
 | 
			
		||||
  public get allPermissions(): string[] {
 | 
			
		||||
    let returnPermissions: string[] = new Array<string>();
 | 
			
		||||
@@ -141,8 +161,10 @@ export class User extends Principal {
 | 
			
		||||
 | 
			
		||||
    if (!this.groups) { return returnPermissions; }
 | 
			
		||||
    for (let group of this.groups) {
 | 
			
		||||
      for (let permission of group.permissions) {
 | 
			
		||||
        returnPermissions.push(permission.toString());
 | 
			
		||||
      if (group.permissions) {
 | 
			
		||||
        for (let permission of group.permissions) {
 | 
			
		||||
          returnPermissions.push(permission.toString());
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return Array.from(new Set(returnPermissions));
 | 
			
		||||
 
 | 
			
		||||
@@ -1,40 +1,39 @@
 | 
			
		||||
import {
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
  IsOptional,
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { ChildEntity, Column } from "typeorm";
 | 
			
		||||
import { ResponsePrincipal } from '../responses/ResponsePrincipal';
 | 
			
		||||
import { ResponseUserGroup } from '../responses/ResponseUserGroup';
 | 
			
		||||
import { Principal } from './Principal';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the UserGroup entity.
 | 
			
		||||
 * This entity describes a group of users with a set of permissions.
 | 
			
		||||
*/
 | 
			
		||||
@ChildEntity()
 | 
			
		||||
export class UserGroup extends Principal {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The group's name
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  name: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The group's description
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  description?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Turns this entity into it's response class.
 | 
			
		||||
   */
 | 
			
		||||
  public toResponse(): ResponsePrincipal {
 | 
			
		||||
    return new ResponseUserGroup(this);
 | 
			
		||||
  }
 | 
			
		||||
import {
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
  IsOptional,
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { ChildEntity, Column } from "typeorm";
 | 
			
		||||
import { ResponseUserGroup } from '../responses/ResponseUserGroup';
 | 
			
		||||
import { Principal } from './Principal';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the UserGroup entity.
 | 
			
		||||
 * This entity describes a group of users with a set of permissions.
 | 
			
		||||
*/
 | 
			
		||||
@ChildEntity()
 | 
			
		||||
export class UserGroup extends Principal {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The group's name
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  name: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The group's description
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  description?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Turns this entity into it's response class.
 | 
			
		||||
   */
 | 
			
		||||
  public toResponse(): ResponseUserGroup {
 | 
			
		||||
    return new ResponseUserGroup(this);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
 */
 | 
			
		||||
export enum PermissionTarget {
 | 
			
		||||
    RUNNER = 'RUNNER',
 | 
			
		||||
    ORGANISATION = 'ORGANISATION',
 | 
			
		||||
    ORGANIZATION = 'ORGANIZATION',
 | 
			
		||||
    TEAM = 'TEAM',
 | 
			
		||||
    TRACK = 'TRACK',
 | 
			
		||||
    USER = 'USER',
 | 
			
		||||
@@ -13,5 +13,8 @@ export enum PermissionTarget {
 | 
			
		||||
    DONOR = 'DONOR',
 | 
			
		||||
    SCAN = 'SCAN',
 | 
			
		||||
    STATION = 'STATION',
 | 
			
		||||
    CARD = 'CARD'
 | 
			
		||||
    CARD = 'CARD',
 | 
			
		||||
    DONATION = 'DONATION',
 | 
			
		||||
    CONTACT = 'CONTACT',
 | 
			
		||||
    MAIL = 'MAIL'
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								src/models/enums/ResponseObjectType.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/models/enums/ResponseObjectType.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
/**
 | 
			
		||||
 * This enum contains all object types/entities a response can contain.
 | 
			
		||||
 */
 | 
			
		||||
export enum ResponseObjectType {
 | 
			
		||||
    AUTH = 'AUTH',
 | 
			
		||||
    DISTANCEDONATION = 'DISTANCEDONATION',
 | 
			
		||||
    DONATION = 'DONATION',
 | 
			
		||||
    DONOR = 'DONOR',
 | 
			
		||||
    EMPTY = 'EMPTY',
 | 
			
		||||
    GROUPCONTACT = 'GROUPCONTACT',
 | 
			
		||||
    LOGOUT = 'LOGOUT',
 | 
			
		||||
    PARTICIPANT = 'PARTICIPANT',
 | 
			
		||||
    PERMISSION = 'PERMISSION',
 | 
			
		||||
    PRINCIPAL = 'PRINCIPAL',
 | 
			
		||||
    RUNNER = 'RUNNER',
 | 
			
		||||
    RUNNERCARD = 'RUNNERCARD',
 | 
			
		||||
    RUNNERGROUP = 'RUNNERGROUP',
 | 
			
		||||
    RUNNERORGANIZATION = 'RUNNERORGANIZATION',
 | 
			
		||||
    RUNNERTEAM = 'RUNNERTEAM',
 | 
			
		||||
    SCAN = 'SCAN',
 | 
			
		||||
    SCANSTATION = 'SCANSTATION',
 | 
			
		||||
    SELFSERVICEDONATION = 'SELFSERVICEDONATION',
 | 
			
		||||
    SELFSERVICERUNNER = 'SELFSERVICRUNNER',
 | 
			
		||||
    STATS = 'STATS',
 | 
			
		||||
    STATSCLIENT = 'STATSCLIENT',
 | 
			
		||||
    STATSORGANIZATION = 'STATSORGANIZATION',
 | 
			
		||||
    STATSRUNNER = 'STATSRUNNER',
 | 
			
		||||
    STATSTEAM = 'STATSTEAM',
 | 
			
		||||
    TRACK = 'TRACK',
 | 
			
		||||
    TRACKSCAN = 'TRACKSCAN',
 | 
			
		||||
    USER = 'USER',
 | 
			
		||||
    USERGROUP = 'USERGROUP',
 | 
			
		||||
    USERPERMISSIONS = 'USERPERMISSIONS',
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								src/models/responses/IResponse.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/models/responses/IResponse.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
import { ResponseObjectType } from '../enums/ResponseObjectType';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the repsonse interface.
 | 
			
		||||
 * This forces all response classes to implement the interfaces properties.
 | 
			
		||||
*/
 | 
			
		||||
export interface IResponse {
 | 
			
		||||
    /**
 | 
			
		||||
    * The responseType.
 | 
			
		||||
    * This contains the type of class/entity this response contains.
 | 
			
		||||
    */
 | 
			
		||||
    responseType: ResponseObjectType;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,9 +1,18 @@
 | 
			
		||||
import { IsInt, IsString } from 'class-validator';
 | 
			
		||||
import { ResponseObjectType } from '../enums/ResponseObjectType';
 | 
			
		||||
import { IResponse } from './IResponse';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the repsonse auth.
 | 
			
		||||
*/
 | 
			
		||||
export class ResponseAuth {
 | 
			
		||||
export class ResponseAuth implements IResponse {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
    * The responseType.
 | 
			
		||||
    * This contains the type of class/entity this response contains.
 | 
			
		||||
    */
 | 
			
		||||
  responseType: ResponseObjectType = ResponseObjectType.AUTH;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * The access_token - JWT shortterm access token.
 | 
			
		||||
  */
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										42
									
								
								src/models/responses/ResponseDistanceDonation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/models/responses/ResponseDistanceDonation.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
import { IsInt, IsObject, IsPositive } from 'class-validator';
 | 
			
		||||
import { DistanceDonation } from '../entities/DistanceDonation';
 | 
			
		||||
import { ResponseObjectType } from '../enums/ResponseObjectType';
 | 
			
		||||
import { IResponse } from './IResponse';
 | 
			
		||||
import { ResponseDonation } from './ResponseDonation';
 | 
			
		||||
import { ResponseRunner } from './ResponseRunner';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the distance donation response.
 | 
			
		||||
*/
 | 
			
		||||
export class ResponseDistanceDonation extends ResponseDonation implements IResponse {
 | 
			
		||||
    /**
 | 
			
		||||
     * The responseType.
 | 
			
		||||
     * This contains the type of class/entity this response contains.
 | 
			
		||||
     */
 | 
			
		||||
    responseType: ResponseObjectType = ResponseObjectType.DISTANCEDONATION;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The donation's associated runner.
 | 
			
		||||
     * Used as the source of the donation's distance.
 | 
			
		||||
     */
 | 
			
		||||
    @IsObject()
 | 
			
		||||
    runner: ResponseRunner;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The donation's amount donated per distance.
 | 
			
		||||
     * The amount the donor set to be donated per kilometer that the runner ran.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsPositive()
 | 
			
		||||
    amountPerDistance: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a ResponseDistanceDonation object from a scan.
 | 
			
		||||
     * @param donation The distance donation the response shall be build for.
 | 
			
		||||
     */
 | 
			
		||||
    public constructor(donation: DistanceDonation) {
 | 
			
		||||
        super(donation);
 | 
			
		||||
        if (donation.runner) { this.runner = donation.runner.toResponse(); }
 | 
			
		||||
        this.amountPerDistance = donation.amountPerDistance;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								src/models/responses/ResponseDonation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/models/responses/ResponseDonation.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
import { IsInt, IsNotEmpty, IsPositive } from "class-validator";
 | 
			
		||||
import { Donation } from '../entities/Donation';
 | 
			
		||||
import { ResponseObjectType } from '../enums/ResponseObjectType';
 | 
			
		||||
import { IResponse } from './IResponse';
 | 
			
		||||
import { ResponseDonor } from './ResponseDonor';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the donation response.
 | 
			
		||||
*/
 | 
			
		||||
export class ResponseDonation implements IResponse {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
    * The responseType.
 | 
			
		||||
    * This contains the type of class/entity this response contains.
 | 
			
		||||
    */
 | 
			
		||||
    responseType: ResponseObjectType = ResponseObjectType.DONATION;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The donation's id.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsPositive()
 | 
			
		||||
    id: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The donation's donor.
 | 
			
		||||
     */
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    donor: ResponseDonor;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The donation's amount in the smalles unit of your currency (default: euro cent).
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    amount: 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();
 | 
			
		||||
        this.amount = donation.amount;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user