Compare commits
	
		
			243 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b3ce56c605 | |||
| 28cefa792c | |||
| 0803abc168 | |||
| 02ae883fa4 | |||
| be4050768e | |||
| dc6ec23cb9 | |||
| 1bb98c13d1 | |||
| bca979bab5 | |||
| e4fafd764c | |||
| 172159414b | |||
| 9355138a8c | |||
| 343cd8b772 | |||
| 01e0d5b94d | |||
| ac00667465 | |||
| 3deae2bfeb | |||
| 3f7b0f6563 | |||
| e6b9d4f273 | |||
| a00231dd3c | |||
| 3bc172e7e0 | |||
| ee9df21ae5 | |||
| f96b256ad3 | |||
| f2c50e929e | |||
| 02e3239848 | |||
| 8a54b027d0 | |||
| 3b11e896d4 | |||
| 89926b2c31 | |||
| 7b4e89555e | |||
| 1e37186247 | |||
| 154c763719 | |||
| 80197d5834 | |||
| 7e95103a2d | |||
| efe1a1f543 | |||
| 4fea690670 | |||
| f1dee1061d | |||
| 61cf0fc08d | |||
| 0c86e5dae1 | |||
| 638898fa28 | |||
| e7cd68e1c8 | |||
| e40e6faebd | |||
| 3d07aac944 | |||
| 1a5493facf | |||
| 9013b9492c | |||
| 188f26ad65 | |||
| 3ceb5a0c0f | |||
| e1ce052d3c | |||
| 70a379edef | |||
| 35ea3154d1 | |||
| ebf66821a2 | |||
| 8463bee253 | |||
| 860680d001 | |||
| df39166279 | |||
| 32fda46f0a | |||
| 36ecae7e6e | |||
| a5bfe4e3d5 | |||
| 4faeddc3f3 | |||
| 98f7bf366f | |||
| af3a9e5ce2 | |||
| 52eb7b1afe | |||
| 490fbd241d | |||
| f132131156 | |||
| c1e680a063 | |||
| c66b06c2c9 | |||
| 65e605cdc4 | |||
| d2fdb4efd9 | |||
| d0deb9d647 | |||
| 5495c90eaf | |||
| bf3ffae67c | |||
| aa0337ea33 | |||
| 4991d735bf | |||
| 398e61bddb | |||
| e6576f4a54 | |||
| c3b9e135b0 | |||
| 3bd4948c43 | |||
| f3cd1380be | |||
| a2c3dfbf85 | |||
| 3c37aafe1f | |||
| c591c182b3 | |||
| 9cc50078d1 | |||
| 7728759bcd | |||
| ce8fed350e | |||
| a005945e9e | |||
| cf86520fae | |||
| db6fdf6baf | |||
| 975ad50afc | |||
| 0c27df7754 | |||
| 102a860ba3 | |||
| 3a886714a0 | |||
| 09ab638239 | |||
| a4f88c78f4 | |||
| ccf2a3b617 | |||
| c8f941a779 | |||
| 5510cbb8e9 | |||
| a434173b54 | |||
| 7387f700fb | |||
| 4f01baaa23 | |||
| 09b37f0ff2 | |||
| 324d5709e3 | |||
| 3f23e4f1f1 | |||
| 9776a35f9f | |||
| 9b9ee70288 | |||
| 2628f69651 | |||
| b9c0a32862 | |||
| 82644a2ff4 | |||
| 3d2c93b5ac | |||
| c447114297 | |||
| 857de9ffcc | |||
| eea656bd7b | |||
| eec5284306 | |||
| 88a6a768c4 | |||
| edac1a224c | |||
| e67d1c5697 | |||
| 30502ec949 | |||
| a2c3913601 | |||
| f1c7713da2 | |||
| d6a41d5a82 | |||
| 72b5ca4153 | |||
| aeec2e1c32 | |||
| f9889bea3d | |||
| 2cad2ac2e9 | |||
| d948fe2631 | |||
| 2b5525323b | |||
| 58156e0d61 | |||
| a4b0dfe43e | |||
| ee2433a5ae | |||
| 2151b8502d | |||
| b57fde9b0a | |||
| 86706f9422 | |||
| 0687f268fc | |||
| bc426831db | |||
| 276e553e13 | |||
| e7ab302c61 | |||
| a5d70ce4b5 | |||
| d67be313e6 | |||
| 15d2d029dc | |||
| b6ea5e6549 | |||
| f378b0651a | |||
| 1a0573e0d0 | |||
| 9f103d8df1 | |||
| daa899a1ef | |||
| 59cb72a11d | |||
| 28c1b6d31d | |||
| dcb791c9a2 | |||
| 907259bf73 | |||
| 02f7ddbb37 | |||
| 63b1ca9b56 | |||
| 39857cf6e6 | |||
| 3090ae69f3 | |||
| 92186a86cc | |||
| 97e8470b0d | |||
| 6b0e3503a7 | |||
| 1e2de7656e | |||
| 56c6a7efb0 | |||
| 9c4e54fc6e | |||
| 2c47436259 | |||
| 9b5d16ae92 | |||
| deb13674b2 | |||
| 17c82ff409 | |||
| f9e314bf9f | |||
| e4c1930dd1 | |||
| b337ab424d | |||
| 82a0e194cb | |||
| 599296c4e3 | |||
| 2594a607dc | |||
| 335d4e24da | |||
| becc277123 | |||
| 52cdd41ec8 | |||
| 53548ba7a6 | |||
| 1dc438beb2 | |||
| c9ba69792f | |||
| ab67e5f4aa | |||
| 557608e318 | |||
| a83fedc9b8 | |||
| 61a17b198f | |||
| 3df1db4ad8 | |||
| e46cfa0d77 | |||
| 4126d31a5e | |||
| 9d9549cdd4 | |||
| eb40de6eb4 | |||
| 6efd09db73 | |||
| 3f09e3d387 | |||
| 05868e0e00 | |||
| 580a73f9a5 | |||
| ab7110d49f | |||
| 875781335c | |||
| 625340cf8a | |||
| 8d9dbc3957 | |||
| 07d813082b | |||
| a684f60252 | |||
| 931cae3c98 | |||
| dfd82a6293 | |||
| 82d4b11de3 | |||
| 75473937cf | |||
| a68bbab8ab | |||
| 5cfd2c9a52 | |||
| 6c7b31d76c | |||
| 2924ac2900 | |||
| a501625dd6 | |||
| cc64ce4498 | |||
| c9378e6cae | |||
| 62c7f26540 | |||
| 18e3ef9a79 | |||
| 395b0101a8 | |||
| 84a0bd2cd9 | |||
| 9cd181c5b8 | |||
| 41828a6e41 | |||
| 356e398caf | |||
| 6cb978df98 | |||
| 4cb0efa6bd | |||
| e0fa58da57 | |||
| 5d31d8d1a2 | |||
| 53a01ad977 | |||
| d7791756dc | |||
| dd48ee2f7e | |||
| ec64ec3d63 | |||
| 35dbfeb5e7 | |||
| a9ecfccfd2 | |||
| d850650aeb | |||
| 43e256f38c | |||
| b5f9cf201d | |||
| 6e121a3ce2 | |||
| 555e37eaf7 | |||
| 9675e79441 | |||
| 345851bf1d | |||
| 7c5a3893ef | |||
| b53b5cf91f | |||
| 04813173e4 | |||
| c4270b0839 | |||
| bb24ed53a4 | |||
| 1b74b21420 | |||
| b7cbe2a0b4 | |||
| 500b94b44a | |||
| 641466a731 | |||
| e3ea83bb47 | |||
| b6043744a9 | |||
| 2b38044271 | |||
| 4c3d2643c1 | |||
| e2cc0c0b80 | |||
| ce55dce011 | |||
| a738c19316 | |||
| 63b8176bdf | |||
| 6a762f570d | |||
| 1b7424f750 | |||
| bdd4f705be | 
							
								
								
									
										24
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								.drone.yml
									
									
									
									
									
								
							| @@ -11,7 +11,7 @@ steps: | ||||
|       - git checkout $DRONE_SOURCE_BRANCH | ||||
|       - mv .env.ci .env | ||||
|   - name: run tests | ||||
|     image: node:alpine | ||||
|     image: node:14.15.1-alpine3.12 | ||||
|     commands: | ||||
|       - yarn | ||||
|       - yarn test:ci | ||||
| @@ -37,11 +37,23 @@ steps: | ||||
|       tags: | ||||
|         - dev | ||||
|       registry: registry.odit.services | ||||
|     when: | ||||
|       branch: | ||||
|         - dev | ||||
|       event: | ||||
|         - push | ||||
|   - name: run full license export | ||||
|     depends_on: ["clone"] | ||||
|     image: node:14.15.1-alpine3.12 | ||||
|     commands: | ||||
|       - yarn | ||||
|       - yarn licenses:export | ||||
|   - name: push new licenses file to repo | ||||
|     depends_on: ["run full license export"] | ||||
|     image: appleboy/drone-git-push | ||||
|     settings: | ||||
|       branch: dev | ||||
|       commit: true | ||||
|       commit_message: new license file version [CI SKIP] | ||||
|       author_email: bot@odit.services | ||||
|       remote: git@git.odit.services:lfk/backend.git | ||||
|       ssh_key: | ||||
|         from_secret: GITLAB_SSHKEY | ||||
|  | ||||
| trigger: | ||||
|   branch: | ||||
|   | ||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -134,3 +134,5 @@ build | ||||
| *.sqlite-jurnal | ||||
| /docs | ||||
| lib | ||||
| /oss-attribution | ||||
| *.tmp | ||||
							
								
								
									
										1296
									
								
								licenses.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1296
									
								
								licenses.md
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										49
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "@odit/lfk-backend", | ||||
|   "version": "0.0.5", | ||||
|   "version": "0.0.11", | ||||
|   "main": "src/app.ts", | ||||
|   "repository": "https://git.odit.services/lfk/backend", | ||||
|   "author": { | ||||
| @@ -22,11 +22,11 @@ | ||||
|   ], | ||||
|   "license": "CC-BY-NC-SA-4.0", | ||||
|   "dependencies": { | ||||
|     "argon2": "^0.27.0", | ||||
|     "argon2": "^0.27.1", | ||||
|     "body-parser": "^1.19.0", | ||||
|     "class-transformer": "^0.3.1", | ||||
|     "class-validator": "^0.12.2", | ||||
|     "class-validator-jsonschema": "^2.0.3", | ||||
|     "class-validator-jsonschema": "^2.1.0", | ||||
|     "consola": "^2.15.0", | ||||
|     "cookie": "^0.4.1", | ||||
|     "cookie-parser": "^1.4.5", | ||||
| @@ -39,32 +39,34 @@ | ||||
|     "pg": "^8.5.1", | ||||
|     "reflect-metadata": "^0.1.13", | ||||
|     "routing-controllers": "^0.9.0-alpha.6", | ||||
|     "routing-controllers-openapi": "^2.1.0", | ||||
|     "sqlite3": "^5.0.0", | ||||
|     "routing-controllers-openapi": "^2.2.0", | ||||
|     "sqlite3": "5.0.0", | ||||
|     "typeorm": "^0.2.29", | ||||
|     "typeorm-routing-controllers-extensions": "^0.2.0", | ||||
|     "typeorm-seeding": "^1.6.1", | ||||
|     "uuid": "^8.3.1", | ||||
|     "uuid": "^8.3.2", | ||||
|     "validator": "^13.5.2" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@types/cors": "^2.8.8", | ||||
|     "@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/jsonwebtoken": "^8.5.0", | ||||
|     "@types/node": "^14.14.9", | ||||
|     "@types/node": "^14.14.20", | ||||
|     "@types/uuid": "^8.3.0", | ||||
|     "axios": "^0.21.0", | ||||
|     "axios": "^0.21.1", | ||||
|     "cp-cli": "^2.0.0", | ||||
|     "jest": "^26.6.3", | ||||
|     "nodemon": "^2.0.6", | ||||
|     "rimraf": "^2.7.1", | ||||
|     "start-server-and-test": "^1.11.6", | ||||
|     "nodemon": "^2.0.7", | ||||
|     "release-it": "^14.2.2", | ||||
|     "rimraf": "^3.0.2", | ||||
|     "start-server-and-test": "^1.11.7", | ||||
|     "ts-jest": "^26.4.4", | ||||
|     "ts-node": "^9.0.0", | ||||
|     "typedoc": "^0.19.2", | ||||
|     "typescript": "^4.1.2" | ||||
|     "ts-node": "^9.1.1", | ||||
|     "typedoc": "^0.20.14", | ||||
|     "typescript": "^4.1.3" | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "dev": "nodemon src/app.ts", | ||||
| @@ -74,7 +76,22 @@ | ||||
|     "test:watch": "jest --watchAll", | ||||
|     "test:ci": "start-server-and-test dev http://localhost:4010/api/docs/openapi.json test", | ||||
|     "seed": "ts-node ./node_modules/typeorm/cli.js schema:sync && ts-node ./node_modules/typeorm-seeding/dist/cli.js seed", | ||||
|     "openapi:export": "ts-node src/openapi_export.ts" | ||||
|     "openapi:export": "ts-node scripts/openapi_export.ts", | ||||
|     "licenses:export": "license-exporter --md", | ||||
|     "release": "release-it" | ||||
|   }, | ||||
|   "release-it": { | ||||
|     "git": { | ||||
|       "requireCleanWorkingDir": false, | ||||
|       "requireBranch": "main", | ||||
|       "push": false, | ||||
|       "tag": true, | ||||
|       "tagName": "v${version}", | ||||
|       "tagAnnotation": "v${version}" | ||||
|     }, | ||||
|     "npm": { | ||||
|       "publish": false | ||||
|     } | ||||
|   }, | ||||
|   "nodemonConfig": { | ||||
|     "ignore": [ | ||||
|   | ||||
							
								
								
									
										34
									
								
								scripts/openapi_export.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								scripts/openapi_export.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| import { validationMetadatasToSchemas } from 'class-validator-jsonschema'; | ||||
| import consola from "consola"; | ||||
| import fs from "fs"; | ||||
| import "reflect-metadata"; | ||||
| import { createExpressServer, getMetadataArgsStorage } from "routing-controllers"; | ||||
| import { generateSpec } from '../src/apispec'; | ||||
| import { config } from '../src/config'; | ||||
| import authchecker from "../src/middlewares/authchecker"; | ||||
| import { ErrorHandler } from '../src/middlewares/ErrorHandler'; | ||||
|  | ||||
| const CONTROLLERS_FILE_EXTENSION = process.env.NODE_ENV === 'production' ? 'js' : 'ts'; | ||||
| createExpressServer({ | ||||
|     authorizationChecker: authchecker, | ||||
|     middlewares: [ErrorHandler], | ||||
|     development: config.development, | ||||
|     cors: true, | ||||
|     routePrefix: "/api", | ||||
|     controllers: [`${__dirname}/../src/controllers/*.${CONTROLLERS_FILE_EXTENSION}`], | ||||
| }); | ||||
|  | ||||
| const storage = getMetadataArgsStorage(); | ||||
| const schemas = validationMetadatasToSchemas({ | ||||
|     refPointerPrefix: "#/components/schemas/", | ||||
| }); | ||||
|  | ||||
| //Spec creation based on the previously created schemas | ||||
| const spec = generateSpec(storage, schemas); | ||||
|  | ||||
| try { | ||||
|     fs.writeFileSync("./openapi.json", JSON.stringify(spec), { encoding: "utf-8" }); | ||||
|     consola.success("Exported openapi spec to openapi.json"); | ||||
| } catch (error) { | ||||
|     consola.error("Couldn't export the openapi spec"); | ||||
| } | ||||
							
								
								
									
										50
									
								
								src/apispec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/apispec.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| import { MetadataArgsStorage } from 'routing-controllers'; | ||||
| import { routingControllersToSpec } from 'routing-controllers-openapi'; | ||||
|  | ||||
| /** | ||||
|  * This function generates a the openapi spec from route metadata and type schemas. | ||||
|  * @param storage MetadataArgsStorage object generated by routing-controllers. | ||||
|  * @param schemas MetadataArgsStorage object generated by class-validator-jsonschema. | ||||
|  */ | ||||
| export function generateSpec(storage: MetadataArgsStorage, schemas) { | ||||
|     return routingControllersToSpec( | ||||
|         storage, | ||||
|         { | ||||
|             routePrefix: "/api" | ||||
|         }, | ||||
|         { | ||||
|             components: { | ||||
|                 schemas, | ||||
|                 "securitySchemes": { | ||||
|                     "AuthToken": { | ||||
|                         "type": "http", | ||||
|                         "scheme": "bearer", | ||||
|                         "bearerFormat": "JWT", | ||||
|                         description: "A JWT based access token. Use /api/auth/login or /api/auth/refresh to get one." | ||||
|                     }, | ||||
|                     "RefreshTokenCookie": { | ||||
|                         "type": "apiKey", | ||||
|                         "in": "cookie", | ||||
|                         "name": "lfk_backend__refresh_token", | ||||
|                         description: "A cookie containing a JWT based refreh token. Attention: Doesn't work in swagger-ui. Use /api/auth/login or /api/auth/refresh to get one." | ||||
|                     }, | ||||
|                     "StatsApiToken": { | ||||
|                         "type": "http", | ||||
|                         "scheme": "bearer", | ||||
|                         description: "Api token that can be obtained by creating a new stats client (post to /api/statsclients). Only valid for obtaining stats." | ||||
|                     }, | ||||
|                     "StationApiToken": { | ||||
|                         "type": "http", | ||||
|                         "scheme": "bearer", | ||||
|                         description: "Api token that can be obtained by creating a new scan station (post to /api/stations). Only valid for creating scans." | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             info: { | ||||
|                 description: "The the backend API for the LfK! runner system.", | ||||
|                 title: "LfK! Backend API", | ||||
|                 version: process.env.npm_package_version | ||||
|             }, | ||||
|         } | ||||
|     ); | ||||
| } | ||||
| @@ -1,9 +1,9 @@ | ||||
| import consola from "consola"; | ||||
| import "reflect-metadata"; | ||||
| import { createExpressServer } from "routing-controllers"; | ||||
| import authchecker from "./authchecker"; | ||||
| import { config, e as errors } from './config'; | ||||
| import loaders from "./loaders/index"; | ||||
| import authchecker from "./middlewares/authchecker"; | ||||
| import { ErrorHandler } from './middlewares/ErrorHandler'; | ||||
|  | ||||
| const CONTROLLERS_FILE_EXTENSION = process.env.NODE_ENV === 'production' ? 'js' : 'ts'; | ||||
|   | ||||
| @@ -2,12 +2,12 @@ import { Body, CookieParam, JsonController, Param, Post, Req, Res } from 'routin | ||||
| 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/CreateAuth'; | ||||
| import { CreateResetToken } from '../models/actions/CreateResetToken'; | ||||
| 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 { Auth } from '../models/responses/ResponseAuth'; | ||||
| import { ResponseAuth } from '../models/responses/ResponseAuth'; | ||||
| import { Logout } from '../models/responses/ResponseLogout'; | ||||
|  | ||||
| @JsonController('/auth') | ||||
| @@ -16,7 +16,7 @@ export class AuthController { | ||||
| 	} | ||||
|  | ||||
| 	@Post("/login") | ||||
| 	@ResponseSchema(Auth) | ||||
| 	@ResponseSchema(ResponseAuth) | ||||
| 	@ResponseSchema(InvalidCredentialsError) | ||||
| 	@ResponseSchema(UserNotFoundError) | ||||
| 	@ResponseSchema(UsernameOrEmailNeededError) | ||||
| @@ -60,7 +60,7 @@ export class AuthController { | ||||
| 	} | ||||
|  | ||||
| 	@Post("/refresh") | ||||
| 	@ResponseSchema(Auth) | ||||
| 	@ResponseSchema(ResponseAuth) | ||||
| 	@ResponseSchema(JwtNotProvidedError) | ||||
| 	@ResponseSchema(IllegalJWTError) | ||||
| 	@ResponseSchema(UserNotFoundError) | ||||
| @@ -70,7 +70,6 @@ export class AuthController { | ||||
| 		if (refresh_token && refresh_token.length != 0 && refreshAuth.token == undefined) { | ||||
| 			refreshAuth.token = refresh_token; | ||||
| 		} | ||||
| 		console.log(req.headers) | ||||
| 		let auth; | ||||
| 		try { | ||||
| 			auth = await refreshAuth.toAuth(); | ||||
| @@ -83,7 +82,7 @@ export class AuthController { | ||||
| 	} | ||||
|  | ||||
| 	@Post("/reset") | ||||
| 	@ResponseSchema(Auth) | ||||
| 	@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}." }) | ||||
| @@ -93,7 +92,7 @@ export class AuthController { | ||||
| 	} | ||||
|  | ||||
| 	@Post("/reset/:token") | ||||
| 	@ResponseSchema(Auth) | ||||
| 	@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." }) | ||||
|   | ||||
							
								
								
									
										105
									
								
								src/controllers/DonorController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/controllers/DonorController.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| 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 { 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'; | ||||
|  | ||||
| @JsonController('/donors') | ||||
| @OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
| export class DonorController { | ||||
| 	private donorRepository: Repository<Donor>; | ||||
|  | ||||
| 	/** | ||||
| 	 * Gets the repository of this controller's model/entity. | ||||
| 	 */ | ||||
| 	constructor() { | ||||
| 		this.donorRepository = getConnectionManager().get().getRepository(Donor); | ||||
| 	} | ||||
|  | ||||
| 	@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.' }) | ||||
| 	async getAll() { | ||||
| 		let responseDonors: ResponseDonor[] = new Array<ResponseDonor>(); | ||||
| 		const donors = await this.donorRepository.find(); | ||||
| 		donors.forEach(donor => { | ||||
| 			responseDonors.push(new ResponseDonor(donor)); | ||||
| 		}); | ||||
| 		return responseDonors; | ||||
| 	} | ||||
|  | ||||
| 	@Get('/:id') | ||||
| 	@Authorized("DONOR:GET") | ||||
| 	@ResponseSchema(ResponseDonor) | ||||
| 	@ResponseSchema(DonorNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(DonorNotFoundError) | ||||
| 	@OpenAPI({ description: 'Lists all information about the runner whose id got provided.' }) | ||||
| 	async getOne(@Param('id') id: number) { | ||||
| 		let donor = await this.donorRepository.findOne({ id: id }) | ||||
| 		if (!donor) { throw new DonorNotFoundError(); } | ||||
| 		return new ResponseDonor(donor); | ||||
| 	} | ||||
|  | ||||
| 	@Post() | ||||
| 	@Authorized("DONOR:CREATE") | ||||
| 	@ResponseSchema(ResponseDonor) | ||||
| 	@OpenAPI({ description: 'Create a new runner. <br> Please remeber to provide the runner\'s group\'s id.' }) | ||||
| 	async post(@Body({ validate: true }) createRunner: CreateDonor) { | ||||
| 		let donor; | ||||
| 		try { | ||||
| 			donor = await createRunner.toEntity(); | ||||
| 		} catch (error) { | ||||
| 			throw error; | ||||
| 		} | ||||
|  | ||||
| 		donor = await this.donorRepository.save(donor) | ||||
| 		return new ResponseDonor(await this.donorRepository.findOne(donor)); | ||||
| 	} | ||||
|  | ||||
| 	@Put('/:id') | ||||
| 	@Authorized("DONOR:UPDATE") | ||||
| 	@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." }) | ||||
| 	async put(@Param('id') id: number, @Body({ validate: true }) donor: UpdateDonor) { | ||||
| 		let oldDonor = await this.donorRepository.findOne({ id: id }); | ||||
|  | ||||
| 		if (!oldDonor) { | ||||
| 			throw new DonorNotFoundError(); | ||||
| 		} | ||||
|  | ||||
| 		if (oldDonor.id != donor.id) { | ||||
| 			throw new DonorIdsNotMatchingError(); | ||||
| 		} | ||||
|  | ||||
| 		await this.donorRepository.save(await donor.update(oldDonor)); | ||||
| 		return new ResponseDonor(await this.donorRepository.findOne({ id: id })); | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/:id') | ||||
| 	@Authorized("DONOR:DELETE") | ||||
| 	@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).' }) | ||||
| 	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); | ||||
|  | ||||
| 		if (!donor) { | ||||
| 			throw new DonorNotFoundError(); | ||||
| 		} | ||||
|  | ||||
| 		//TODO: DELETE DONATIONS AND WARN FOR FORCE (https://git.odit.services/lfk/backend/issues/66) | ||||
|  | ||||
| 		await this.donorRepository.delete(donor); | ||||
| 		return new ResponseDonor(responseDonor); | ||||
| 	} | ||||
| } | ||||
| @@ -3,8 +3,8 @@ import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { PermissionIdsNotMatchingError, PermissionNeedsPrincipalError, PermissionNotFoundError } from '../errors/PermissionErrors'; | ||||
| import { PrincipalNotFoundError } from '../errors/PrincipalErrors'; | ||||
| import { CreatePermission } from '../models/actions/CreatePermission'; | ||||
| import { UpdatePermission } from '../models/actions/UpdatePermission'; | ||||
| import { CreatePermission } from '../models/actions/create/CreatePermission'; | ||||
| import { UpdatePermission } from '../models/actions/update/UpdatePermission'; | ||||
| import { Permission } from '../models/entities/Permission'; | ||||
| import { ResponseEmpty } from '../models/responses/ResponseEmpty'; | ||||
| import { ResponsePermission } from '../models/responses/ResponsePermission'; | ||||
| @@ -58,7 +58,7 @@ export class PermissionController { | ||||
|     async post(@Body({ validate: true }) createPermission: CreatePermission) { | ||||
|         let permission; | ||||
|         try { | ||||
|             permission = await createPermission.toPermission(); | ||||
|             permission = await createPermission.toEntity(); | ||||
|         } catch (error) { | ||||
|             throw error; | ||||
|         } | ||||
| @@ -96,7 +96,7 @@ export class PermissionController { | ||||
|             return new ResponsePermission(existingPermission); | ||||
|         } | ||||
|  | ||||
|         await this.permissionRepository.save(await permission.updatePermission(oldPermission)); | ||||
|         await this.permissionRepository.save(await permission.update(oldPermission)); | ||||
|  | ||||
|         return new ResponsePermission(await this.permissionRepository.findOne({ id: permission.id }, { relations: ['principal'] })); | ||||
|     } | ||||
|   | ||||
							
								
								
									
										106
									
								
								src/controllers/RunnerCardController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								src/controllers/RunnerCardController.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { RunnerCardHasScansError, RunnerCardIdsNotMatchingError, RunnerCardNotFoundError } from '../errors/RunnerCardErrors'; | ||||
| import { RunnerNotFoundError } from '../errors/RunnerErrors'; | ||||
| import { CreateRunnerCard } from '../models/actions/create/CreateRunnerCard'; | ||||
| import { UpdateRunnerCard } from '../models/actions/update/UpdateRunnerCard'; | ||||
| import { RunnerCard } from '../models/entities/RunnerCard'; | ||||
| import { ResponseEmpty } from '../models/responses/ResponseEmpty'; | ||||
| import { ResponseRunnerCard } from '../models/responses/ResponseRunnerCard'; | ||||
| import { ScanController } from './ScanController'; | ||||
|  | ||||
| @JsonController('/cards') | ||||
| @OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
| export class RunnerCardController { | ||||
| 	private cardRepository: Repository<RunnerCard>; | ||||
|  | ||||
| 	/** | ||||
| 	 * Gets the repository of this controller's model/entity. | ||||
| 	 */ | ||||
| 	constructor() { | ||||
| 		this.cardRepository = getConnectionManager().get().getRepository(RunnerCard); | ||||
| 	} | ||||
|  | ||||
| 	@Get() | ||||
| 	@Authorized("CARD:GET") | ||||
| 	@ResponseSchema(ResponseRunnerCard, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all card.' }) | ||||
| 	async getAll() { | ||||
| 		let responseCards: ResponseRunnerCard[] = new Array<ResponseRunnerCard>(); | ||||
| 		const cards = await this.cardRepository.find({ relations: ['runner'] }); | ||||
| 		cards.forEach(card => { | ||||
| 			responseCards.push(new ResponseRunnerCard(card)); | ||||
| 		}); | ||||
| 		return responseCards; | ||||
| 	} | ||||
|  | ||||
| 	@Get('/:id') | ||||
| 	@Authorized("CARD:GET") | ||||
| 	@ResponseSchema(ResponseRunnerCard) | ||||
| 	@ResponseSchema(RunnerCardNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(RunnerCardNotFoundError) | ||||
| 	@OpenAPI({ description: "Lists all information about the card whose id got provided." }) | ||||
| 	async getOne(@Param('id') id: number) { | ||||
| 		let card = await this.cardRepository.findOne({ id: id }, { relations: ['runner'] }); | ||||
| 		if (!card) { throw new RunnerCardNotFoundError(); } | ||||
| 		return card.toResponse(); | ||||
| 	} | ||||
|  | ||||
| 	@Post() | ||||
| 	@Authorized("CARD:CREATE") | ||||
| 	@ResponseSchema(ResponseRunnerCard) | ||||
| 	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) | ||||
| 	@OpenAPI({ description: "Create a new card. <br> You can provide a associated runner by id but you don't have to." }) | ||||
| 	async post(@Body({ validate: true }) createCard: CreateRunnerCard) { | ||||
| 		let card = await createCard.toEntity(); | ||||
| 		card = await this.cardRepository.save(card); | ||||
| 		return (await this.cardRepository.findOne({ id: card.id }, { relations: ['runner'] })).toResponse(); | ||||
| 	} | ||||
|  | ||||
| 	@Put('/:id') | ||||
| 	@Authorized("CARD:UPDATE") | ||||
| 	@ResponseSchema(ResponseRunnerCard) | ||||
| 	@ResponseSchema(RunnerCardNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(RunnerCardIdsNotMatchingError, { statusCode: 406 }) | ||||
| 	@OpenAPI({ description: "Update the card whose id you provided. <br> Scans created via this card will still be associated with the old runner. <br> Please remember that ids can't be changed." }) | ||||
| 	async put(@Param('id') id: number, @Body({ validate: true }) card: UpdateRunnerCard) { | ||||
| 		let oldCard = await this.cardRepository.findOne({ id: id }); | ||||
|  | ||||
| 		if (!oldCard) { | ||||
| 			throw new RunnerCardNotFoundError(); | ||||
| 		} | ||||
|  | ||||
| 		if (oldCard.id != card.id) { | ||||
| 			throw new RunnerCardIdsNotMatchingError(); | ||||
| 		} | ||||
|  | ||||
| 		await this.cardRepository.save(await card.update(oldCard)); | ||||
| 		return (await this.cardRepository.findOne({ id: id }, { relations: ['runner'] })).toResponse(); | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/:id') | ||||
| 	@Authorized("CARD:DELETE") | ||||
| 	@ResponseSchema(ResponseRunnerCard) | ||||
| 	@ResponseSchema(ResponseEmpty, { statusCode: 204 }) | ||||
| 	@ResponseSchema(RunnerCardHasScansError, { statusCode: 406 }) | ||||
| 	@OnUndefined(204) | ||||
| 	@OpenAPI({ description: "Delete the card whose id you provided. <br> If no card with this id exists it will just return 204(no content). <br> If the card still has scans associated you have to provide the force=true query param (warning: this deletes all scans associated with by this card - please disable it instead or just remove the runner association)." }) | ||||
| 	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { | ||||
| 		let card = await this.cardRepository.findOne({ id: id }); | ||||
| 		if (!card) { return null; } | ||||
|  | ||||
| 		const cardScans = (await this.cardRepository.findOne({ id: id }, { relations: ["scans"] })).scans; | ||||
| 		if (cardScans.length != 0 && !force) { | ||||
| 			throw new RunnerCardHasScansError(); | ||||
| 		} | ||||
| 		const scanController = new ScanController; | ||||
| 		for (let scan of cardScans) { | ||||
| 			await scanController.remove(scan.id, force); | ||||
| 		} | ||||
|  | ||||
| 		await this.cardRepository.delete(card); | ||||
| 		return card.toResponse(); | ||||
| 	} | ||||
| } | ||||
| @@ -3,11 +3,13 @@ import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { RunnerGroupNeededError, RunnerIdsNotMatchingError, RunnerNotFoundError } from '../errors/RunnerErrors'; | ||||
| import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors'; | ||||
| import { CreateRunner } from '../models/actions/CreateRunner'; | ||||
| import { UpdateRunner } from '../models/actions/UpdateRunner'; | ||||
| 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 { RunnerCardController } from './RunnerCardController'; | ||||
| import { ScanController } from './ScanController'; | ||||
|  | ||||
| @JsonController('/runners') | ||||
| @OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
| @@ -27,7 +29,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'] }); | ||||
| 		const runners = await this.runnerRepository.find({ relations: ['scans', 'group', 'scans.track', 'cards'] }); | ||||
| 		runners.forEach(runner => { | ||||
| 			responseRunners.push(new ResponseRunner(runner)); | ||||
| 		}); | ||||
| @@ -41,7 +43,7 @@ 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'] }) | ||||
| 		let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'scans.track', 'cards'] }) | ||||
| 		if (!runner) { throw new RunnerNotFoundError(); } | ||||
| 		return new ResponseRunner(runner); | ||||
| 	} | ||||
| @@ -55,13 +57,13 @@ export class RunnerController { | ||||
| 	async post(@Body({ validate: true }) createRunner: CreateRunner) { | ||||
| 		let runner; | ||||
| 		try { | ||||
| 			runner = await createRunner.toRunner(); | ||||
| 			runner = await createRunner.toEntity(); | ||||
| 		} catch (error) { | ||||
| 			throw error; | ||||
| 		} | ||||
|  | ||||
| 		runner = await this.runnerRepository.save(runner) | ||||
| 		return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group'] })); | ||||
| 		return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'scans.track', 'cards'] })); | ||||
| 	} | ||||
|  | ||||
| 	@Put('/:id') | ||||
| @@ -81,8 +83,8 @@ export class RunnerController { | ||||
| 			throw new RunnerIdsNotMatchingError(); | ||||
| 		} | ||||
|  | ||||
| 		await this.runnerRepository.save(await runner.updateRunner(oldRunner)); | ||||
| 		return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] })); | ||||
| 		await this.runnerRepository.save(await runner.update(oldRunner)); | ||||
| 		return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'scans.track', 'cards'] })); | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/:id') | ||||
| @@ -90,16 +92,28 @@ export class RunnerController { | ||||
| 	@ResponseSchema(ResponseRunner) | ||||
| 	@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 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'] }); | ||||
| 		const responseRunner = await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'scans.track', 'cards'] }); | ||||
|  | ||||
| 		if (!runner) { | ||||
| 			throw new RunnerNotFoundError(); | ||||
| 		} | ||||
|  | ||||
| 		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); | ||||
| 		} | ||||
|  | ||||
| 		const runnerScans = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["scans"] })).scans; | ||||
| 		const scanController = new ScanController; | ||||
| 		for (let scan of runnerScans) { | ||||
| 			await scanController.remove(scan.id, force); | ||||
| 		} | ||||
|  | ||||
| 		await this.runnerRepository.delete(runner); | ||||
| 		return new ResponseRunner(responseRunner); | ||||
| 	} | ||||
|   | ||||
| @@ -2,8 +2,8 @@ import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post | ||||
| 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/CreateRunnerOrganisation'; | ||||
| import { UpdateRunnerOrganisation } from '../models/actions/UpdateRunnerOrganisation'; | ||||
| 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'; | ||||
| @@ -55,7 +55,7 @@ export class RunnerOrganisationController { | ||||
| 	async post(@Body({ validate: true }) createRunnerOrganisation: CreateRunnerOrganisation) { | ||||
| 		let runnerOrganisation; | ||||
| 		try { | ||||
| 			runnerOrganisation = await createRunnerOrganisation.toRunnerOrganisation(); | ||||
| 			runnerOrganisation = await createRunnerOrganisation.toEntity(); | ||||
| 		} catch (error) { | ||||
| 			throw error; | ||||
| 		} | ||||
| @@ -82,7 +82,7 @@ export class RunnerOrganisationController { | ||||
| 			throw new RunnerOrganisationIdsNotMatchingError(); | ||||
| 		} | ||||
|  | ||||
| 		await this.runnerOrganisationRepository.save(await updateOrganisation.updateRunnerOrganisation(oldRunnerOrganisation)); | ||||
| 		await this.runnerOrganisationRepository.save(await updateOrganisation.update(oldRunnerOrganisation)); | ||||
|  | ||||
| 		return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(id, { relations: ['address', 'contact', 'teams'] })); | ||||
| 	} | ||||
|   | ||||
| @@ -2,8 +2,8 @@ import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { RunnerTeamHasRunnersError, RunnerTeamIdsNotMatchingError, RunnerTeamNotFoundError } from '../errors/RunnerTeamErrors'; | ||||
| import { CreateRunnerTeam } from '../models/actions/CreateRunnerTeam'; | ||||
| import { UpdateRunnerTeam } from '../models/actions/UpdateRunnerTeam'; | ||||
| 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 { ResponseRunnerTeam } from '../models/responses/ResponseRunnerTeam'; | ||||
| @@ -54,7 +54,7 @@ export class RunnerTeamController { | ||||
| 	async post(@Body({ validate: true }) createRunnerTeam: CreateRunnerTeam) { | ||||
| 		let runnerTeam; | ||||
| 		try { | ||||
| 			runnerTeam = await createRunnerTeam.toRunnerTeam(); | ||||
| 			runnerTeam = await createRunnerTeam.toEntity(); | ||||
| 		} catch (error) { | ||||
| 			throw error; | ||||
| 		} | ||||
| @@ -82,7 +82,7 @@ export class RunnerTeamController { | ||||
| 			throw new RunnerTeamIdsNotMatchingError(); | ||||
| 		} | ||||
|  | ||||
| 		await this.runnerTeamRepository.save(await runnerTeam.updateRunnerTeam(oldRunnerTeam)); | ||||
| 		await this.runnerTeamRepository.save(await runnerTeam.update(oldRunnerTeam)); | ||||
|  | ||||
| 		return new ResponseRunnerTeam(await this.runnerTeamRepository.findOne({ id: runnerTeam.id }, { relations: ['parentGroup', 'contact'] })); | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										140
									
								
								src/controllers/ScanController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								src/controllers/ScanController.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,140 @@ | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam, UseBefore } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { RunnerNotFoundError } from '../errors/RunnerErrors'; | ||||
| import { ScanIdsNotMatchingError, ScanNotFoundError } from '../errors/ScanErrors'; | ||||
| import { ScanStationNotFoundError } from '../errors/ScanStationErrors'; | ||||
| import ScanAuth from '../middlewares/ScanAuth'; | ||||
| import { CreateScan } from '../models/actions/create/CreateScan'; | ||||
| import { CreateTrackScan } from '../models/actions/create/CreateTrackScan'; | ||||
| import { UpdateScan } from '../models/actions/update/UpdateScan'; | ||||
| import { UpdateTrackScan } from '../models/actions/update/UpdateTrackScan'; | ||||
| import { Scan } from '../models/entities/Scan'; | ||||
| import { TrackScan } from '../models/entities/TrackScan'; | ||||
| import { ResponseEmpty } from '../models/responses/ResponseEmpty'; | ||||
| import { ResponseScan } from '../models/responses/ResponseScan'; | ||||
| import { ResponseTrackScan } from '../models/responses/ResponseTrackScan'; | ||||
|  | ||||
| @JsonController('/scans') | ||||
| @OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
| export class ScanController { | ||||
| 	private scanRepository: Repository<Scan>; | ||||
| 	private trackScanRepository: Repository<TrackScan>; | ||||
|  | ||||
| 	/** | ||||
| 	 * Gets the repository of this controller's model/entity. | ||||
| 	 */ | ||||
| 	constructor() { | ||||
| 		this.scanRepository = getConnectionManager().get().getRepository(Scan); | ||||
| 		this.trackScanRepository = getConnectionManager().get().getRepository(TrackScan); | ||||
| 	} | ||||
|  | ||||
| 	@Get() | ||||
| 	@Authorized("SCAN:GET") | ||||
| 	@ResponseSchema(ResponseScan, { isArray: true }) | ||||
| 	@ResponseSchema(ResponseTrackScan, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all scans (normal or track) from all runners. <br> This includes the scan\'s runner\'s distance ran.' }) | ||||
| 	async getAll() { | ||||
| 		let responseScans: ResponseScan[] = new Array<ResponseScan>(); | ||||
| 		const scans = await this.scanRepository.find({ relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] }); | ||||
| 		scans.forEach(scan => { | ||||
| 			responseScans.push(scan.toResponse()); | ||||
| 		}); | ||||
| 		return responseScans; | ||||
| 	} | ||||
|  | ||||
| 	@Get('/:id') | ||||
| 	@Authorized("SCAN:GET") | ||||
| 	@ResponseSchema(ResponseScan) | ||||
| 	@ResponseSchema(ResponseTrackScan) | ||||
| 	@ResponseSchema(ScanNotFoundError, { statusCode: 404 }) | ||||
| 	@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'] }) | ||||
| 		if (!scan) { throw new ScanNotFoundError(); } | ||||
| 		return scan.toResponse(); | ||||
| 	} | ||||
|  | ||||
| 	@Post() | ||||
| 	@UseBefore(ScanAuth) | ||||
| 	@ResponseSchema(ResponseScan) | ||||
| 	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) | ||||
| 	@OpenAPI({ description: 'Create a new scan (not track scan - use /scans/trackscans instead). <br> Please rmemember to provide the scan\'s runner\'s id and distance.', security: [{ "ScanApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
| 	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(); | ||||
| 	} | ||||
|  | ||||
| 	@Post("/trackscans") | ||||
| 	@UseBefore(ScanAuth) | ||||
| 	@ResponseSchema(ResponseTrackScan) | ||||
| 	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) | ||||
| 	@OpenAPI({ description: 'Create a new track scan (for "normal" scans use /scans instead). <br> Please remember that to provide the scan\'s card\'s station\'s id.', security: [{ "ScanApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
| 	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(); | ||||
| 	} | ||||
|  | ||||
| 	@Put('/:id') | ||||
| 	@Authorized("SCAN:UPDATE") | ||||
| 	@ResponseSchema(ResponseScan) | ||||
| 	@ResponseSchema(ScanNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(ScanIdsNotMatchingError, { statusCode: 406 }) | ||||
| 	@OpenAPI({ description: "Update the scan (not track scan use /scans/trackscans/:id instead) whose id you provided. <br> Please remember that ids can't be changed and distances must be positive." }) | ||||
| 	async put(@Param('id') id: number, @Body({ validate: true }) scan: UpdateScan) { | ||||
| 		let oldScan = await this.scanRepository.findOne({ id: id }); | ||||
|  | ||||
| 		if (!oldScan) { | ||||
| 			throw new ScanNotFoundError(); | ||||
| 		} | ||||
|  | ||||
| 		if (oldScan.id != scan.id) { | ||||
| 			throw new ScanIdsNotMatchingError(); | ||||
| 		} | ||||
|  | ||||
| 		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(); | ||||
| 	} | ||||
|  | ||||
| 	@Put('/trackscans/:id') | ||||
| 	@Authorized("SCAN:UPDATE") | ||||
| 	@ResponseSchema(ResponseTrackScan) | ||||
| 	@ResponseSchema(ScanNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(ScanIdsNotMatchingError, { statusCode: 406 }) | ||||
| 	@OpenAPI({ description: 'Update the track scan (not "normal" scan use /scans/trackscans/:id instead) whose id you provided. <br> Please remember that only the validity, runner and track can be changed.' }) | ||||
| 	async putTrackScan(@Param('id') id: number, @Body({ validate: true }) scan: UpdateTrackScan) { | ||||
| 		let oldScan = await this.trackScanRepository.findOne({ id: id }); | ||||
|  | ||||
| 		if (!oldScan) { | ||||
| 			throw new ScanNotFoundError(); | ||||
| 		} | ||||
|  | ||||
| 		if (oldScan.id != scan.id) { | ||||
| 			throw new ScanIdsNotMatchingError(); | ||||
| 		} | ||||
|  | ||||
| 		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(); | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/:id') | ||||
| 	@Authorized("SCAN:DELETE") | ||||
| 	@ResponseSchema(ResponseScan) | ||||
| 	@ResponseSchema(ResponseEmpty, { statusCode: 204 }) | ||||
| 	@OnUndefined(204) | ||||
| 	@OpenAPI({ description: 'Delete the scan whose id you provided. <br> If no scan with this id exists it will just return 204(no content).' }) | ||||
| 	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'] }); | ||||
|  | ||||
| 		await this.scanRepository.delete(scan); | ||||
| 		return responseScan.toResponse(); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										108
									
								
								src/controllers/ScanStationController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								src/controllers/ScanStationController.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,108 @@ | ||||
| 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 { ScanStationHasScansError, ScanStationIdsNotMatchingError, ScanStationNotFoundError } from '../errors/ScanStationErrors'; | ||||
| import { TrackNotFoundError } from '../errors/TrackErrors'; | ||||
| import { CreateScanStation } from '../models/actions/create/CreateScanStation'; | ||||
| import { UpdateScanStation } from '../models/actions/update/UpdateScanStation'; | ||||
| import { ScanStation } from '../models/entities/ScanStation'; | ||||
| import { ResponseEmpty } from '../models/responses/ResponseEmpty'; | ||||
| import { ResponseScanStation } from '../models/responses/ResponseScanStation'; | ||||
| import { ScanController } from './ScanController'; | ||||
|  | ||||
| @JsonController('/stations') | ||||
| @OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
| export class ScanStationController { | ||||
| 	private stationRepository: Repository<ScanStation>; | ||||
|  | ||||
| 	/** | ||||
| 	 * Gets the repository of this controller's model/entity. | ||||
| 	 */ | ||||
| 	constructor() { | ||||
| 		this.stationRepository = getConnectionManager().get().getRepository(ScanStation); | ||||
| 	} | ||||
|  | ||||
| 	@Get() | ||||
| 	@Authorized("STATION:GET") | ||||
| 	@ResponseSchema(ResponseScanStation, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all stations. <br> This includes their associated tracks.' }) | ||||
| 	async getAll() { | ||||
| 		let responseStations: ResponseScanStation[] = new Array<ResponseScanStation>(); | ||||
| 		const stations = await this.stationRepository.find({ relations: ['track'] }); | ||||
| 		stations.forEach(station => { | ||||
| 			responseStations.push(station.toResponse()); | ||||
| 		}); | ||||
| 		return responseStations; | ||||
| 	} | ||||
|  | ||||
| 	@Get('/:id') | ||||
| 	@Authorized("STATION:GET") | ||||
| 	@ResponseSchema(ResponseScanStation) | ||||
| 	@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(ScanStationNotFoundError) | ||||
| 	@OpenAPI({ description: 'Lists all information about the station whose id got provided. <br> This includes it\'s associated track.' }) | ||||
| 	async getOne(@Param('id') id: number) { | ||||
| 		let scan = await this.stationRepository.findOne({ id: id }, { relations: ['track'] }) | ||||
| 		if (!scan) { throw new ScanStationNotFoundError(); } | ||||
| 		return scan.toResponse(); | ||||
| 	} | ||||
|  | ||||
| 	@Post() | ||||
| 	@Authorized("STATION:CREATE") | ||||
| 	@ResponseSchema(ResponseScanStation) | ||||
| 	@ResponseSchema(TrackNotFoundError, { statusCode: 404 }) | ||||
| 	@OpenAPI({ description: 'Create a new station. <br> Please remeber to provide the station\'s track\'s id. <br> Please also remember that the station key is only visibe on creation.' }) | ||||
| 	async post(@Body({ validate: true }) createStation: CreateScanStation) { | ||||
| 		let newStation = await createStation.toEntity(); | ||||
| 		const station = await this.stationRepository.save(newStation); | ||||
| 		let responseStation = (await this.stationRepository.findOne({ id: station.id }, { relations: ['track'] })).toResponse(); | ||||
| 		responseStation.key = newStation.cleartextkey; | ||||
| 		return responseStation; | ||||
| 	} | ||||
|  | ||||
| 	@Put('/:id') | ||||
| 	@Authorized("STATION:UPDATE") | ||||
| 	@ResponseSchema(ResponseScanStation) | ||||
| 	@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(ScanStationIdsNotMatchingError, { statusCode: 406 }) | ||||
| 	@OpenAPI({ description: "Update the station whose id you provided. <br> Please remember that only the description and enabled state can be changed." }) | ||||
| 	async put(@Param('id') id: number, @Body({ validate: true }) station: UpdateScanStation) { | ||||
| 		let oldStation = await this.stationRepository.findOne({ id: id }); | ||||
|  | ||||
| 		if (!oldStation) { | ||||
| 			throw new ScanStationNotFoundError(); | ||||
| 		} | ||||
|  | ||||
| 		if (oldStation.id != station.id) { | ||||
| 			throw new ScanStationIdsNotMatchingError(); | ||||
| 		} | ||||
|  | ||||
| 		await this.stationRepository.save(await station.update(oldStation)); | ||||
| 		return (await this.stationRepository.findOne({ id: id }, { relations: ['track'] })).toResponse(); | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/:id') | ||||
| 	@Authorized("STATION:DELETE") | ||||
| 	@ResponseSchema(ResponseScanStation) | ||||
| 	@ResponseSchema(ResponseEmpty, { statusCode: 204 }) | ||||
| 	@ResponseSchema(ScanStationHasScansError, { statusCode: 406 }) | ||||
| 	@OnUndefined(204) | ||||
| 	@OpenAPI({ description: 'Delete the station whose id you provided. <br> If no station with this id exists it will just return 204(no content). <br> If the station still has scans associated you have to provide the force=true query param (warning: this deletes all scans associated with/created by this station - please disable it instead).' }) | ||||
| 	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { | ||||
| 		let station = await this.stationRepository.findOne({ id: id }); | ||||
| 		if (!station) { return null; } | ||||
|  | ||||
| 		const stationScans = (await this.stationRepository.findOne({ id: station.id }, { relations: ["scans"] })).scans; | ||||
| 		if (stationScans.length != 0 && !force) { | ||||
| 			throw new ScanStationHasScansError(); | ||||
| 		} | ||||
| 		const scanController = new ScanController; | ||||
| 		for (let scan of stationScans) { | ||||
| 			await scanController.remove(scan.id, force); | ||||
| 		} | ||||
|  | ||||
| 		const responseStation = await this.stationRepository.findOne({ id: station.id }, { relations: ["track"] }); | ||||
| 		await this.stationRepository.delete(station); | ||||
| 		return responseStation.toResponse(); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										75
									
								
								src/controllers/StatsClientController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/controllers/StatsClientController.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { StatsClientNotFoundError } from '../errors/StatsClientErrors'; | ||||
| import { TrackNotFoundError } from "../errors/TrackErrors"; | ||||
| import { CreateStatsClient } from '../models/actions/create/CreateStatsClient'; | ||||
| import { StatsClient } from '../models/entities/StatsClient'; | ||||
| import { ResponseEmpty } from '../models/responses/ResponseEmpty'; | ||||
| import { ResponseStatsClient } from '../models/responses/ResponseStatsClient'; | ||||
|  | ||||
| @JsonController('/statsclients') | ||||
| @OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
| export class StatsClientController { | ||||
| 	private clientRepository: Repository<StatsClient>; | ||||
|  | ||||
| 	/** | ||||
| 	 * Gets the repository of this controller's model/entity. | ||||
| 	 */ | ||||
| 	constructor() { | ||||
| 		this.clientRepository = getConnectionManager().get().getRepository(StatsClient); | ||||
| 	} | ||||
|  | ||||
| 	@Get() | ||||
| 	@Authorized("STATSCLIENT:GET") | ||||
| 	@ResponseSchema(ResponseStatsClient, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all stats clients. Please remember that the key can only be viewed on creation.' }) | ||||
| 	async getAll() { | ||||
| 		let responseClients: ResponseStatsClient[] = new Array<ResponseStatsClient>(); | ||||
| 		const clients = await this.clientRepository.find(); | ||||
| 		clients.forEach(clients => { | ||||
| 			responseClients.push(new ResponseStatsClient(clients)); | ||||
| 		}); | ||||
| 		return responseClients; | ||||
| 	} | ||||
|  | ||||
| 	@Get('/:id') | ||||
| 	@Authorized("STATSCLIENT:GET") | ||||
| 	@ResponseSchema(ResponseStatsClient) | ||||
| 	@ResponseSchema(StatsClientNotFoundError, { statusCode: 404 }) | ||||
| 	@OnUndefined(StatsClientNotFoundError) | ||||
| 	@OpenAPI({ description: "Lists all information about the stats client whose id got provided. Please remember that the key can only be viewed on creation." }) | ||||
| 	async getOne(@Param('id') id: number) { | ||||
| 		let client = await this.clientRepository.findOne({ id: id }); | ||||
| 		if (!client) { throw new TrackNotFoundError(); } | ||||
| 		return new ResponseStatsClient(client); | ||||
| 	} | ||||
|  | ||||
| 	@Post() | ||||
| 	@Authorized("STATSCLIENT:CREATE") | ||||
| 	@ResponseSchema(ResponseStatsClient) | ||||
| 	@OpenAPI({ description: "Create a new stats client. <br> Please remember that the client\'s key will be generated automaticly and that it can only be viewed on creation." }) | ||||
| 	async post( | ||||
| 		@Body({ validate: true }) | ||||
| 		client: CreateStatsClient | ||||
| 	) { | ||||
| 		let newClient = await this.clientRepository.save(await client.toEntity()); | ||||
| 		let responseClient = new ResponseStatsClient(newClient); | ||||
| 		responseClient.key = newClient.cleartextkey; | ||||
| 		return responseClient; | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/:id') | ||||
| 	@Authorized("STATSCLIENT:DELETE") | ||||
| 	@ResponseSchema(ResponseStatsClient) | ||||
| 	@ResponseSchema(ResponseEmpty, { statusCode: 204 }) | ||||
| 	@OnUndefined(204) | ||||
| 	@OpenAPI({ description: "Delete the stats client whose id you provided. <br> If no client with this id exists it will just return 204(no content)." }) | ||||
| 	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { | ||||
| 		let client = await this.clientRepository.findOne({ id: id }); | ||||
| 		if (!client) { return null; } | ||||
|  | ||||
| 		await this.clientRepository.delete(client); | ||||
| 		return new ResponseStatsClient(client); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										124
									
								
								src/controllers/StatsController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								src/controllers/StatsController.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,124 @@ | ||||
| import { Get, JsonController, UseBefore } from 'routing-controllers'; | ||||
| import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnection } from 'typeorm'; | ||||
| import StatsAuth from '../middlewares/StatsAuth'; | ||||
| import { Donation } from '../models/entities/Donation'; | ||||
| import { Runner } from '../models/entities/Runner'; | ||||
| import { RunnerOrganisation } from '../models/entities/RunnerOrganisation'; | ||||
| 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 { ResponseStatsRunner } from '../models/responses/ResponseStatsRunner'; | ||||
| import { ResponseStatsTeam } from '../models/responses/ResponseStatsTeam'; | ||||
|  | ||||
| @JsonController('/stats') | ||||
| export class StatsController { | ||||
|  | ||||
|     @Get() | ||||
|     @ResponseSchema(ResponseStats) | ||||
|     @OpenAPI({ description: "A very basic stats endpoint providing basic counters for a dashboard or simmilar" }) | ||||
|     async get() { | ||||
|         let connection = getConnection(); | ||||
|         let runners = await connection.getRepository(Runner).find({ relations: ['scans', 'scans.track'] }); | ||||
|         let teams = await connection.getRepository(RunnerTeam).find(); | ||||
|         let orgs = await connection.getRepository(RunnerOrganisation).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'] }); | ||||
|         return new ResponseStats(runners, teams, orgs, users, scans, donations) | ||||
|     } | ||||
|  | ||||
|     @Get("/runners/distance") | ||||
|     @UseBefore(StatsAuth) | ||||
|     @ResponseSchema(ResponseStatsRunner, { isArray: true }) | ||||
|     @OpenAPI({ description: "Returns the top ten runners by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
|     async getTopRunnersByDistance() { | ||||
|         let runners = await getConnection().getRepository(Runner).find({ relations: ['scans', 'group', 'distanceDonations', 'scans.track'] }); | ||||
|         let topRunners = runners.sort((runner1, runner2) => runner1.distance - runner2.distance).slice(0, 9); | ||||
|         let responseRunners: ResponseStatsRunner[] = new Array<ResponseStatsRunner>(); | ||||
|         topRunners.forEach(runner => { | ||||
|             responseRunners.push(new ResponseStatsRunner(runner)); | ||||
|         }); | ||||
|         return responseRunners; | ||||
|     } | ||||
|  | ||||
|     @Get("/runners/donations") | ||||
|     @UseBefore(StatsAuth) | ||||
|     @ResponseSchema(ResponseStatsRunner, { isArray: true }) | ||||
|     @OpenAPI({ description: "Returns the top ten runners by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
|     async getTopRunnersByDonations() { | ||||
|         let runners = await getConnection().getRepository(Runner).find({ relations: ['scans', 'group', 'distanceDonations', 'scans.track'] }); | ||||
|         let topRunners = runners.sort((runner1, runner2) => runner1.distanceDonationAmount - runner2.distanceDonationAmount).slice(0, 9); | ||||
|         let responseRunners: ResponseStatsRunner[] = new Array<ResponseStatsRunner>(); | ||||
|         topRunners.forEach(runner => { | ||||
|             responseRunners.push(new ResponseStatsRunner(runner)); | ||||
|         }); | ||||
|         return responseRunners; | ||||
|     } | ||||
|  | ||||
|     @Get("/scans") | ||||
|     @UseBefore(StatsAuth) | ||||
|     @ResponseSchema(ResponseStatsRunner, { isArray: true }) | ||||
|     @OpenAPI({ description: "Returns the top ten fastest track times (with their runner and the runner's group).", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
|     async getTopRunnersByTrackTime() { | ||||
|         throw new Error("Not implemented yet.") | ||||
|     } | ||||
|  | ||||
|     @Get("/teams/distance") | ||||
|     @UseBefore(StatsAuth) | ||||
|     @ResponseSchema(ResponseStatsTeam, { isArray: true }) | ||||
|     @OpenAPI({ description: "Returns the top ten teams by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
|     async getTopTeamsByDistance() { | ||||
|         let teams = await getConnection().getRepository(RunnerTeam).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track'] }); | ||||
|         let topTeams = teams.sort((team1, team2) => team1.distance - team2.distance).slice(0, 9); | ||||
|         let responseTeams: ResponseStatsTeam[] = new Array<ResponseStatsTeam>(); | ||||
|         topTeams.forEach(team => { | ||||
|             responseTeams.push(new ResponseStatsTeam(team)); | ||||
|         }); | ||||
|         return responseTeams; | ||||
|     } | ||||
|  | ||||
|     @Get("/teams/donations") | ||||
|     @UseBefore(StatsAuth) | ||||
|     @ResponseSchema(ResponseStatsTeam, { isArray: true }) | ||||
|     @OpenAPI({ description: "Returns the top ten teams by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
|     async getTopTeamsByDonations() { | ||||
|         let teams = await getConnection().getRepository(RunnerTeam).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track'] }); | ||||
|         let topTeams = teams.sort((team1, team2) => team1.distanceDonationAmount - team2.distanceDonationAmount).slice(0, 9); | ||||
|         let responseTeams: ResponseStatsTeam[] = new Array<ResponseStatsTeam>(); | ||||
|         topTeams.forEach(team => { | ||||
|             responseTeams.push(new ResponseStatsTeam(team)); | ||||
|         }); | ||||
|         return responseTeams; | ||||
|     } | ||||
|  | ||||
|     @Get("/organisations/distance") | ||||
|     @UseBefore(StatsAuth) | ||||
|     @ResponseSchema(ResponseStatsOrgnisation, { isArray: true }) | ||||
|     @OpenAPI({ description: "Returns the top ten organisations 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 topOrgs = orgs.sort((org1, org2) => org1.distance - org2.distance).slice(0, 9); | ||||
|         let responseOrgs: ResponseStatsOrgnisation[] = new Array<ResponseStatsOrgnisation>(); | ||||
|         topOrgs.forEach(org => { | ||||
|             responseOrgs.push(new ResponseStatsOrgnisation(org)); | ||||
|         }); | ||||
|         return responseOrgs; | ||||
|     } | ||||
|  | ||||
|     @Get("/organisations/donations") | ||||
|     @UseBefore(StatsAuth) | ||||
|     @ResponseSchema(ResponseStatsOrgnisation, { isArray: true }) | ||||
|     @OpenAPI({ description: "Returns the top ten organisations 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 topOrgs = orgs.sort((org1, org2) => org1.distanceDonationAmount - org2.distanceDonationAmount).slice(0, 9); | ||||
|         let responseOrgs: ResponseStatsOrgnisation[] = new Array<ResponseStatsOrgnisation>(); | ||||
|         topOrgs.forEach(org => { | ||||
|             responseOrgs.push(new ResponseStatsOrgnisation(org)); | ||||
|         }); | ||||
|         return responseOrgs; | ||||
|     } | ||||
| } | ||||
| @@ -1,12 +1,13 @@ | ||||
| import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers'; | ||||
| 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 { EntityFromBody } from 'typeorm-routing-controllers-extensions'; | ||||
| import { TrackIdsNotMatchingError, TrackNotFoundError } from "../errors/TrackErrors"; | ||||
| import { CreateTrack } from '../models/actions/CreateTrack'; | ||||
| import { TrackHasScanStationsError, TrackIdsNotMatchingError, TrackLapTimeCantBeNegativeError, TrackNotFoundError } from "../errors/TrackErrors"; | ||||
| import { CreateTrack } from '../models/actions/create/CreateTrack'; | ||||
| import { UpdateTrack } from '../models/actions/update/UpdateTrack'; | ||||
| import { Track } from '../models/entities/Track'; | ||||
| import { ResponseEmpty } from '../models/responses/ResponseEmpty'; | ||||
| import { ResponseTrack } from '../models/responses/ResponseTrack'; | ||||
| import { ScanStationController } from './ScanStationController'; | ||||
|  | ||||
| @JsonController('/tracks') | ||||
| @OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) | ||||
| @@ -48,12 +49,13 @@ export class TrackController { | ||||
| 	@Post() | ||||
| 	@Authorized("TRACK:CREATE") | ||||
| 	@ResponseSchema(ResponseTrack) | ||||
| 	@ResponseSchema(TrackLapTimeCantBeNegativeError, { statusCode: 406 }) | ||||
| 	@OpenAPI({ description: "Create a new track. <br> Please remember that the track\'s distance must be greater than 0." }) | ||||
| 	async post( | ||||
| 		@Body({ validate: true }) | ||||
| 		track: CreateTrack | ||||
| 	) { | ||||
| 		return new ResponseTrack(await this.trackRepository.save(track.toTrack())); | ||||
| 		return new ResponseTrack(await this.trackRepository.save(await track.toEntity())); | ||||
| 	} | ||||
|  | ||||
| 	@Put('/:id') | ||||
| @@ -61,20 +63,21 @@ export class TrackController { | ||||
| 	@ResponseSchema(ResponseTrack) | ||||
| 	@ResponseSchema(TrackNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(TrackIdsNotMatchingError, { statusCode: 406 }) | ||||
| 	@ResponseSchema(TrackLapTimeCantBeNegativeError, { statusCode: 406 }) | ||||
| 	@OpenAPI({ description: "Update the track whose id you provided. <br> Please remember that ids can't be changed." }) | ||||
| 	async put(@Param('id') id: number, @EntityFromBody() track: Track) { | ||||
| 	async put(@Param('id') id: number, @Body({ validate: true }) updateTrack: UpdateTrack) { | ||||
| 		let oldTrack = await this.trackRepository.findOne({ id: id }); | ||||
|  | ||||
| 		if (!oldTrack) { | ||||
| 			throw new TrackNotFoundError(); | ||||
| 		} | ||||
|  | ||||
| 		if (oldTrack.id != track.id) { | ||||
| 		if (oldTrack.id != updateTrack.id) { | ||||
| 			throw new TrackIdsNotMatchingError(); | ||||
| 		} | ||||
| 		await this.trackRepository.save(await updateTrack.update(oldTrack)); | ||||
|  | ||||
| 		await this.trackRepository.save(track); | ||||
| 		return new ResponseTrack(track); | ||||
| 		return new ResponseTrack(await this.trackRepository.findOne({ id: id })); | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/:id') | ||||
| @@ -83,10 +86,19 @@ export class TrackController { | ||||
| 	@ResponseSchema(ResponseEmpty, { statusCode: 204 }) | ||||
| 	@OnUndefined(204) | ||||
| 	@OpenAPI({ description: "Delete the track whose id you provided. <br> If no track with this id exists it will just return 204(no content)." }) | ||||
| 	async remove(@Param("id") id: number) { | ||||
| 	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { | ||||
| 		let track = await this.trackRepository.findOne({ id: id }); | ||||
| 		if (!track) { return null; } | ||||
|  | ||||
| 		const trackStations = (await this.trackRepository.findOne({ id: id }, { relations: ["stations"] })).stations; | ||||
| 		if (trackStations.length != 0 && !force) { | ||||
| 			throw new TrackHasScanStationsError(); | ||||
| 		} | ||||
| 		const stationController = new ScanStationController; | ||||
| 		for (let station of trackStations) { | ||||
| 			await stationController.remove(station.id, force); | ||||
| 		} | ||||
|  | ||||
| 		await this.trackRepository.delete(track); | ||||
| 		return new ResponseTrack(track); | ||||
| 	} | ||||
|   | ||||
| @@ -3,8 +3,8 @@ import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { UserIdsNotMatchingError, UserNotFoundError } from '../errors/UserErrors'; | ||||
| import { UserGroupNotFoundError } from '../errors/UserGroupErrors'; | ||||
| import { CreateUser } from '../models/actions/CreateUser'; | ||||
| import { UpdateUser } from '../models/actions/UpdateUser'; | ||||
| 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'; | ||||
| @@ -25,11 +25,11 @@ export class UserController { | ||||
|  | ||||
| 	@Get() | ||||
| 	@Authorized("USER:GET") | ||||
| 	@ResponseSchema(User, { isArray: true }) | ||||
| 	@ResponseSchema(ResponseUser, { isArray: true }) | ||||
| 	@OpenAPI({ description: 'Lists all users. <br> This includes their groups and permissions directly granted to them (if existing/associated).' }) | ||||
| 	async getAll() { | ||||
| 		let responseUsers: ResponseUser[] = new Array<ResponseUser>(); | ||||
| 		const users = await this.userRepository.find({ relations: ['permissions', 'groups'] }); | ||||
| 		const users = await this.userRepository.find({ relations: ['permissions', 'groups', 'groups.permissions'] }); | ||||
| 		users.forEach(user => { | ||||
| 			responseUsers.push(new ResponseUser(user)); | ||||
| 		}); | ||||
| @@ -38,25 +38,25 @@ export class UserController { | ||||
|  | ||||
| 	@Get('/:id') | ||||
| 	@Authorized("USER:GET") | ||||
| 	@ResponseSchema(User) | ||||
| 	@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.' }) | ||||
| 	async getOne(@Param('id') id: number) { | ||||
| 		let user = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] }) | ||||
| 		let user = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions'] }) | ||||
| 		if (!user) { throw new UserNotFoundError(); } | ||||
| 		return new ResponseUser(user); | ||||
| 	} | ||||
|  | ||||
| 	@Post() | ||||
| 	@Authorized("USER:CREATE") | ||||
| 	@ResponseSchema(User) | ||||
| 	@ResponseSchema(ResponseUser) | ||||
| 	@ResponseSchema(UserGroupNotFoundError) | ||||
| 	@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; | ||||
| 		try { | ||||
| 			user = await createUser.toUser(); | ||||
| 			user = await createUser.toEntity(); | ||||
| 		} catch (error) { | ||||
| 			throw error; | ||||
| 		} | ||||
| @@ -67,7 +67,7 @@ export class UserController { | ||||
|  | ||||
| 	@Put('/:id') | ||||
| 	@Authorized("USER:UPDATE") | ||||
| 	@ResponseSchema(User) | ||||
| 	@ResponseSchema(ResponseUser) | ||||
| 	@ResponseSchema(UserNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(UserIdsNotMatchingError, { 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." }) | ||||
| @@ -81,14 +81,14 @@ export class UserController { | ||||
| 		if (oldUser.id != updateUser.id) { | ||||
| 			throw new UserIdsNotMatchingError(); | ||||
| 		} | ||||
| 		await this.userRepository.save(await updateUser.updateUser(oldUser)); | ||||
| 		await this.userRepository.save(await updateUser.update(oldUser)); | ||||
|  | ||||
| 		return new ResponseUser(await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] })); | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/:id') | ||||
| 	@Authorized("USER:DELETE") | ||||
| 	@ResponseSchema(User) | ||||
| 	@ResponseSchema(ResponseUser) | ||||
| 	@ResponseSchema(ResponseEmpty, { statusCode: 204 }) | ||||
| 	@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).' }) | ||||
|   | ||||
| @@ -3,7 +3,8 @@ import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; | ||||
| import { getConnectionManager, Repository } from 'typeorm'; | ||||
| import { EntityFromBody } from 'typeorm-routing-controllers-extensions'; | ||||
| import { UserGroupIdsNotMatchingError, UserGroupNotFoundError } from '../errors/UserGroupErrors'; | ||||
| import { CreateUserGroup } from '../models/actions/CreateUserGroup'; | ||||
| import { CreateUserGroup } from '../models/actions/create/CreateUserGroup'; | ||||
| import { UpdateUserGroup } from '../models/actions/update/UpdateUserGroup'; | ||||
| import { UserGroup } from '../models/entities/UserGroup'; | ||||
| import { ResponseEmpty } from '../models/responses/ResponseEmpty'; | ||||
| import { ResponseUserGroup } from '../models/responses/ResponseUserGroup'; | ||||
| @@ -48,7 +49,7 @@ export class UserGroupController { | ||||
| 	async post(@Body({ validate: true }) createUserGroup: CreateUserGroup) { | ||||
| 		let userGroup; | ||||
| 		try { | ||||
| 			userGroup = await createUserGroup.toUserGroup(); | ||||
| 			userGroup = await createUserGroup.toEntity(); | ||||
| 		} catch (error) { | ||||
| 			throw error; | ||||
| 		} | ||||
| @@ -62,19 +63,19 @@ export class UserGroupController { | ||||
| 	@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(UserGroupIdsNotMatchingError, { statusCode: 406 }) | ||||
| 	@OpenAPI({ description: "Update the group whose id you provided. <br> To change the permissions granted to the group please use /api/permissions instead. <br> Please remember that ids can't be changed." }) | ||||
| 	async put(@Param('id') id: number, @EntityFromBody() userGroup: UserGroup) { | ||||
| 		let oldUserGroup = await this.userGroupsRepository.findOne({ id: id }, { relations: ["permissions"] }); | ||||
| 	async put(@Param('id') id: number, @EntityFromBody() updateGroup: UpdateUserGroup) { | ||||
| 		let oldGroup = await this.userGroupsRepository.findOne({ id: id }); | ||||
|  | ||||
| 		if (!oldUserGroup) { | ||||
| 			throw new UserGroupNotFoundError() | ||||
| 		if (!oldGroup) { | ||||
| 			throw new UserGroupNotFoundError(); | ||||
| 		} | ||||
|  | ||||
| 		if (oldUserGroup.id != userGroup.id) { | ||||
| 		if (oldGroup.id != updateGroup.id) { | ||||
| 			throw new UserGroupIdsNotMatchingError(); | ||||
| 		} | ||||
| 		await this.userGroupsRepository.save(await updateGroup.update(oldGroup)); | ||||
|  | ||||
| 		await this.userGroupsRepository.save(userGroup); | ||||
| 		return userGroup; | ||||
| 		return (await this.userGroupsRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] })).toResponse(); | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/:id') | ||||
| @@ -88,9 +89,9 @@ export class UserGroupController { | ||||
| 		if (!group) { return null; } | ||||
| 		const responseGroup = await this.userGroupsRepository.findOne({ id: id }, { relations: ['permissions'] }); | ||||
|  | ||||
| 		const permissionControler = new PermissionController(); | ||||
| 		const permissionController = new PermissionController(); | ||||
| 		for (let permission of responseGroup.permissions) { | ||||
| 			await permissionControler.remove(permission.id, true); | ||||
| 			await permissionController.remove(permission.id, true); | ||||
| 		} | ||||
|  | ||||
| 		await this.userGroupsRepository.delete(group); | ||||
|   | ||||
| @@ -9,11 +9,11 @@ export class AddressWrongTypeError extends NotAcceptableError { | ||||
| 	name = "AddressWrongTypeError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The address must be an existing adress's id. \n You provided a object of another type." | ||||
| 	message = "The address must be an existing address's id. \n You provided a object of another type." | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw, when a non-existant address get's loaded. | ||||
|  * Error to throw, when a non-existent address get's loaded. | ||||
|  */ | ||||
| export class AddressNotFoundError extends NotFoundError { | ||||
| 	@IsString() | ||||
|   | ||||
| @@ -118,7 +118,7 @@ export class RefreshTokenCountInvalidError extends NotAcceptableError { | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when someone tryes to reset a user's password more than once in 15 minutes. | ||||
|  * Error to throw when someone tries to reset a user's password more than once in 15 minutes. | ||||
|  */ | ||||
| export class ResetAlreadyRequestedError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
|   | ||||
							
								
								
									
										36
									
								
								src/errors/DonorErrors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/errors/DonorErrors.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| import { IsString } from 'class-validator'; | ||||
| import { NotAcceptableError, NotFoundError } from 'routing-controllers'; | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a donor couldn't be found. | ||||
|  */ | ||||
| export class DonorNotFoundError extends NotFoundError { | ||||
| 	@IsString() | ||||
| 	name = "DonorNotFoundError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "Donor not found!" | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when two donors' ids don't match. | ||||
|  * Usually occurs when a user tries to change a donor's id. | ||||
|  */ | ||||
| export class DonorIdsNotMatchingError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "DonorIdsNotMatchingError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The ids don't match! \n And if you wanted to change a donor's id: This isn't allowed!" | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a donor needs a receipt, but no address is associated with them. | ||||
|  */ | ||||
| export class DonorReceiptAddressNeededError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "DonorReceiptAddressNeededError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "An address is needed to create a receipt for a donor. \n You didn't provide one." | ||||
| } | ||||
| @@ -13,7 +13,7 @@ export class GroupContactWrongTypeError extends NotAcceptableError { | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw, when a non-existant groupContact get's loaded. | ||||
|  * Error to throw, when a non-existent groupContact get's loaded. | ||||
|  */ | ||||
| export class GroupContactNotFoundError extends NotFoundError { | ||||
| 	@IsString() | ||||
|   | ||||
| @@ -13,12 +13,12 @@ export class PrincipalNotFoundError extends NotFoundError { | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw, when a provided runnerOrganisation doesn't belong to the accepted types. | ||||
|  * Error to throw, when a provided runner organization doesn't belong to the accepted types. | ||||
|  */ | ||||
| export class PrincipalWrongTypeError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "PrincipalWrongTypeError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The princial must have an existing principal's id. \n You provided a object of another type." | ||||
| 	message = "The principal must have an existing principal's id. \n You provided a object of another type." | ||||
| } | ||||
|   | ||||
							
								
								
									
										48
									
								
								src/errors/RunnerCardErrors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/errors/RunnerCardErrors.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| import { IsString } from 'class-validator'; | ||||
| import { NotAcceptableError, NotFoundError } from 'routing-controllers'; | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a card couldn't be found. | ||||
|  */ | ||||
| export class RunnerCardNotFoundError extends NotFoundError { | ||||
| 	@IsString() | ||||
| 	name = "RunnerCardNotFoundError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "Card not found!" | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when two cards' ids don't match. | ||||
|  * Usually occurs when a user tries to change a card's id. | ||||
|  */ | ||||
| export class RunnerCardIdsNotMatchingError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "RunnerCardIdsNotMatchingError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The ids don't match! \n And if you wanted to change a cards's id: This isn't allowed" | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a card still has scans associated. | ||||
|  */ | ||||
| export class RunnerCardHasScansError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "RunnerCardHasScansError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "This card still has scans associated with it. \n If you want to delete this card with all it's scans add `?force` to your query. \n Otherwise please consider just disabling it." | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a card's id is too big to generate a ean-13 barcode for it. | ||||
|  * This error should never reach a end user. | ||||
|  */ | ||||
| export class RunnerCardIdOutOfRangeError extends Error { | ||||
| 	@IsString() | ||||
| 	name = "RunnerCardIdOutOfRangeError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The card's id is too big to fit into a ean-13 barcode. \n This has a very low probability of happening but means that you might want to switch your barcode format for something that can accept numbers over 9999999999." | ||||
| } | ||||
| @@ -32,5 +32,5 @@ export class RunnerGroupNeededError extends NotAcceptableError { | ||||
| 	name = "RunnerGroupNeededError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "Runner's need to be part of one group (team or organisiation)! \n You provided neither." | ||||
| 	message = "Runner's need to be part of one group (team or organisation)! \n You provided neither." | ||||
| } | ||||
| @@ -13,7 +13,7 @@ export class RunnerOrganisationNotFoundError extends NotFoundError { | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when two runner organisations' ids don't match. | ||||
|  * 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 { | ||||
|   | ||||
							
								
								
									
										25
									
								
								src/errors/ScanErrors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/errors/ScanErrors.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| import { IsString } from 'class-validator'; | ||||
| import { NotAcceptableError, NotFoundError } from 'routing-controllers'; | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a Scan couldn't be found. | ||||
|  */ | ||||
| export class ScanNotFoundError extends NotFoundError { | ||||
| 	@IsString() | ||||
| 	name = "ScanNotFoundError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "Scan not found!" | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when two Scans' ids don't match. | ||||
|  * Usually occurs when a user tries to change a Scan's id. | ||||
|  */ | ||||
| export class ScanIdsNotMatchingError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "ScanIdsNotMatchingError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The ids don't match! \n And if you wanted to change a Scan's id: This isn't allowed!" | ||||
| } | ||||
							
								
								
									
										36
									
								
								src/errors/ScanStationErrors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/errors/ScanStationErrors.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| import { IsString } from 'class-validator'; | ||||
| import { NotAcceptableError, NotFoundError } from 'routing-controllers'; | ||||
|  | ||||
| /** | ||||
|  * Error to throw, when a non-existent scan station get's loaded. | ||||
|  */ | ||||
| export class ScanStationNotFoundError extends NotFoundError { | ||||
| 	@IsString() | ||||
| 	name = "ScanStationNotFoundError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The scan station you provided couldn't be located in the system. \n Please check your request." | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when two scan stations' ids don't match. | ||||
|  * Usually occurs when a user tries to change a scan station's id. | ||||
|  */ | ||||
| export class ScanStationIdsNotMatchingError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "ScanStationIdsNotMatchingError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The ids don't match! \n And if you wanted to change a scan station's id: This isn't allowed!" | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a station still has scans associated. | ||||
|  */ | ||||
| export class ScanStationHasScansError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "ScanStationHasScansError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "This station still has scans associated with it. \n If you want to delete this station with all it's scans add `?force` to your query." | ||||
| } | ||||
							
								
								
									
										25
									
								
								src/errors/StatsClientErrors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/errors/StatsClientErrors.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| import { IsString } from 'class-validator'; | ||||
| import { NotAcceptableError, NotFoundError } from 'routing-controllers'; | ||||
|  | ||||
| /** | ||||
|  * Error to throw, when a non-existent stats client get's loaded. | ||||
|  */ | ||||
| export class StatsClientNotFoundError extends NotFoundError { | ||||
| 	@IsString() | ||||
| 	name = "StatsClientNotFoundError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The stats client you provided couldn't be located in the system. \n Please check your request." | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when two stats clients' ids don't match. | ||||
|  * Usually occurs when a user tries to change a stats client's id. | ||||
|  */ | ||||
| export class StatsClientIdsNotMatchingError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "StatsClientIdsNotMatchingError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The ids don't match! \n And if you wanted to change a stats client's id: This isn't allowed!" | ||||
| } | ||||
| @@ -23,3 +23,22 @@ export class TrackIdsNotMatchingError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	message = "The ids don't match! \n And if you wanted to change a track's id: This isn't allowed" | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a track's lap time is set to a negative value. | ||||
|  */ | ||||
| export class TrackLapTimeCantBeNegativeError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "TrackLapTimeCantBeNegativeError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The minimum lap time you provided is negative - That isn't possible. \n If you wanted to disable it: Just set it to 0/null." | ||||
| } | ||||
|  | ||||
| export class TrackHasScanStationsError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "TrackHasScanStationsError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "This track still has stations associated with it. \n If you want to delete this track with all it's stations and scans add `?force` to your query." | ||||
| } | ||||
| @@ -2,7 +2,7 @@ import { IsString } from 'class-validator'; | ||||
| import { NotAcceptableError, NotFoundError } from 'routing-controllers'; | ||||
|  | ||||
| /** | ||||
|  * Error to throw when no groupname is set. | ||||
|  * Error to throw when no group name is set. | ||||
|  */ | ||||
| export class GroupNameNeededError extends NotFoundError { | ||||
| 	@IsString() | ||||
| @@ -13,7 +13,7 @@ export class GroupNameNeededError extends NotFoundError { | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a usergroup couldn't be found. | ||||
|  * Error to throw when a user group couldn't be found. | ||||
|  */ | ||||
| export class UserGroupNotFoundError extends NotFoundError { | ||||
| 	@IsString() | ||||
| @@ -24,13 +24,13 @@ export class UserGroupNotFoundError extends NotFoundError { | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when two usergroups' ids don't match. | ||||
|  * Usually occurs when a user tries to change a usergroups's id. | ||||
|  * Error to throw when two user groups' ids don't match. | ||||
|  * Usually occurs when a user tries to change a user groups's id. | ||||
|  */ | ||||
| export class UserGroupIdsNotMatchingError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "UserGroupIdsNotMatchingError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The ids don't match!! \n If you wanted to change a usergroup's id: This isn't allowed!" | ||||
| 	message = "The ids don't match!! \n If you wanted to change a user group's id: This isn't allowed!" | ||||
| } | ||||
| @@ -106,23 +106,6 @@ export class JwtUser { | ||||
|         this.refreshTokenCount = user.refreshTokenCount; | ||||
|         this.uuid = user.uuid; | ||||
|         this.profilePic = user.profilePic; | ||||
|         this.permissions = this.getPermissions(user); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Handels getting the permissions granted to this user (direct or indirect). | ||||
|      * @param user User which's permissions shall be gotten. | ||||
|      */ | ||||
|     public getPermissions(user: User): string[] { | ||||
|         let returnPermissions: string[] = new Array<string>(); | ||||
|         for (let permission of user.permissions) { | ||||
|             returnPermissions.push(permission.toString()); | ||||
|         } | ||||
|         for (let group of user.groups) { | ||||
|             for (let permission of group.permissions) { | ||||
|                 returnPermissions.push(permission.toString()); | ||||
|             } | ||||
|         } | ||||
|         return Array.from(new Set(returnPermissions)); | ||||
|         this.permissions = user.allPermissions; | ||||
|     } | ||||
| } | ||||
| @@ -2,7 +2,7 @@ import { validationMetadatasToSchemas } from "class-validator-jsonschema"; | ||||
| import express, { Application } from "express"; | ||||
| import path from 'path'; | ||||
| import { getMetadataArgsStorage } from "routing-controllers"; | ||||
| import { routingControllersToSpec } from "routing-controllers-openapi"; | ||||
| import { generateSpec } from '../apispec'; | ||||
|  | ||||
| /** | ||||
|  * Loader for everything openapi related - from creating the schema to serving it via a static route and swaggerUiExpress. | ||||
| @@ -15,36 +15,7 @@ export default async (app: Application) => { | ||||
|   }); | ||||
|  | ||||
|   //Spec creation based on the previously created schemas | ||||
|   const spec = routingControllersToSpec( | ||||
|     storage, | ||||
|     { | ||||
|       routePrefix: "/api" | ||||
|     }, | ||||
|     { | ||||
|       components: { | ||||
|         schemas, | ||||
|         "securitySchemes": { | ||||
|           "AuthToken": { | ||||
|             "type": "http", | ||||
|             "scheme": "bearer", | ||||
|             "bearerFormat": "JWT", | ||||
|             description: "A JWT based access token. Use /api/auth/login or /api/auth/refresh to get one." | ||||
|           }, | ||||
|           "RefreshTokenCookie": { | ||||
|             "type": "apiKey", | ||||
|             "in": "cookie", | ||||
|             "name": "lfk_backend__refresh_token", | ||||
|             description: "A cookie containing a JWT based refreh token. Attention: Doesn't work in swagger-ui. Use /api/auth/login or /api/auth/refresh to get one." | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|       info: { | ||||
|         description: "The the backend API for the LfK! runner system.", | ||||
|         title: "LfK! Backend API", | ||||
|         version: "0.0.5", | ||||
|       }, | ||||
|     } | ||||
|   ); | ||||
|   const spec = generateSpec(storage, schemas); | ||||
|   app.get(["/api/docs/openapi.json", "/api/docs/swagger.json"], (req, res) => { | ||||
|     res.json(spec); | ||||
|   }); | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| import { Request, Response } from 'express'; | ||||
|  | ||||
| /** | ||||
|  * Custom express middleware that appends the raw body to the request obeject. | ||||
|  * Mainly used for parsing csvs from boddies. | ||||
|  * Custom express middleware that appends the raw body to the request object. | ||||
|  * Mainly used for parsing csvs from bodies. | ||||
|  */ | ||||
|  | ||||
| const RawBodyMiddleware = (req: Request, res: Response, next: () => void) => { | ||||
|   | ||||
							
								
								
									
										69
									
								
								src/middlewares/ScanAuth.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/middlewares/ScanAuth.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| import * as argon2 from "argon2"; | ||||
| import { Request, Response } from 'express'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { ScanStation } from '../models/entities/ScanStation'; | ||||
| import authchecker from './authchecker'; | ||||
|  | ||||
| /** | ||||
|  * This middleware handles the authentication of scan station api tokens. | ||||
|  * The tokens have to be provided via Bearer authorization header. | ||||
|  * You have to manually use this middleware via @UseBefore(ScanAuth) instead of using @Authorized(). | ||||
|  * @param req Express request object. | ||||
|  * @param res Express response object. | ||||
|  * @param next Next function to call on success. | ||||
|  */ | ||||
| const ScanAuth = async (req: Request, res: Response, next: () => void) => { | ||||
|     let provided_token: string = req.headers["authorization"]; | ||||
|     if (provided_token == "" || provided_token === undefined || provided_token === null) { | ||||
|         res.status(401).send("No api token provided."); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     try { | ||||
|         provided_token = provided_token.replace("Bearer ", ""); | ||||
|     } catch (error) { | ||||
|         res.status(401).send("No valid jwt or api token provided."); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     let prefix = ""; | ||||
|     try { | ||||
|         prefix = provided_token.split(".")[0]; | ||||
|     } | ||||
|     finally { | ||||
|         if (prefix == "" || prefix == undefined || prefix == null) { | ||||
|             res.status(401).send("Api token non-existent or invalid syntax."); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     const station = await getConnectionManager().get().getRepository(ScanStation).findOne({ prefix: prefix }); | ||||
|     if (!station) { | ||||
|         let user_authorized = false; | ||||
|         try { | ||||
|             let action = { request: req, response: res, context: null, next: next } | ||||
|             user_authorized = await authchecker(action, ["SCAN:CREATE"]); | ||||
|         } | ||||
|         finally { | ||||
|             if (user_authorized == false) { | ||||
|                 res.status(401).send("Api token non-existent or invalid syntax."); | ||||
|                 return; | ||||
|             } | ||||
|             else { | ||||
|                 next(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     else { | ||||
|         if (station.enabled == false) { | ||||
|             res.status(401).send("Station disabled."); | ||||
|         } | ||||
|         if (!(await argon2.verify(station.key, provided_token))) { | ||||
|             res.status(401).send("Api token invalid."); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         next(); | ||||
|     } | ||||
| } | ||||
| export default ScanAuth; | ||||
							
								
								
									
										66
									
								
								src/middlewares/StatsAuth.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/middlewares/StatsAuth.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| import * as argon2 from "argon2"; | ||||
| import { Request, Response } from 'express'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { StatsClient } from '../models/entities/StatsClient'; | ||||
| import authchecker from './authchecker'; | ||||
|  | ||||
| /** | ||||
|  * This middleware handles the authentication of stats client api tokens. | ||||
|  * The tokens have to be provided via Bearer authorization header. | ||||
|  * You have to manually use this middleware via @UseBefore(StatsAuth) instead of using @Authorized(). | ||||
|  * @param req Express request object. | ||||
|  * @param res Express response object. | ||||
|  * @param next Next function to call on success. | ||||
|  */ | ||||
| const StatsAuth = async (req: Request, res: Response, next: () => void) => { | ||||
|     let provided_token: string = req.headers["authorization"]; | ||||
|     if (provided_token == "" || provided_token === undefined || provided_token === null) { | ||||
|         res.status(401).send("No api token provided."); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     try { | ||||
|         provided_token = provided_token.replace("Bearer ", ""); | ||||
|     } catch (error) { | ||||
|         res.status(401).send("No valid jwt or api token provided."); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     let prefix = ""; | ||||
|     try { | ||||
|         prefix = provided_token.split(".")[0]; | ||||
|     } | ||||
|     finally { | ||||
|         if (prefix == "" || prefix == undefined || prefix == null) { | ||||
|             res.status(401).send("Api token non-existant or invalid syntax."); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     const client = await getConnectionManager().get().getRepository(StatsClient).findOne({ prefix: prefix }); | ||||
|     if (!client) { | ||||
|         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"]); | ||||
|         } | ||||
|         finally { | ||||
|             if (user_authorized == false) { | ||||
|                 res.status(401).send("Api token non-existant or invalid syntax."); | ||||
|                 return; | ||||
|             } | ||||
|             else { | ||||
|                 next(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     else { | ||||
|         if (!(await argon2.verify(client.key, provided_token))) { | ||||
|             res.status(401).send("Api token invalid."); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         next(); | ||||
|     } | ||||
| } | ||||
| export default StatsAuth; | ||||
| @@ -2,13 +2,13 @@ import cookie from "cookie"; | ||||
| import * as jwt from "jsonwebtoken"; | ||||
| import { Action } from "routing-controllers"; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { config } from './config'; | ||||
| import { IllegalJWTError, NoPermissionError, UserDisabledError, UserNonexistantOrRefreshtokenInvalidError } from './errors/AuthError'; | ||||
| import { JwtCreator, JwtUser } from './jwtcreator'; | ||||
| import { User } from './models/entities/User'; | ||||
| import { config } from '../config'; | ||||
| import { IllegalJWTError, NoPermissionError, UserDisabledError, UserNonexistantOrRefreshtokenInvalidError } from '../errors/AuthError'; | ||||
| import { JwtCreator, JwtUser } from '../jwtcreator'; | ||||
| import { User } from '../models/entities/User'; | ||||
| 
 | ||||
| /** | ||||
|  * Handels authorisation verification via jwt's for all api endpoints using the @Authorized decorator. | ||||
|  * Handles authentication via jwt's (Bearer authorization header) for all api endpoints using the @Authorized decorator. | ||||
|  * @param action Routing-Controllers action object that provides request and response objects among other stuff. | ||||
|  * @param permissions The permissions that the endpoint using @Authorized requires. | ||||
|  */ | ||||
| @@ -43,7 +43,7 @@ const authchecker = async (action: Action, permissions: string[] | string) => { | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Handels soft-refreshing of access-tokens. | ||||
|  * 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) => { | ||||
| @@ -1,33 +0,0 @@ | ||||
| import { IsInt, IsNotEmpty, IsPositive, IsString } from 'class-validator'; | ||||
| import { Track } from '../entities/Track'; | ||||
|  | ||||
| /** | ||||
|  * This classed is used to create a new Track entity from a json body (post request). | ||||
|  */ | ||||
| export class CreateTrack { | ||||
|     /** | ||||
|      * The new track's name. | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsNotEmpty() | ||||
|     name: string; | ||||
|  | ||||
|     /** | ||||
|      * The new track's distance in meters (must be greater than 0). | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsPositive() | ||||
|     distance: number; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new Track entity from this. | ||||
|      */ | ||||
|     public toTrack(): Track { | ||||
|         let newTrack: Track = new Track(); | ||||
|  | ||||
|         newTrack.name = this.name; | ||||
|         newTrack.distance = this.distance; | ||||
|  | ||||
|         return newTrack; | ||||
|     } | ||||
| } | ||||
| @@ -5,7 +5,7 @@ import { RunnerOrganisationNotFoundError } from '../../errors/RunnerOrganisation | ||||
| import { RunnerGroup } from '../entities/RunnerGroup'; | ||||
| import { RunnerOrganisation } from '../entities/RunnerOrganisation'; | ||||
| import { RunnerTeam } from '../entities/RunnerTeam'; | ||||
| import { CreateRunner } from './CreateRunner'; | ||||
| import { CreateRunner } from './create/CreateRunner'; | ||||
|  | ||||
| /** | ||||
|  * Special class used to import runners from csv files - or json arrays created from csv to be exact. | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import { config } from '../../config'; | ||||
| import { IllegalJWTError, JwtNotProvidedError, RefreshTokenCountInvalidError, UserDisabledError, UserNotFoundError } from '../../errors/AuthError'; | ||||
| import { JwtCreator } from "../../jwtcreator"; | ||||
| import { User } from '../entities/User'; | ||||
| import { Auth } from '../responses/ResponseAuth'; | ||||
| import { ResponseAuth } from '../responses/ResponseAuth'; | ||||
|  | ||||
| /** | ||||
|  * This class is used to create refreshed auth credentials. | ||||
| @@ -24,8 +24,8 @@ export class RefreshAuth { | ||||
|     /** | ||||
|      * Creates a new auth object based on this. | ||||
|      */ | ||||
|     public async toAuth(): Promise<Auth> { | ||||
|         let newAuth: Auth = new Auth(); | ||||
|     public async toAuth(): Promise<ResponseAuth> { | ||||
|         let newAuth: ResponseAuth = new ResponseAuth(); | ||||
|         if (!this.token || this.token === undefined) { | ||||
|             throw new JwtNotProvidedError() | ||||
|         } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import { IsNotEmpty, IsOptional, IsPostalCode, IsString } from 'class-validator'; | ||||
| import { config } from '../../config'; | ||||
| import { Address } from '../entities/Address'; | ||||
| 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). | ||||
| @@ -56,7 +56,7 @@ export class CreateAddress { | ||||
|     /** | ||||
|      * Creates a new Address entity from this. | ||||
|      */ | ||||
|     public toAddress(): Address { | ||||
|     public async toEntity(): Promise<Address> { | ||||
|         let newAddress: Address = new Address(); | ||||
| 
 | ||||
|         newAddress.address1 = this.address1; | ||||
| @@ -1,11 +1,11 @@ | ||||
| import * as argon2 from "argon2"; | ||||
| import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { InvalidCredentialsError, PasswordNeededError, UserDisabledError, UserNotFoundError } from '../../errors/AuthError'; | ||||
| import { UsernameOrEmailNeededError } from '../../errors/UserErrors'; | ||||
| import { JwtCreator } from '../../jwtcreator'; | ||||
| import { User } from '../entities/User'; | ||||
| import { Auth } from '../responses/ResponseAuth'; | ||||
| import { InvalidCredentialsError, PasswordNeededError, UserDisabledError, UserNotFoundError } from '../../../errors/AuthError'; | ||||
| import { UsernameOrEmailNeededError } from '../../../errors/UserErrors'; | ||||
| import { JwtCreator } from '../../../jwtcreator'; | ||||
| import { User } from '../../entities/User'; | ||||
| import { ResponseAuth } from '../../responses/ResponseAuth'; | ||||
| 
 | ||||
| /** | ||||
|  * This class is used to create auth credentials based on user credentials provided in a json body (post request). | ||||
| @@ -42,8 +42,8 @@ export class CreateAuth { | ||||
|     /** | ||||
|      * Creates a new auth object based on this. | ||||
|      */ | ||||
|     public async toAuth(): Promise<Auth> { | ||||
|         let newAuth: Auth = new Auth(); | ||||
|     public async toAuth(): Promise<ResponseAuth> { | ||||
|         let newAuth: ResponseAuth = new ResponseAuth(); | ||||
| 
 | ||||
|         if (this.email === undefined && this.username === undefined) { | ||||
|             throw new UsernameOrEmailNeededError(); | ||||
							
								
								
									
										38
									
								
								src/models/actions/create/CreateDonor.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/models/actions/create/CreateDonor.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| import { IsBoolean, IsOptional } from 'class-validator'; | ||||
| import { DonorReceiptAddressNeededError } from '../../../errors/DonorErrors'; | ||||
| import { Donor } from '../../entities/Donor'; | ||||
| import { CreateParticipant } from './CreateParticipant'; | ||||
|  | ||||
| /** | ||||
|  * This classed is used to create a new Donor entity from a json body (post request). | ||||
|  */ | ||||
| export class CreateDonor extends CreateParticipant { | ||||
|  | ||||
|     /** | ||||
|      * Does this donor need a receipt? | ||||
|      */ | ||||
|     @IsBoolean() | ||||
|     @IsOptional() | ||||
|     receiptNeeded?: boolean = false; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new Donor entity from this. | ||||
|      */ | ||||
|     public async toEntity(): Promise<Donor> { | ||||
|         let newDonor: Donor = new Donor(); | ||||
|  | ||||
|         newDonor.firstname = this.firstname; | ||||
|         newDonor.middlename = this.middlename; | ||||
|         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) { | ||||
|             throw new DonorReceiptAddressNeededError() | ||||
|         } | ||||
|  | ||||
|         return newDonor; | ||||
|     } | ||||
| } | ||||
| @@ -1,9 +1,9 @@ | ||||
| import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { config } from '../../config'; | ||||
| import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors'; | ||||
| import { Address } from '../entities/Address'; | ||||
| import { GroupContact } from '../entities/GroupContact'; | ||||
| import { config } from '../../../config'; | ||||
| import { AddressNotFoundError, AddressWrongTypeError } from '../../../errors/AddressErrors'; | ||||
| import { Address } from '../../entities/Address'; | ||||
| import { GroupContact } from '../../entities/GroupContact'; | ||||
| 
 | ||||
| /** | ||||
|  * This classed is used to create a new Group entity from a json body (post request). | ||||
| @@ -72,7 +72,7 @@ export class CreateGroupContact { | ||||
|     /** | ||||
|      * Creates a new Address entity from this. | ||||
|      */ | ||||
|     public async toGroupContact(): Promise<GroupContact> { | ||||
|     public async toEntity(): Promise<GroupContact> { | ||||
|         let contact: GroupContact = new GroupContact(); | ||||
|         contact.firstname = this.firstname; | ||||
|         contact.middlename = this.middlename; | ||||
| @@ -1,8 +1,8 @@ | ||||
| import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { config } from '../../config'; | ||||
| import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors'; | ||||
| import { Address } from '../entities/Address'; | ||||
| import { config } from '../../../config'; | ||||
| import { AddressNotFoundError, AddressWrongTypeError } from '../../../errors/AddressErrors'; | ||||
| import { Address } from '../../entities/Address'; | ||||
| 
 | ||||
| /** | ||||
|  * This classed is used to create a new Participant entity from a json body (post request). | ||||
| @@ -4,11 +4,11 @@ import { | ||||
|     IsNotEmpty | ||||
| } from "class-validator"; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { PrincipalNotFoundError } from '../../errors/PrincipalErrors'; | ||||
| import { Permission } from '../entities/Permission'; | ||||
| import { Principal } from '../entities/Principal'; | ||||
| import { PermissionAction } from '../enums/PermissionAction'; | ||||
| import { PermissionTarget } from '../enums/PermissionTargets'; | ||||
| import { PrincipalNotFoundError } from '../../../errors/PrincipalErrors'; | ||||
| import { Permission } from '../../entities/Permission'; | ||||
| import { Principal } from '../../entities/Principal'; | ||||
| import { PermissionAction } from '../../enums/PermissionAction'; | ||||
| import { PermissionTarget } from '../../enums/PermissionTargets'; | ||||
| 
 | ||||
| /** | ||||
|  * This classed is used to create a new Permission entity from a json body (post request). | ||||
| @@ -39,7 +39,7 @@ export class CreatePermission { | ||||
|     /** | ||||
|      * Creates a new Permission entity from this. | ||||
|      */ | ||||
|     public async toPermission(): Promise<Permission> { | ||||
|     public async toEntity(): Promise<Permission> { | ||||
|         let newPermission: Permission = new Permission(); | ||||
| 
 | ||||
|         newPermission.principal = await this.getPrincipal(); | ||||
| @@ -1,9 +1,9 @@ | ||||
| import { IsEmail, IsOptional, IsString } from 'class-validator'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { ResetAlreadyRequestedError, UserDisabledError, UserNotFoundError } from '../../errors/AuthError'; | ||||
| import { UsernameOrEmailNeededError } from '../../errors/UserErrors'; | ||||
| import { JwtCreator } from '../../jwtcreator'; | ||||
| import { User } from '../entities/User'; | ||||
| import { ResetAlreadyRequestedError, UserDisabledError, UserNotFoundError } from '../../../errors/AuthError'; | ||||
| import { UsernameOrEmailNeededError } from '../../../errors/UserErrors'; | ||||
| import { JwtCreator } from '../../../jwtcreator'; | ||||
| import { User } from '../../entities/User'; | ||||
| 
 | ||||
| /** | ||||
|  * This calss is used to create password reset tokens for users. | ||||
| @@ -1,10 +1,10 @@ | ||||
| import { IsInt } from 'class-validator'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { RunnerGroupNotFoundError } from '../../errors/RunnerGroupErrors'; | ||||
| import { RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors'; | ||||
| import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors'; | ||||
| import { Runner } from '../entities/Runner'; | ||||
| import { RunnerGroup } from '../entities/RunnerGroup'; | ||||
| import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors'; | ||||
| import { RunnerOrganisationWrongTypeError } from '../../../errors/RunnerOrganisationErrors'; | ||||
| import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors'; | ||||
| import { Runner } from '../../entities/Runner'; | ||||
| import { RunnerGroup } from '../../entities/RunnerGroup'; | ||||
| import { CreateParticipant } from './CreateParticipant'; | ||||
| 
 | ||||
| /** | ||||
| @@ -21,7 +21,7 @@ export class CreateRunner extends CreateParticipant { | ||||
|     /** | ||||
|      * Creates a new Runner entity from this. | ||||
|      */ | ||||
|     public async toRunner(): Promise<Runner> { | ||||
|     public async toEntity(): Promise<Runner> { | ||||
|         let newRunner: Runner = new Runner(); | ||||
| 
 | ||||
|         newRunner.firstname = this.firstname; | ||||
							
								
								
									
										45
									
								
								src/models/actions/create/CreateRunnerCard.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/models/actions/create/CreateRunnerCard.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| import { IsBoolean, IsInt, IsOptional } from 'class-validator'; | ||||
| import { getConnection } from 'typeorm'; | ||||
| import { RunnerNotFoundError } from '../../../errors/RunnerErrors'; | ||||
| import { Runner } from '../../entities/Runner'; | ||||
| import { RunnerCard } from '../../entities/RunnerCard'; | ||||
|  | ||||
| /** | ||||
|  * This classed is used to create a new RunnerCard entity from a json body (post request). | ||||
|  */ | ||||
| export class CreateRunnerCard { | ||||
|     /** | ||||
|      * The card's associated runner. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsOptional() | ||||
|     runner?: number; | ||||
|  | ||||
|     /** | ||||
|      * Is the new card enabled (for fraud reasons)? | ||||
|      * Default: true | ||||
|      */ | ||||
|     @IsBoolean() | ||||
|     enabled: boolean = true; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new RunnerCard entity from this. | ||||
|      */ | ||||
|     public async toEntity(): Promise<RunnerCard> { | ||||
|         let newCard: RunnerCard = new RunnerCard(); | ||||
|  | ||||
|         newCard.enabled = this.enabled; | ||||
|         newCard.runner = await this.getRunner(); | ||||
|  | ||||
|         return newCard; | ||||
|     } | ||||
|  | ||||
|     public async getRunner(): Promise<Runner> { | ||||
|         if (!this.runner) { return null; } | ||||
|         const runner = await getConnection().getRepository(Runner).findOne({ id: this.runner }); | ||||
|         if (!runner) { | ||||
|             throw new RunnerNotFoundError(); | ||||
|         } | ||||
|         return runner; | ||||
|     } | ||||
| } | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { GroupContactNotFoundError, GroupContactWrongTypeError } from '../../errors/GroupContactErrors'; | ||||
| import { GroupContact } from '../entities/GroupContact'; | ||||
| import { GroupContactNotFoundError, GroupContactWrongTypeError } from '../../../errors/GroupContactErrors'; | ||||
| import { GroupContact } from '../../entities/GroupContact'; | ||||
| 
 | ||||
| /** | ||||
|  * This classed is used to create a new RunnerGroup entity from a json body (post request). | ||||
| @@ -1,8 +1,8 @@ | ||||
| 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 { AddressNotFoundError, AddressWrongTypeError } from '../../../errors/AddressErrors'; | ||||
| import { Address } from '../../entities/Address'; | ||||
| import { RunnerOrganisation } from '../../entities/RunnerOrganisation'; | ||||
| import { CreateRunnerGroup } from './CreateRunnerGroup'; | ||||
| 
 | ||||
| /** | ||||
| @@ -36,12 +36,12 @@ export class CreateRunnerOrganisation extends CreateRunnerGroup { | ||||
|     /** | ||||
|      * Creates a new RunnerOrganisation entity from this. | ||||
|      */ | ||||
|     public async toRunnerOrganisation(): Promise<RunnerOrganisation> { | ||||
|     public async toEntity(): Promise<RunnerOrganisation> { | ||||
|         let newRunnerOrganisation: RunnerOrganisation = new RunnerOrganisation(); | ||||
| 
 | ||||
|         newRunnerOrganisation.name = this.name; | ||||
|         newRunnerOrganisation.contact = await this.getContact(); | ||||
|         newRunnerOrganisation.address = await this.getAddress(); | ||||
|         // newRunnerOrganisation.address = await this.getAddress();
 | ||||
| 
 | ||||
|         return newRunnerOrganisation; | ||||
|     } | ||||
| @@ -1,9 +1,9 @@ | ||||
| import { IsInt, IsNotEmpty } from 'class-validator'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { RunnerOrganisationNotFoundError, RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors'; | ||||
| import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors'; | ||||
| import { RunnerOrganisation } from '../entities/RunnerOrganisation'; | ||||
| import { RunnerTeam } from '../entities/RunnerTeam'; | ||||
| import { RunnerOrganisationNotFoundError, RunnerOrganisationWrongTypeError } from '../../../errors/RunnerOrganisationErrors'; | ||||
| import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors'; | ||||
| import { RunnerOrganisation } from '../../entities/RunnerOrganisation'; | ||||
| import { RunnerTeam } from '../../entities/RunnerTeam'; | ||||
| import { CreateRunnerGroup } from './CreateRunnerGroup'; | ||||
| 
 | ||||
| /** | ||||
| @@ -37,7 +37,7 @@ export class CreateRunnerTeam extends CreateRunnerGroup { | ||||
|     /** | ||||
|      * Creates a new RunnerTeam entity from this. | ||||
|      */ | ||||
|     public async toRunnerTeam(): Promise<RunnerTeam> { | ||||
|     public async toEntity(): Promise<RunnerTeam> { | ||||
|         let newRunnerTeam: RunnerTeam = new RunnerTeam(); | ||||
| 
 | ||||
|         newRunnerTeam.name = this.name; | ||||
							
								
								
									
										59
									
								
								src/models/actions/create/CreateScan.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/models/actions/create/CreateScan.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| import { IsBoolean, IsInt, IsOptional, IsPositive } from 'class-validator'; | ||||
| import { getConnection } from 'typeorm'; | ||||
| import { RunnerNotFoundError } from '../../../errors/RunnerErrors'; | ||||
| import { Runner } from '../../entities/Runner'; | ||||
| import { Scan } from '../../entities/Scan'; | ||||
|  | ||||
| /** | ||||
|  * This class is used to create a new Scan entity from a json body (post request). | ||||
|  */ | ||||
| export abstract class CreateScan { | ||||
|     /** | ||||
|      * The scan's associated runner. | ||||
|      * This is important to link ran distances to runners. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsPositive() | ||||
|     runner: number; | ||||
|  | ||||
|     /** | ||||
|      * Is the scan valid (for fraud reasons). | ||||
|      * The determination of validity will work differently for every child class. | ||||
|      * Default: true | ||||
|      */ | ||||
|     @IsBoolean() | ||||
|     @IsOptional() | ||||
|     valid?: boolean = true; | ||||
|  | ||||
|     /** | ||||
|      * The scan's distance in meters. | ||||
|      * Can be set manually or derived from another object. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsPositive() | ||||
|     public distance: number; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new Scan entity from this. | ||||
|      */ | ||||
|     public async toEntity(): Promise<Scan> { | ||||
|         let newScan = new Scan(); | ||||
|  | ||||
|         newScan.distance = this.distance; | ||||
|         newScan.valid = this.valid; | ||||
|         newScan.runner = await this.getRunner(); | ||||
|  | ||||
|         return newScan; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										64
									
								
								src/models/actions/create/CreateScanStation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/models/actions/create/CreateScanStation.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| import * as argon2 from "argon2"; | ||||
| import { IsBoolean, IsInt, IsOptional, IsPositive, IsString } from 'class-validator'; | ||||
| import crypto from 'crypto'; | ||||
| import { getConnection } from 'typeorm'; | ||||
| import * as uuid from 'uuid'; | ||||
| import { TrackNotFoundError } from '../../../errors/TrackErrors'; | ||||
| import { ScanStation } from '../../entities/ScanStation'; | ||||
| import { Track } from '../../entities/Track'; | ||||
|  | ||||
| /** | ||||
|  * This class is used to create a new StatsClient entity from a json body (post request). | ||||
|  */ | ||||
| export class CreateScanStation { | ||||
|     /** | ||||
|      * The new station's description. | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsOptional() | ||||
|     description?: string; | ||||
|  | ||||
|     /** | ||||
|      * The station's associated track. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsPositive() | ||||
|     track: number; | ||||
|  | ||||
|     /** | ||||
|      * Is this station enabled? | ||||
|      */ | ||||
|     @IsBoolean() | ||||
|     @IsOptional() | ||||
|     enabled?: boolean = true; | ||||
|  | ||||
|     /** | ||||
|      * Converts this to a ScanStation entity. | ||||
|      */ | ||||
|     public async toEntity(): Promise<ScanStation> { | ||||
|         let newStation: ScanStation = new ScanStation(); | ||||
|  | ||||
|         newStation.description = this.description; | ||||
|         newStation.enabled = this.enabled; | ||||
|         newStation.track = await this.getTrack(); | ||||
|  | ||||
|         let newUUID = uuid.v4().toUpperCase(); | ||||
|         newStation.prefix = crypto.createHash("sha3-512").update(newUUID).digest('hex').substring(0, 7).toUpperCase(); | ||||
|         newStation.key = await argon2.hash(newStation.prefix + "." + newUUID); | ||||
|         newStation.cleartextkey = newStation.prefix + "." + newUUID; | ||||
|  | ||||
|         return newStation; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get's a track by it's id provided via this.track. | ||||
|      * Used to link the new station to a track. | ||||
|      */ | ||||
|     public async getTrack(): Promise<Track> { | ||||
|         const track = await getConnection().getRepository(Track).findOne({ id: this.track }); | ||||
|         if (!track) { | ||||
|             throw new TrackNotFoundError(); | ||||
|         } | ||||
|         return track; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										33
									
								
								src/models/actions/create/CreateStatsClient.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/models/actions/create/CreateStatsClient.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| import * as argon2 from "argon2"; | ||||
| import { IsOptional, IsString } from 'class-validator'; | ||||
| import crypto from 'crypto'; | ||||
| import * as uuid from 'uuid'; | ||||
| import { StatsClient } from '../../entities/StatsClient'; | ||||
|  | ||||
| /** | ||||
|  * This classed is used to create a new StatsClient entity from a json body (post request). | ||||
|  */ | ||||
| export class CreateStatsClient { | ||||
|     /** | ||||
|      * The new client's description. | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsOptional() | ||||
|     description?: string; | ||||
|  | ||||
|     /** | ||||
|      * Converts this to a StatsClient entity. | ||||
|      */ | ||||
|     public async toEntity(): Promise<StatsClient> { | ||||
|         let newClient: StatsClient = new StatsClient(); | ||||
|  | ||||
|         newClient.description = this.description; | ||||
|  | ||||
|         let newUUID = uuid.v4().toUpperCase(); | ||||
|         newClient.prefix = crypto.createHash("sha3-512").update(newUUID).digest('hex').substring(0, 7).toUpperCase(); | ||||
|         newClient.key = await argon2.hash(newClient.prefix + "." + newUUID); | ||||
|         newClient.cleartextkey = newClient.prefix + "." + newUUID; | ||||
|  | ||||
|         return newClient; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										46
									
								
								src/models/actions/create/CreateTrack.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/models/actions/create/CreateTrack.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| import { IsInt, IsNotEmpty, IsOptional, IsPositive, IsString } from 'class-validator'; | ||||
| import { TrackLapTimeCantBeNegativeError } from '../../../errors/TrackErrors'; | ||||
| import { Track } from '../../entities/Track'; | ||||
|  | ||||
| /** | ||||
|  * This classed is used to create a new Track entity from a json body (post request). | ||||
|  */ | ||||
| export class CreateTrack { | ||||
|     /** | ||||
|      * The new track's name. | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsNotEmpty() | ||||
|     name: string; | ||||
|  | ||||
|     /** | ||||
|      * The new track's distance in meters (must be greater than 0). | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsPositive() | ||||
|     distance: number; | ||||
|  | ||||
|     /** | ||||
|      * The minimum time a runner should take to run a lap on this track (in seconds). | ||||
|      * Will be used for fraud detection. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsOptional() | ||||
|     minimumLapTime: number; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new Track entity from this. | ||||
|      */ | ||||
|     public toEntity(): Track { | ||||
|         let newTrack: Track = new Track(); | ||||
|  | ||||
|         newTrack.name = this.name; | ||||
|         newTrack.distance = this.distance; | ||||
|         newTrack.minimumLapTime = this.minimumLapTime; | ||||
|         if (this.minimumLapTime < 0) { | ||||
|             throw new TrackLapTimeCantBeNegativeError(); | ||||
|         } | ||||
|  | ||||
|         return newTrack; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										79
									
								
								src/models/actions/create/CreateTrackScan.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/models/actions/create/CreateTrackScan.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| import { IsInt, IsPositive } from 'class-validator'; | ||||
| import { getConnection } from 'typeorm'; | ||||
| import { RunnerCardNotFoundError } from '../../../errors/RunnerCardErrors'; | ||||
| import { RunnerNotFoundError } from '../../../errors/RunnerErrors'; | ||||
| import { ScanStationNotFoundError } from '../../../errors/ScanStationErrors'; | ||||
| import { RunnerCard } from '../../entities/RunnerCard'; | ||||
| import { ScanStation } from '../../entities/ScanStation'; | ||||
| import { TrackScan } from '../../entities/TrackScan'; | ||||
|  | ||||
| /** | ||||
|  * This classed is used to create a new Scan entity from a json body (post request). | ||||
|  */ | ||||
| export class CreateTrackScan { | ||||
|     /** | ||||
|      * The runnerCard associated with the scan. | ||||
|      * This get's saved for documentation and management purposes. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsPositive() | ||||
|     card: number; | ||||
|  | ||||
|     /** | ||||
|      * The scanning station that created the scan. | ||||
|      * Mainly used for logging and traceing back scans (or errors) | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsPositive() | ||||
|     station: number; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new Track entity from this. | ||||
|      */ | ||||
|     public async toEntity(): Promise<TrackScan> { | ||||
|         let newScan: TrackScan = new TrackScan(); | ||||
|  | ||||
|         newScan.station = await this.getStation(); | ||||
|         newScan.card = await this.getCard(); | ||||
|  | ||||
|         newScan.track = newScan.station.track; | ||||
|         newScan.runner = newScan.card.runner; | ||||
|  | ||||
|         if (!newScan.runner) { | ||||
|             throw new RunnerNotFoundError(); | ||||
|         } | ||||
|  | ||||
|         newScan.timestamp = Math.round(new Date().getTime() / 1000); | ||||
|         newScan.valid = await this.validateScan(newScan); | ||||
|  | ||||
|         return newScan; | ||||
|     } | ||||
|  | ||||
|     public async getCard(): Promise<RunnerCard> { | ||||
|         const track = await getConnection().getRepository(RunnerCard).findOne({ id: this.card }, { relations: ["runner"] }); | ||||
|         if (!track) { | ||||
|             throw new RunnerCardNotFoundError(); | ||||
|         } | ||||
|         return track; | ||||
|     } | ||||
|  | ||||
|     public async getStation(): Promise<ScanStation> { | ||||
|         const station = await getConnection().getRepository(ScanStation).findOne({ id: this.station }, { relations: ["track"] }); | ||||
|         if (!station) { | ||||
|             throw new ScanStationNotFoundError(); | ||||
|         } | ||||
|         return station; | ||||
|     } | ||||
|  | ||||
|     public async validateScan(scan: TrackScan): Promise<boolean> { | ||||
|         const scans = await getConnection().getRepository(TrackScan).find({ where: { runner: scan.runner, valid: true }, relations: ["track"] }); | ||||
|         if (scans.length == 0) { return true; } | ||||
|  | ||||
|         const newestScan = scans[scans.length - 1]; | ||||
|         if ((scan.timestamp - newestScan.timestamp) > scan.track.minimumLapTime) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| @@ -1,12 +1,12 @@ | ||||
| import * as argon2 from "argon2"; | ||||
| import { IsBoolean, IsEmail, IsOptional, IsPhoneNumber, IsString } from 'class-validator'; | ||||
| import { IsBoolean, IsEmail, 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 { UserGroupNotFoundError } from '../../errors/UserGroupErrors'; | ||||
| import { User } from '../entities/User'; | ||||
| import { UserGroup } from '../entities/UserGroup'; | ||||
| import { config } from '../../../config'; | ||||
| import { UsernameOrEmailNeededError } from '../../../errors/UserErrors'; | ||||
| import { UserGroupNotFoundError } from '../../../errors/UserGroupErrors'; | ||||
| import { User } from '../../entities/User'; | ||||
| import { UserGroup } from '../../entities/UserGroup'; | ||||
| 
 | ||||
| /** | ||||
|  * This classed is used to create a new User entity from a json body (post request). | ||||
| @@ -78,12 +78,18 @@ export class CreateUser { | ||||
|     @IsOptional() | ||||
|     groups?: number[] | number | ||||
| 
 | ||||
|     //TODO: ProfilePics
 | ||||
|     /** | ||||
|     * The user's profile pic (or rather a url pointing to it). | ||||
|     */ | ||||
|     @IsString() | ||||
|     @IsUrl() | ||||
|     @IsOptional() | ||||
|     profilePic?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Converts this to a User entity. | ||||
|      */ | ||||
|     public async toUser(): Promise<User> { | ||||
|     public async toEntity(): Promise<User> { | ||||
|         let newUser: User = new User(); | ||||
| 
 | ||||
|         if (this.email === undefined && this.username === undefined) { | ||||
| @@ -100,7 +106,9 @@ export class CreateUser { | ||||
|         newUser.password = await argon2.hash(this.password + newUser.uuid); | ||||
|         newUser.groups = await this.getGroups(); | ||||
|         newUser.enabled = this.enabled; | ||||
|         //TODO: ProfilePics
 | ||||
| 
 | ||||
|         if (!this.profilePic) { newUser.profilePic = `https://dev.lauf-fuer-kaya.de/lfk-logo.png`; } | ||||
|         else { newUser.profilePic = this.profilePic; } | ||||
| 
 | ||||
|         return newUser; | ||||
|     } | ||||
| @@ -1,5 +1,5 @@ | ||||
| import { IsOptional, IsString } from 'class-validator'; | ||||
| import { UserGroup } from '../entities/UserGroup'; | ||||
| import { UserGroup } from '../../entities/UserGroup'; | ||||
| 
 | ||||
| /** | ||||
|  * This classed is used to create a new UserGroup entity from a json body (post request). | ||||
| @@ -22,7 +22,7 @@ export class CreateUserGroup { | ||||
|     /** | ||||
|      * Creates a new UserGroup entity from this. | ||||
|      */ | ||||
|     public async toUserGroup(): Promise<UserGroup> { | ||||
|     public async toEntity(): Promise<UserGroup> { | ||||
|         let newUserGroup: UserGroup = new UserGroup(); | ||||
| 
 | ||||
|         newUserGroup.name = this.name; | ||||
							
								
								
									
										44
									
								
								src/models/actions/update/UpdateDonor.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/models/actions/update/UpdateDonor.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| import { IsBoolean, IsInt, IsOptional } from 'class-validator'; | ||||
| import { DonorReceiptAddressNeededError } from '../../../errors/DonorErrors'; | ||||
| import { Donor } from '../../entities/Donor'; | ||||
| import { CreateParticipant } from '../create/CreateParticipant'; | ||||
|  | ||||
| /** | ||||
|  * This class is used to update a Donor entity (via put request). | ||||
|  */ | ||||
| export class UpdateDonor extends CreateParticipant { | ||||
|  | ||||
|     /** | ||||
|      * The updated donor'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; | ||||
|  | ||||
|     /** | ||||
|      * Does the updated donor need a receipt? | ||||
|      */ | ||||
|     @IsBoolean() | ||||
|     @IsOptional() | ||||
|     receiptNeeded?: boolean; | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Updates a provided Donor entity based on this. | ||||
|      */ | ||||
|     public async update(donor: Donor): Promise<Donor> { | ||||
|         donor.firstname = this.firstname; | ||||
|         donor.middlename = this.middlename; | ||||
|         donor.lastname = this.lastname; | ||||
|         donor.phone = this.phone; | ||||
|         donor.email = this.email; | ||||
|         donor.receiptNeeded = this.receiptNeeded; | ||||
|         donor.address = await this.getAddress(); | ||||
|  | ||||
|         if (this.receiptNeeded == true && this.address == null) { | ||||
|             throw new DonorReceiptAddressNeededError() | ||||
|         } | ||||
|  | ||||
|         return donor; | ||||
|     } | ||||
| } | ||||
| @@ -1,11 +1,11 @@ | ||||
| import { IsInt, IsNotEmpty, IsObject } from 'class-validator'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { PermissionNeedsPrincipalError } from '../../errors/PermissionErrors'; | ||||
| import { PrincipalNotFoundError, PrincipalWrongTypeError } from '../../errors/PrincipalErrors'; | ||||
| import { Permission } from '../entities/Permission'; | ||||
| import { Principal } from '../entities/Principal'; | ||||
| import { PermissionAction } from '../enums/PermissionAction'; | ||||
| import { PermissionTarget } from '../enums/PermissionTargets'; | ||||
| import { PermissionNeedsPrincipalError } from '../../../errors/PermissionErrors'; | ||||
| import { PrincipalNotFoundError, PrincipalWrongTypeError } from '../../../errors/PrincipalErrors'; | ||||
| import { Permission } from '../../entities/Permission'; | ||||
| import { Principal } from '../../entities/Principal'; | ||||
| import { PermissionAction } from '../../enums/PermissionAction'; | ||||
| import { PermissionTarget } from '../../enums/PermissionTargets'; | ||||
| 
 | ||||
| /** | ||||
|  * This class is used to update a Permission entity (via put request). | ||||
| @@ -42,7 +42,7 @@ export class UpdatePermission { | ||||
|     /** | ||||
|      * Updates a provided Permission entity based on this. | ||||
|      */ | ||||
|     public async updatePermission(permission: Permission): Promise<Permission> { | ||||
|     public async update(permission: Permission): Promise<Permission> { | ||||
|         permission.principal = await this.getPrincipal(); | ||||
|         permission.target = this.target; | ||||
|         permission.action = this.action; | ||||
| @@ -1,11 +1,11 @@ | ||||
| import { IsInt, IsObject } from 'class-validator'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { RunnerGroupNotFoundError } from '../../errors/RunnerGroupErrors'; | ||||
| import { RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors'; | ||||
| import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors'; | ||||
| import { Runner } from '../entities/Runner'; | ||||
| import { RunnerGroup } from '../entities/RunnerGroup'; | ||||
| import { CreateParticipant } from './CreateParticipant'; | ||||
| import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors'; | ||||
| import { RunnerOrganisationWrongTypeError } from '../../../errors/RunnerOrganisationErrors'; | ||||
| import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors'; | ||||
| import { Runner } from '../../entities/Runner'; | ||||
| import { RunnerGroup } from '../../entities/RunnerGroup'; | ||||
| import { CreateParticipant } from '../create/CreateParticipant'; | ||||
| 
 | ||||
| /** | ||||
|  * This class is used to update a Runner entity (via put request). | ||||
| @@ -29,7 +29,7 @@ export class UpdateRunner extends CreateParticipant { | ||||
|     /** | ||||
|      * Updates a provided Runner entity based on this. | ||||
|      */ | ||||
|     public async updateRunner(runner: Runner): Promise<Runner> { | ||||
|     public async update(runner: Runner): Promise<Runner> { | ||||
|         runner.firstname = this.firstname; | ||||
|         runner.middlename = this.middlename; | ||||
|         runner.lastname = this.lastname; | ||||
							
								
								
									
										51
									
								
								src/models/actions/update/UpdateRunnerCard.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/models/actions/update/UpdateRunnerCard.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| import { IsBoolean, IsInt, IsOptional, IsPositive } from 'class-validator'; | ||||
| import { getConnection } from 'typeorm'; | ||||
| import { RunnerNotFoundError } from '../../../errors/RunnerErrors'; | ||||
| import { Runner } from '../../entities/Runner'; | ||||
| import { RunnerCard } from '../../entities/RunnerCard'; | ||||
|  | ||||
| /** | ||||
|  * This class is used to update a RunnerCard entity (via put request). | ||||
|  */ | ||||
| export class UpdateRunnerCard { | ||||
|     /** | ||||
|      * The updated card'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() | ||||
|     @IsPositive() | ||||
|     id?: number; | ||||
|  | ||||
|     /** | ||||
|      * The updated card's associated runner. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsOptional() | ||||
|     runner?: number; | ||||
|  | ||||
|     /** | ||||
|      * Is the updated card enabled (for fraud reasons)? | ||||
|      * Default: true | ||||
|      */ | ||||
|     @IsBoolean() | ||||
|     enabled: boolean = true; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new RunnerCard entity from this. | ||||
|      */ | ||||
|     public async update(card: RunnerCard): Promise<RunnerCard> { | ||||
|         card.enabled = this.enabled; | ||||
|         card.runner = await this.getRunner(); | ||||
|  | ||||
|         return card; | ||||
|     } | ||||
|  | ||||
|     public async getRunner(): Promise<Runner> { | ||||
|         if (!this.runner) { return null; } | ||||
|         const runner = await getConnection().getRepository(Runner).findOne({ id: this.runner }); | ||||
|         if (!runner) { | ||||
|             throw new RunnerNotFoundError(); | ||||
|         } | ||||
|         return runner; | ||||
|     } | ||||
| } | ||||
| @@ -1,9 +1,9 @@ | ||||
| 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 './CreateRunnerGroup'; | ||||
| 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). | ||||
| @@ -41,11 +41,11 @@ export class UpdateRunnerOrganisation extends CreateRunnerGroup { | ||||
|     /** | ||||
|      * Updates a provided RunnerOrganisation entity based on this. | ||||
|      */ | ||||
|     public async updateRunnerOrganisation(organisation: RunnerOrganisation): Promise<RunnerOrganisation> { | ||||
|     public async update(organisation: RunnerOrganisation): Promise<RunnerOrganisation> { | ||||
| 
 | ||||
|         organisation.name = this.name; | ||||
|         organisation.contact = await this.getContact(); | ||||
|         organisation.address = await this.getAddress(); | ||||
|         // organisation.address = await this.getAddress();
 | ||||
| 
 | ||||
|         return organisation; | ||||
|     } | ||||
| @@ -1,10 +1,10 @@ | ||||
| import { IsInt, IsNotEmpty, IsObject } from 'class-validator'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { RunnerOrganisationNotFoundError, RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors'; | ||||
| import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors'; | ||||
| import { RunnerOrganisation } from '../entities/RunnerOrganisation'; | ||||
| import { RunnerTeam } from '../entities/RunnerTeam'; | ||||
| import { CreateRunnerGroup } from './CreateRunnerGroup'; | ||||
| import { RunnerOrganisationNotFoundError, RunnerOrganisationWrongTypeError } from '../../../errors/RunnerOrganisationErrors'; | ||||
| import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors'; | ||||
| import { RunnerOrganisation } from '../../entities/RunnerOrganisation'; | ||||
| import { RunnerTeam } from '../../entities/RunnerTeam'; | ||||
| import { CreateRunnerGroup } from '../create/CreateRunnerGroup'; | ||||
| 
 | ||||
| /** | ||||
|  * This class is used to update a RunnerTeam entity (via put request). | ||||
| @@ -45,7 +45,7 @@ export class UpdateRunnerTeam extends CreateRunnerGroup { | ||||
|     /** | ||||
|      * Updates a provided RunnerTeam entity based on this. | ||||
|      */ | ||||
|     public async updateRunnerTeam(team: RunnerTeam): Promise<RunnerTeam> { | ||||
|     public async update(team: RunnerTeam): Promise<RunnerTeam> { | ||||
| 
 | ||||
|         team.name = this.name; | ||||
|         team.parentGroup = await this.getParent(); | ||||
							
								
								
									
										62
									
								
								src/models/actions/update/UpdateScan.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/models/actions/update/UpdateScan.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| import { IsBoolean, IsInt, IsOptional, IsPositive } from 'class-validator'; | ||||
| import { getConnection } from 'typeorm'; | ||||
| import { RunnerNotFoundError } from '../../../errors/RunnerErrors'; | ||||
| import { Runner } from '../../entities/Runner'; | ||||
| import { Scan } from '../../entities/Scan'; | ||||
|  | ||||
| /** | ||||
|  * This class is used to update a Scan entity (via put request) | ||||
|  */ | ||||
| export abstract class UpdateScan { | ||||
|     /** | ||||
|      * The updated scan'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 scan's associated runner. | ||||
|      * This is important to link ran distances to runners. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsPositive() | ||||
|     runner: number; | ||||
|  | ||||
|     /** | ||||
|      * Is the updated scan valid (for fraud reasons). | ||||
|      */ | ||||
|     @IsBoolean() | ||||
|     @IsOptional() | ||||
|     valid?: boolean = true; | ||||
|  | ||||
|     /** | ||||
|      * The updated scan's distance in meters. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsPositive() | ||||
|     public distance: number; | ||||
|  | ||||
|     /** | ||||
|      * Update a Scan entity based on this. | ||||
|      * @param scan The scan that shall be updated. | ||||
|      */ | ||||
|     public async update(scan: Scan): Promise<Scan> { | ||||
|         scan.distance = this.distance; | ||||
|         scan.valid = this.valid; | ||||
|         scan.runner = await this.getRunner(); | ||||
|  | ||||
|         return scan; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										39
									
								
								src/models/actions/update/UpdateScanStation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/models/actions/update/UpdateScanStation.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| import { IsBoolean, IsInt, IsOptional, IsString } from 'class-validator'; | ||||
| import { ScanStation } from '../../entities/ScanStation'; | ||||
|  | ||||
| /** | ||||
|  * This class is used to update a ScanStation entity (via put request) | ||||
|  */ | ||||
| export class UpdateScanStation { | ||||
|     /** | ||||
|      * The updated station'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 station's description. | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsOptional() | ||||
|     description?: string; | ||||
|  | ||||
|     /** | ||||
|      * Is this station enabled? | ||||
|      */ | ||||
|     @IsBoolean() | ||||
|     @IsOptional() | ||||
|     enabled?: boolean = true; | ||||
|  | ||||
|     /** | ||||
|      * Update a ScanStation entity based on this. | ||||
|      * @param station The station that shall be updated. | ||||
|      */ | ||||
|     public async update(station: ScanStation): Promise<ScanStation> { | ||||
|         station.description = this.description; | ||||
|         station.enabled = this.enabled; | ||||
|  | ||||
|         return station; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										50
									
								
								src/models/actions/update/UpdateTrack.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/models/actions/update/UpdateTrack.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| import { IsInt, IsNotEmpty, IsOptional, IsPositive, IsString } from 'class-validator'; | ||||
| import { TrackLapTimeCantBeNegativeError } from '../../../errors/TrackErrors'; | ||||
| import { Track } from '../../entities/Track'; | ||||
|  | ||||
| /** | ||||
|  * This class is used to update a Track entity (via put request). | ||||
|  */ | ||||
| export class UpdateTrack { | ||||
|     /** | ||||
|      * The updated track'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; | ||||
|  | ||||
|     @IsString() | ||||
|     @IsNotEmpty() | ||||
|     name: string; | ||||
|  | ||||
|     /** | ||||
|      * The updated track's distance in meters (must be greater than 0). | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsPositive() | ||||
|     distance: number; | ||||
|  | ||||
|     /** | ||||
|      * The minimum time a runner should take to run a lap on this track (in seconds). | ||||
|      * Will be used for fraud detection. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsOptional() | ||||
|     minimumLapTime: number; | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Update a Track entity based on this. | ||||
|      * @param track The track that shall be updated. | ||||
|      */ | ||||
|     public async update(track: Track): Promise<Track> { | ||||
|         track.name = this.name; | ||||
|         track.distance = this.distance; | ||||
|         track.minimumLapTime = this.minimumLapTime; | ||||
|         if (this.minimumLapTime < 0) { | ||||
|             throw new TrackLapTimeCantBeNegativeError(); | ||||
|         } | ||||
|  | ||||
|         return track; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										81
									
								
								src/models/actions/update/UpdateTrackScan.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/models/actions/update/UpdateTrackScan.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| import { IsBoolean, IsInt, IsOptional } from 'class-validator'; | ||||
| import { getConnection } from 'typeorm'; | ||||
| import { RunnerNotFoundError } from '../../../errors/RunnerErrors'; | ||||
| import { ScanStationNotFoundError } from '../../../errors/ScanStationErrors'; | ||||
| import { Runner } from '../../entities/Runner'; | ||||
| import { ScanStation } from '../../entities/ScanStation'; | ||||
| import { TrackScan } from '../../entities/TrackScan'; | ||||
|  | ||||
| /** | ||||
|  * This class is used to update a TrackScan entity (via put request) | ||||
|  */ | ||||
| export abstract class UpdateTrackScan { | ||||
|     /** | ||||
|      * The updated scan'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 scan's associated runner. | ||||
|      * This is important to link ran distances to runners. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsOptional() | ||||
|     runner?: number; | ||||
|  | ||||
|     /** | ||||
|      * Is the updated scan valid (for fraud reasons). | ||||
|      */ | ||||
|     @IsBoolean() | ||||
|     @IsOptional() | ||||
|     valid?: boolean = true; | ||||
|  | ||||
|     /** | ||||
|      * The updated scan's associated station. | ||||
|      * This is important to link ran distances to runners. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsOptional() | ||||
|     public station?: number; | ||||
|  | ||||
|     /** | ||||
|      * Update a TrackScan entity based on this. | ||||
|      * @param scan The scan that shall be updated. | ||||
|      */ | ||||
|     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.track = scan.station.track; | ||||
|  | ||||
|         return scan; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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 }); | ||||
|         if (!station) { | ||||
|             throw new ScanStationNotFoundError(); | ||||
|         } | ||||
|         return station; | ||||
|     } | ||||
| } | ||||
| @@ -1,11 +1,11 @@ | ||||
| import * as argon2 from "argon2"; | ||||
| import { IsBoolean, IsEmail, IsInt, IsOptional, IsPhoneNumber, IsString } from 'class-validator'; | ||||
| import { IsBoolean, IsEmail, IsInt, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { config } from '../../config'; | ||||
| import { UsernameOrEmailNeededError } from '../../errors/AuthError'; | ||||
| import { UserGroupNotFoundError } from '../../errors/UserGroupErrors'; | ||||
| import { User } from '../entities/User'; | ||||
| import { UserGroup } from '../entities/UserGroup'; | ||||
| import { config } from '../../../config'; | ||||
| import { UsernameOrEmailNeededError } from '../../../errors/AuthError'; | ||||
| import { UserGroupNotFoundError } from '../../../errors/UserGroupErrors'; | ||||
| import { User } from '../../entities/User'; | ||||
| import { UserGroup } from '../../entities/UserGroup'; | ||||
| 
 | ||||
| /** | ||||
|  * This class is used to update a User entity (via put request). | ||||
| @@ -87,9 +87,18 @@ export class UpdateUser { | ||||
|     groups?: UserGroup[] | ||||
| 
 | ||||
|     /** | ||||
|      * Updates a provided User entity based on this. | ||||
|     * The user's profile pic (or rather a url pointing to it). | ||||
|     */ | ||||
|     @IsString() | ||||
|     @IsUrl() | ||||
|     @IsOptional() | ||||
|     profilePic?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Updates a user entity based on this. | ||||
|      * @param user The user that shall be updated. | ||||
|      */ | ||||
|     public async updateUser(user: User): Promise<User> { | ||||
|     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)) { | ||||
| @@ -106,7 +115,9 @@ export class UpdateUser { | ||||
|         user.lastname = this.lastname | ||||
|         user.phone = this.phone; | ||||
|         user.groups = await this.getGroups(); | ||||
|         //TODO: ProfilePics
 | ||||
| 
 | ||||
|         if (!this.profilePic) { user.profilePic = `https://dev.lauf-fuer-kaya.de/lfk-logo.png`; } | ||||
|         else { user.profilePic = this.profilePic; } | ||||
| 
 | ||||
|         return user; | ||||
|     } | ||||
							
								
								
									
										39
									
								
								src/models/actions/update/UpdateUserGroup.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/models/actions/update/UpdateUserGroup.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | ||||
| import { IsInt, IsOptional, IsString } from 'class-validator'; | ||||
| import { UserGroup } from '../../entities/UserGroup'; | ||||
|  | ||||
| /** | ||||
|  * This class is used to update a UserGroup entity (via put request). | ||||
|  */ | ||||
| export class UpdateUserGroup { | ||||
|  | ||||
|     /** | ||||
|      * The updated group'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 group's name. | ||||
|      */ | ||||
|     @IsString() | ||||
|     name: string; | ||||
|  | ||||
|     /** | ||||
|      * The updated groups's description. | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsOptional() | ||||
|     description?: string; | ||||
|  | ||||
|     /** | ||||
|      * Updates a group entity based on this. | ||||
|      * @param group The group that shall be updated. | ||||
|      */ | ||||
|     public async update(group: UserGroup): Promise<UserGroup> { | ||||
|         group.name = this.name; | ||||
|         group.description = this.description; | ||||
|  | ||||
|         return group; | ||||
|     } | ||||
| } | ||||
| @@ -7,8 +7,7 @@ import { | ||||
| } from "class-validator"; | ||||
| import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm"; | ||||
| import { config } from '../../config'; | ||||
| import { Participant } from "./Participant"; | ||||
| import { RunnerOrganisation } from "./RunnerOrganisation"; | ||||
| import { IAddressUser } from './IAddressUser'; | ||||
|  | ||||
| /** | ||||
|  * Defines the Address entity. | ||||
| @@ -79,12 +78,13 @@ export class Address { | ||||
|   /** | ||||
|    * Used to link the address to participants. | ||||
|    */ | ||||
|   @OneToMany(() => Participant, participant => participant.address, { nullable: true }) | ||||
|   participants: Participant[]; | ||||
|   @OneToMany(() => IAddressUser, addressUser => addressUser.address, { nullable: true }) | ||||
|   addressUsers: IAddressUser[]; | ||||
|  | ||||
|   /** | ||||
|    * Used to link the address to runner groups. | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   @OneToMany(() => RunnerOrganisation, group => group.address, { nullable: true }) | ||||
|   groups: RunnerOrganisation[]; | ||||
|   public toResponse() { | ||||
|     return new Error("NotImplemented"); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -39,4 +39,11 @@ export class DistanceDonation extends Donation { | ||||
|     } | ||||
|     return calculatedAmount; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   public toResponse() { | ||||
|     return new Error("NotImplemented"); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import { | ||||
|   IsNotEmpty | ||||
| } from "class-validator"; | ||||
| import { Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; | ||||
| import { Participant } from "./Participant"; | ||||
| import { Donor } from './Donor'; | ||||
|  | ||||
| /** | ||||
|  * Defines the Donation entity. | ||||
| @@ -24,12 +24,19 @@ export abstract class Donation { | ||||
|    * The donations's donor. | ||||
|    */ | ||||
|   @IsNotEmpty() | ||||
|   @ManyToOne(() => Participant, donor => donor.donations) | ||||
|   donor: Participant; | ||||
|   @ManyToOne(() => Donor, donor => donor.donations) | ||||
|   donor: Donor; | ||||
|  | ||||
|   /** | ||||
|    * 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 | Promise<number>; | ||||
|   abstract amount: number; | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   public toResponse() { | ||||
|     return new Error("NotImplemented"); | ||||
|   } | ||||
| } | ||||
| @@ -1,5 +1,7 @@ | ||||
| import { IsBoolean } from "class-validator"; | ||||
| import { ChildEntity, Column } from "typeorm"; | ||||
| import { ChildEntity, Column, OneToMany } from "typeorm"; | ||||
| import { ResponseDonor } from '../responses/ResponseDonor'; | ||||
| import { Donation } from './Donation'; | ||||
| import { Participant } from "./Participant"; | ||||
|  | ||||
| /** | ||||
| @@ -14,4 +16,18 @@ export class Donor extends Participant { | ||||
|   @Column() | ||||
|   @IsBoolean() | ||||
|   receiptNeeded: boolean = false; | ||||
|  | ||||
|   /** | ||||
|  * Used to link the participant as the donor of a donation. | ||||
|  * Attention: Only runner's can be associated as a distanceDonations distance source. | ||||
|  */ | ||||
|   @OneToMany(() => Donation, donation => donation.donor, { nullable: true }) | ||||
|   donations: Donation[]; | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   public toResponse(): ResponseDonor { | ||||
|     return new ResponseDonor(this); | ||||
|   } | ||||
| } | ||||
| @@ -16,4 +16,11 @@ export class FixedDonation extends Donation { | ||||
|   @IsInt() | ||||
|   @IsPositive() | ||||
|   amount: number; | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   public toResponse() { | ||||
|     return new Error("NotImplemented"); | ||||
|   } | ||||
| } | ||||
| @@ -10,6 +10,7 @@ import { | ||||
| import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; | ||||
| import { config } from '../../config'; | ||||
| import { Address } from "./Address"; | ||||
| import { IAddressUser } from './IAddressUser'; | ||||
| import { RunnerGroup } from "./RunnerGroup"; | ||||
|  | ||||
| /** | ||||
| @@ -17,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 { | ||||
| export class GroupContact implements IAddressUser { | ||||
|   /** | ||||
|    * Autogenerated unique id (primary key). | ||||
|    */ | ||||
| @@ -54,7 +55,7 @@ export class GroupContact { | ||||
|    * This is a address object to prevent any formatting differences. | ||||
|    */ | ||||
|   @IsOptional() | ||||
|   @ManyToOne(() => Address, address => address.participants, { nullable: true }) | ||||
|   @ManyToOne(() => Address, address => address.addressUsers, { nullable: true }) | ||||
|   address?: Address; | ||||
|  | ||||
|   /** | ||||
| @@ -80,4 +81,11 @@ export class GroupContact { | ||||
|     */ | ||||
|   @OneToMany(() => RunnerGroup, group => group.contact, { nullable: true }) | ||||
|   groups: RunnerGroup[]; | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   public toResponse() { | ||||
|     return new Error("NotImplemented"); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										20
									
								
								src/models/entities/IAddressUser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/models/entities/IAddressUser.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| 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,10 +7,11 @@ import { | ||||
|  | ||||
|   IsString | ||||
| } from "class-validator"; | ||||
| import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; | ||||
| import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; | ||||
| import { config } from '../../config'; | ||||
| import { ResponseParticipant } from '../responses/ResponseParticipant'; | ||||
| import { Address } from "./Address"; | ||||
| import { Donation } from "./Donation"; | ||||
| import { IAddressUser } from './IAddressUser'; | ||||
|  | ||||
| /** | ||||
|  * Defines the Participant entity. | ||||
| @@ -18,7 +19,7 @@ import { Donation } from "./Donation"; | ||||
| */ | ||||
| @Entity() | ||||
| @TableInheritance({ column: { name: "type", type: "varchar" } }) | ||||
| export abstract class Participant { | ||||
| export abstract class Participant implements IAddressUser { | ||||
|   /** | ||||
|    * Autogenerated unique id (primary key). | ||||
|    */ | ||||
| @@ -54,7 +55,7 @@ export abstract class Participant { | ||||
|    * The participant's address. | ||||
|    * This is a address object to prevent any formatting differences. | ||||
|    */ | ||||
|   @ManyToOne(() => Address, address => address.participants, { nullable: true }) | ||||
|   @ManyToOne(() => Address, address => address.addressUsers, { nullable: true }) | ||||
|   address?: Address; | ||||
|  | ||||
|   /** | ||||
| @@ -76,9 +77,7 @@ export abstract class Participant { | ||||
|   email?: string; | ||||
|  | ||||
|   /** | ||||
|    * Used to link the participant as the donor of a donation. | ||||
|    * Attention: Only runner's can be associated as a distanceDonations distance source. | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   @OneToMany(() => Donation, donation => donation.donor, { nullable: true }) | ||||
|   donations: Donation[]; | ||||
|   public abstract toResponse(): ResponseParticipant; | ||||
| } | ||||
| @@ -6,6 +6,7 @@ import { | ||||
| import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; | ||||
| import { PermissionAction } from '../enums/PermissionAction'; | ||||
| import { PermissionTarget } from '../enums/PermissionTargets'; | ||||
| import { ResponsePermission } from '../responses/ResponsePermission'; | ||||
| import { Principal } from './Principal'; | ||||
| /** | ||||
|  * Defines the Permission entity. | ||||
| @@ -51,4 +52,11 @@ export class Permission { | ||||
|   public toString(): string { | ||||
|     return this.target + ":" + this.action; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   public toResponse(): ResponsePermission { | ||||
|     return new ResponsePermission(this); | ||||
|   } | ||||
| } | ||||
| @@ -1,5 +1,6 @@ | ||||
| import { IsInt, IsNotEmpty } from "class-validator"; | ||||
| import { ChildEntity, ManyToOne, OneToMany } from "typeorm"; | ||||
| import { ResponseRunner } from '../responses/ResponseRunner'; | ||||
| import { DistanceDonation } from "./DistanceDonation"; | ||||
| import { Participant } from "./Participant"; | ||||
| import { RunnerCard } from "./RunnerCard"; | ||||
| @@ -18,7 +19,7 @@ export class Runner extends Participant { | ||||
|    * Can be a runner team or organisation. | ||||
|    */ | ||||
|   @IsNotEmpty() | ||||
|   @ManyToOne(() => RunnerGroup, group => group.runners, { nullable: false }) | ||||
|   @ManyToOne(() => RunnerGroup, group => group.runners) | ||||
|   group: RunnerGroup; | ||||
|  | ||||
|   /** | ||||
| @@ -47,7 +48,7 @@ export class Runner extends Participant { | ||||
|    * This is implemented here to avoid duplicate code in other files. | ||||
|    */ | ||||
|   public get validScans(): Scan[] { | ||||
|     return this.scans.filter(scan => { scan.valid === true }); | ||||
|     return this.scans.filter(scan => scan.valid == true); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -58,4 +59,19 @@ export class Runner extends Participant { | ||||
|   public get distance(): number { | ||||
|     return this.validScans.reduce((sum, current) => sum + current.distance, 0); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Returns the total donations a runner has collected based on his linked donations and distance ran. | ||||
|   */ | ||||
|   @IsInt() | ||||
|   public get distanceDonationAmount(): number { | ||||
|     return this.distanceDonations.reduce((sum, current) => sum + current.amountPerDistance, 0) * this.distance; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   public toResponse(): ResponseRunner { | ||||
|     return new ResponseRunner(this); | ||||
|   } | ||||
| } | ||||
| @@ -1,12 +1,13 @@ | ||||
| import { | ||||
|   IsBoolean, | ||||
|   IsEAN, | ||||
|  | ||||
|   IsInt, | ||||
|   IsNotEmpty, | ||||
|   IsOptional, | ||||
|   IsString | ||||
|  | ||||
|   IsOptional | ||||
| } from "class-validator"; | ||||
| import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; | ||||
| import { RunnerCardIdOutOfRangeError } from '../../errors/RunnerCardErrors'; | ||||
| import { ResponseRunnerCard } from '../responses/ResponseRunnerCard'; | ||||
| import { Runner } from "./Runner"; | ||||
| import { TrackScan } from "./TrackScan"; | ||||
|  | ||||
| @@ -32,17 +33,6 @@ export class RunnerCard { | ||||
|   @ManyToOne(() => Runner, runner => runner.cards, { nullable: true }) | ||||
|   runner: Runner; | ||||
|  | ||||
|   /** | ||||
|    * The card's code. | ||||
|    * This has to be able to being converted to something barcode compatible. | ||||
|    * Will get automaticlly generated (not implemented yet). | ||||
|    */ | ||||
|   @Column() | ||||
|   @IsEAN() | ||||
|   @IsString() | ||||
|   @IsNotEmpty() | ||||
|   code: string; | ||||
|  | ||||
|   /** | ||||
|    * Is the card enabled (for fraud reasons)? | ||||
|    * Default: true | ||||
| @@ -57,4 +47,38 @@ export class RunnerCard { | ||||
|    */ | ||||
|   @OneToMany(() => TrackScan, scan => scan.track, { nullable: true }) | ||||
|   scans: TrackScan[]; | ||||
|  | ||||
|   /** | ||||
|    * Generates a ean-13 compliant string for barcode generation. | ||||
|    */ | ||||
|   public get code(): string { | ||||
|     const multiply = [1, 3]; | ||||
|     let total = 0; | ||||
|     this.paddedId.split('').forEach((letter, index) => { | ||||
|       total += parseInt(letter, 10) * multiply[index % 2]; | ||||
|     }); | ||||
|     const checkSum = (Math.ceil(total / 10) * 10) - total; | ||||
|     return this.paddedId + checkSum.toString(); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Returns this card's id as a string padded to the length of 12 characters with leading zeros. | ||||
|    */ | ||||
|   private get paddedId(): string { | ||||
|     let id: string = this.id.toString(); | ||||
|  | ||||
|     if (id.length > 12) { | ||||
|       throw new RunnerCardIdOutOfRangeError(); | ||||
|     } | ||||
|     while (id.length < 12) { id = '0' + id; } | ||||
|  | ||||
|     return id; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   public toResponse() { | ||||
|     return new ResponseRunnerCard(this); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import { | ||||
|   IsString | ||||
| } from "class-validator"; | ||||
| import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; | ||||
| import { ResponseRunnerGroup } from '../responses/ResponseRunnerGroup'; | ||||
| import { GroupContact } from "./GroupContact"; | ||||
| import { Runner } from "./Runner"; | ||||
|  | ||||
| @@ -44,4 +45,25 @@ export abstract class RunnerGroup { | ||||
|    */ | ||||
|   @OneToMany(() => Runner, runner => runner.group, { nullable: true }) | ||||
|   runners: Runner[]; | ||||
|  | ||||
|   /** | ||||
|    * Returns the total distance ran by this group's runners based on all their valid scans. | ||||
|   */ | ||||
|   @IsInt() | ||||
|   public get distance(): number { | ||||
|     return this.runners.reduce((sum, current) => sum + current.distance, 0); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Returns the total donations a runner has collected based on his linked donations and distance ran. | ||||
|   */ | ||||
|   @IsInt() | ||||
|   public get distanceDonationAmount(): number { | ||||
|     return this.runners.reduce((sum, current) => sum + current.distanceDonationAmount, 0); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   public abstract toResponse(): ResponseRunnerGroup; | ||||
| } | ||||
| @@ -1,6 +1,9 @@ | ||||
| import { IsOptional } from "class-validator"; | ||||
| import { IsInt, IsOptional } from "class-validator"; | ||||
| import { ChildEntity, ManyToOne, OneToMany } from "typeorm"; | ||||
| import { Address } from "./Address"; | ||||
| import { ResponseRunnerOrganisation } from '../responses/ResponseRunnerOrganisation'; | ||||
| import { Address } from './Address'; | ||||
| import { IAddressUser } from './IAddressUser'; | ||||
| import { Runner } from './Runner'; | ||||
| import { RunnerGroup } from "./RunnerGroup"; | ||||
| import { RunnerTeam } from "./RunnerTeam"; | ||||
|  | ||||
| @@ -9,13 +12,13 @@ import { RunnerTeam } from "./RunnerTeam"; | ||||
|  * This usually is a school, club or company. | ||||
| */ | ||||
| @ChildEntity() | ||||
| export class RunnerOrganisation extends RunnerGroup { | ||||
| export class RunnerOrganisation extends RunnerGroup implements IAddressUser { | ||||
|  | ||||
|   /** | ||||
|    * The organisations's address. | ||||
|    */ | ||||
|   @IsOptional() | ||||
|   @ManyToOne(() => Address, address => address.groups, { nullable: true }) | ||||
|   @ManyToOne(() => Address, address => address.addressUsers, { nullable: true }) | ||||
|   address?: Address; | ||||
|  | ||||
|   /** | ||||
| @@ -24,4 +27,39 @@ export class RunnerOrganisation extends RunnerGroup { | ||||
|    */ | ||||
|   @OneToMany(() => RunnerTeam, team => team.parentGroup, { nullable: true }) | ||||
|   teams: RunnerTeam[]; | ||||
|  | ||||
|   /** | ||||
|    * Returns all runners associated with this organisation (directly or indirectly via teams). | ||||
|    */ | ||||
|   public get allRunners(): Runner[] { | ||||
|     let returnRunners: Runner[] = new Array<Runner>(); | ||||
|     returnRunners.push(...this.runners); | ||||
|     for (let team of this.teams) { | ||||
|       returnRunners.push(...team.runners) | ||||
|     } | ||||
|     return returnRunners; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Returns the total distance ran by this group's runners based on all their valid scans. | ||||
|   */ | ||||
|   @IsInt() | ||||
|   public get distance(): number { | ||||
|     return this.allRunners.reduce((sum, current) => sum + current.distance, 0); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Returns the total donations a runner has collected based on his linked donations and distance ran. | ||||
|   */ | ||||
|   @IsInt() | ||||
|   public get distanceDonationAmount(): number { | ||||
|     return this.allRunners.reduce((sum, current) => sum + current.distanceDonationAmount, 0); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   public toResponse(): ResponseRunnerOrganisation { | ||||
|     return new ResponseRunnerOrganisation(this); | ||||
|   } | ||||
| } | ||||
| @@ -1,5 +1,6 @@ | ||||
| import { IsNotEmpty } from "class-validator"; | ||||
| import { ChildEntity, ManyToOne } from "typeorm"; | ||||
| import { ResponseRunnerTeam } from '../responses/ResponseRunnerTeam'; | ||||
| import { RunnerGroup } from "./RunnerGroup"; | ||||
| import { RunnerOrganisation } from "./RunnerOrganisation"; | ||||
|  | ||||
| @@ -17,4 +18,11 @@ export class RunnerTeam extends RunnerGroup { | ||||
|   @IsNotEmpty() | ||||
|   @ManyToOne(() => RunnerOrganisation, org => org.teams, { nullable: true }) | ||||
|   parentGroup?: RunnerOrganisation; | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   public toResponse(): ResponseRunnerTeam { | ||||
|     return new ResponseRunnerTeam(this); | ||||
|   } | ||||
| } | ||||
| @@ -6,6 +6,7 @@ import { | ||||
|   IsPositive | ||||
| } from "class-validator"; | ||||
| import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; | ||||
| import { ResponseScan } from '../responses/ResponseScan'; | ||||
| import { Runner } from "./Runner"; | ||||
|  | ||||
| /** | ||||
| @@ -14,7 +15,7 @@ import { Runner } from "./Runner"; | ||||
| */ | ||||
| @Entity() | ||||
| @TableInheritance({ column: { name: "type", type: "varchar" } }) | ||||
| export abstract class Scan { | ||||
| export class Scan { | ||||
|   /** | ||||
|    * Autogenerated unique id (primary key). | ||||
|    */ | ||||
| @@ -30,14 +31,6 @@ export abstract class Scan { | ||||
|   @ManyToOne(() => Runner, runner => runner.scans, { nullable: false }) | ||||
|   runner: Runner; | ||||
|  | ||||
|   /** | ||||
|    * The scan's distance in meters. | ||||
|    * Can be set manually or derived from another object. | ||||
|    */ | ||||
|   @IsInt() | ||||
|   @IsPositive() | ||||
|   abstract distance: number; | ||||
|  | ||||
|   /** | ||||
|    * Is the scan valid (for fraud reasons). | ||||
|    * The determination of validity will work differently for every child class. | ||||
| @@ -46,4 +39,37 @@ export abstract class Scan { | ||||
|   @Column() | ||||
|   @IsBoolean() | ||||
|   valid: boolean = true; | ||||
|  | ||||
|   /** | ||||
|    * The scan's distance in meters. | ||||
|    * This is the "real" value used by "normal" scans.. | ||||
|    */ | ||||
|   @Column({ nullable: true }) | ||||
|   @IsInt() | ||||
|   private _distance?: number; | ||||
|  | ||||
|   /** | ||||
|    * The scan's distance in meters. | ||||
|    * Can be set manually or derived from another object. | ||||
|    */ | ||||
|   @IsInt() | ||||
|   @IsPositive() | ||||
|   public get distance(): number { | ||||
|     return this._distance; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * The scan's distance in meters. | ||||
|    * Can be set manually or derived from another object. | ||||
|    */ | ||||
|   public set distance(value: number) { | ||||
|     this._distance = value; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   public toResponse(): ResponseScan { | ||||
|     return new ResponseScan(this); | ||||
|   } | ||||
| } | ||||
| @@ -6,6 +6,7 @@ import { | ||||
|   IsString | ||||
| } from "class-validator"; | ||||
| import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; | ||||
| import { ResponseScanStation } from '../responses/ResponseScanStation'; | ||||
| import { Track } from "./Track"; | ||||
| import { TrackScan } from "./TrackScan"; | ||||
|  | ||||
| @@ -39,6 +40,14 @@ export class ScanStation { | ||||
|   @ManyToOne(() => Track, track => track.stations, { nullable: false }) | ||||
|   track: Track; | ||||
|  | ||||
|   /** | ||||
|    * The client's api key prefix. | ||||
|    * This is used identitfy a client by it's api key. | ||||
|    */ | ||||
|   @Column({ unique: true }) | ||||
|   @IsString() | ||||
|   prefix: string; | ||||
|  | ||||
|   /** | ||||
|    * The station's api key. | ||||
|    * This is used to authorize a station against the api (not implemented yet). | ||||
| @@ -49,16 +58,30 @@ export class ScanStation { | ||||
|   key: string; | ||||
|  | ||||
|   /** | ||||
|    * Is the station enabled (for fraud and setup reasons)? | ||||
|    * Default: true | ||||
|    * The client's api key in plain text. | ||||
|    * This will only be used to display the full key on creation and updates. | ||||
|    */ | ||||
|   @Column() | ||||
|   @IsBoolean() | ||||
|   enabled: boolean = true; | ||||
|   @IsString() | ||||
|   @IsOptional() | ||||
|   cleartextkey?: string; | ||||
|  | ||||
|   /** | ||||
|    * Used to link track scans to a scan station. | ||||
|    */ | ||||
|   @OneToMany(() => TrackScan, scan => scan.track, { nullable: true }) | ||||
|   scans: TrackScan[]; | ||||
|  | ||||
|   /** | ||||
|   * Is this station enabled? | ||||
|   */ | ||||
|   @Column({ nullable: true }) | ||||
|   @IsBoolean() | ||||
|   enabled?: boolean = true; | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   public toResponse(): ResponseScanStation { | ||||
|     return new ResponseScanStation(this); | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										56
									
								
								src/models/entities/StatsClient.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/models/entities/StatsClient.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| import { IsInt, IsOptional, IsString } from "class-validator"; | ||||
| import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; | ||||
| import { ResponseStatsClient } from '../responses/ResponseStatsClient'; | ||||
| /** | ||||
|  * Defines the StatsClient entity. | ||||
|  * StatsClients can be used to access the protected parts of the stats api (top runners, donators and so on). | ||||
| */ | ||||
| @Entity() | ||||
| export class StatsClient { | ||||
|     /** | ||||
|      * Autogenerated unique id (primary key). | ||||
|      */ | ||||
|     @PrimaryGeneratedColumn() | ||||
|     @IsInt() | ||||
|     id: number; | ||||
|  | ||||
|     /** | ||||
|      * The clients's description. | ||||
|      * Mostly for better UX when traceing back stuff. | ||||
|      */ | ||||
|     @Column({ nullable: true }) | ||||
|     @IsOptional() | ||||
|     @IsString() | ||||
|     description?: string; | ||||
|  | ||||
|     /** | ||||
|      * The client's api key prefix. | ||||
|      * This is used identitfy a client by it's api key. | ||||
|      */ | ||||
|     @Column({ unique: true }) | ||||
|     @IsString() | ||||
|     prefix: string; | ||||
|  | ||||
|     /** | ||||
|      * The client's api key hash. | ||||
|      * The api key can be used to authenticate against the /stats/** routes. | ||||
|      */ | ||||
|     @Column() | ||||
|     @IsString() | ||||
|     key: string; | ||||
|  | ||||
|     /** | ||||
|      * The client's api key in plain text. | ||||
|      * This will only be used to display the full key on creation and updates. | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsOptional() | ||||
|     cleartextkey?: string; | ||||
|  | ||||
|     /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|     public toResponse(): ResponseStatsClient { | ||||
|         return new ResponseStatsClient(this); | ||||
|     } | ||||
| } | ||||
| @@ -1,10 +1,12 @@ | ||||
| import { | ||||
|   IsInt, | ||||
|   IsNotEmpty, | ||||
|   IsOptional, | ||||
|   IsPositive, | ||||
|   IsString | ||||
| } from "class-validator"; | ||||
| import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm"; | ||||
| import { ResponseTrack } from '../responses/ResponseTrack'; | ||||
| import { ScanStation } from "./ScanStation"; | ||||
| import { TrackScan } from "./TrackScan"; | ||||
|  | ||||
| @@ -18,7 +20,7 @@ export class Track { | ||||
|    */ | ||||
|   @PrimaryGeneratedColumn() | ||||
|   @IsInt() | ||||
|   id: number;; | ||||
|   id: number; | ||||
|  | ||||
|   /** | ||||
|    * The track's name. | ||||
| @@ -38,6 +40,15 @@ export class Track { | ||||
|   @IsPositive() | ||||
|   distance: number; | ||||
|  | ||||
|   /** | ||||
|    * The minimum time a runner should take to run a lap on this track (in seconds). | ||||
|    * Will be used for fraud detection. | ||||
|    */ | ||||
|   @Column({ nullable: true }) | ||||
|   @IsInt() | ||||
|   @IsOptional() | ||||
|   minimumLapTime?: number; | ||||
|  | ||||
|   /** | ||||
|    * Used to link scan stations to a certain track. | ||||
|    * This makes the configuration of the scan stations easier. | ||||
| @@ -51,4 +62,11 @@ export class Track { | ||||
|    */ | ||||
|   @OneToMany(() => TrackScan, scan => scan.track, { nullable: true }) | ||||
|   scans: TrackScan[]; | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   public toResponse(): ResponseTrack { | ||||
|     return new ResponseTrack(this); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| import { | ||||
|   IsDateString, | ||||
|   IsInt, | ||||
|   IsNotEmpty, | ||||
|  | ||||
|   IsPositive | ||||
| } from "class-validator"; | ||||
| import { ChildEntity, Column, ManyToOne } from "typeorm"; | ||||
| import { ResponseTrackScan } from '../responses/ResponseTrackScan'; | ||||
| import { RunnerCard } from "./RunnerCard"; | ||||
| import { Scan } from "./Scan"; | ||||
| import { ScanStation } from "./ScanStation"; | ||||
| @@ -56,7 +56,13 @@ export class TrackScan extends Scan { | ||||
|    * Will be used to implement fraud detection. | ||||
|    */ | ||||
|   @Column() | ||||
|   @IsDateString() | ||||
|   @IsNotEmpty() | ||||
|   timestamp: string; | ||||
|   @IsInt() | ||||
|   timestamp: number; | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   public toResponse(): ResponseTrackScan { | ||||
|     return new ResponseTrackScan(this); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUUID } from "class-validator"; | ||||
| import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl, IsUUID } from "class-validator"; | ||||
| import { ChildEntity, Column, JoinTable, ManyToMany, OneToMany } from "typeorm"; | ||||
| import { config } from '../../config'; | ||||
| import { ResponsePrincipal } from '../responses/ResponsePrincipal'; | ||||
| @@ -106,10 +106,10 @@ export class User extends Principal { | ||||
|   * The user's profile picture. | ||||
|   * We haven't decided yet if this will be a bas64 encoded image or just a link to the profile picture. | ||||
|   */ | ||||
|   @Column({ nullable: true, unique: false }) | ||||
|   @Column({ nullable: false, unique: false }) | ||||
|   @IsString() | ||||
|   @IsOptional() | ||||
|   profilePic?: string; | ||||
|   @IsUrl() | ||||
|   profilePic: string; | ||||
|  | ||||
|   /** | ||||
|   * The last time the user requested a password reset. | ||||
| @@ -128,6 +128,26 @@ export class User extends Principal { | ||||
|   @OneToMany(() => UserAction, action => action.user, { nullable: true }) | ||||
|   actions: UserAction[] | ||||
|  | ||||
|   /** | ||||
|    * Resolves all permissions granted to this user through groups or directly to the string enum format. | ||||
|    */ | ||||
|   public get allPermissions(): string[] { | ||||
|     let returnPermissions: string[] = new Array<string>(); | ||||
|  | ||||
|     if (!this.permissions) { return returnPermissions; } | ||||
|     for (let permission of this.permissions) { | ||||
|       returnPermissions.push(permission.toString()); | ||||
|     } | ||||
|  | ||||
|     if (!this.groups) { return returnPermissions; } | ||||
|     for (let group of this.groups) { | ||||
|       for (let permission of group.permissions) { | ||||
|         returnPermissions.push(permission.toString()); | ||||
|       } | ||||
|     } | ||||
|     return Array.from(new Set(returnPermissions)); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   | ||||
| @@ -52,4 +52,11 @@ export class UserAction { | ||||
|   @IsOptional() | ||||
|   @IsString() | ||||
|   changed: string; | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   public toResponse() { | ||||
|     return new Error("NotImplemented"); | ||||
|   } | ||||
| } | ||||
| @@ -8,5 +8,10 @@ export enum PermissionTarget { | ||||
|     TRACK = 'TRACK', | ||||
|     USER = 'USER', | ||||
|     USERGROUP = 'USERGROUP', | ||||
|     PERMISSION = 'PERMISSION' | ||||
|     PERMISSION = 'PERMISSION', | ||||
|     STATSCLIENT = 'STATSCLIENT', | ||||
|     DONOR = 'DONOR', | ||||
|     SCAN = 'SCAN', | ||||
|     STATION = 'STATION', | ||||
|     CARD = 'CARD' | ||||
| } | ||||
| @@ -3,7 +3,7 @@ import { IsInt, IsString } from 'class-validator'; | ||||
| /** | ||||
|  * Defines the repsonse auth. | ||||
| */ | ||||
| export class Auth { | ||||
| export class ResponseAuth { | ||||
|   /** | ||||
|   * The access_token - JWT shortterm access token. | ||||
|   */ | ||||
|   | ||||
							
								
								
									
										26
									
								
								src/models/responses/ResponseDonor.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/models/responses/ResponseDonor.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| import { | ||||
|     IsBoolean | ||||
| } from "class-validator"; | ||||
| import { Donor } from '../entities/Donor'; | ||||
| import { ResponseParticipant } from './ResponseParticipant'; | ||||
|  | ||||
| /** | ||||
|  * Defines the donor response. | ||||
| */ | ||||
| export class ResponseDonor extends ResponseParticipant { | ||||
|  | ||||
|     /** | ||||
|      * Does this donor need a receipt? | ||||
|      */ | ||||
|     @IsBoolean() | ||||
|     receiptNeeded: boolean; | ||||
|  | ||||
|     /** | ||||
|      * Creates a ResponseRunner object from a runner. | ||||
|      * @param runner The user the response shall be build for. | ||||
|      */ | ||||
|     public constructor(donor: Donor) { | ||||
|         super(donor); | ||||
|         this.receiptNeeded = donor.receiptNeeded; | ||||
|     } | ||||
| } | ||||
| @@ -29,7 +29,8 @@ export class ResponseRunner extends ResponseParticipant { | ||||
|      */ | ||||
|     public constructor(runner: Runner) { | ||||
|         super(runner); | ||||
|         this.distance = runner.scans.filter(scan => { scan.valid === true }).reduce((sum, current) => sum + current.distance, 0); | ||||
|         if (!runner.scans) { this.distance = 0 } | ||||
|         else { this.distance = runner.validScans.reduce((sum, current) => sum + current.distance, 0); } | ||||
|         this.group = runner.group; | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										53
									
								
								src/models/responses/ResponseRunnerCard.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/models/responses/ResponseRunnerCard.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| import { IsBoolean, IsEAN, IsInt, IsNotEmpty, IsObject, IsString } from "class-validator"; | ||||
| import { RunnerCard } from '../entities/RunnerCard'; | ||||
| import { ResponseRunner } from './ResponseRunner'; | ||||
|  | ||||
| /** | ||||
|  * Defines the runner card response. | ||||
| */ | ||||
| export class ResponseRunnerCard { | ||||
|     /** | ||||
|      * The card's id. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     id: number;; | ||||
|  | ||||
|     /** | ||||
|      * The card's associated runner. | ||||
|      * This is important to link scans to runners. | ||||
|      */ | ||||
|     @IsObject() | ||||
|     runner: ResponseRunner | null; | ||||
|  | ||||
|     /** | ||||
|      * The card's code. | ||||
|      */ | ||||
|     @IsEAN() | ||||
|     @IsString() | ||||
|     @IsNotEmpty() | ||||
|     code: string; | ||||
|  | ||||
|     /** | ||||
|      * Is the enabled valid (for fraud reasons). | ||||
|      * The determination of validity will work differently for every child class. | ||||
|      */ | ||||
|     @IsBoolean() | ||||
|     enabled: boolean = true; | ||||
|  | ||||
|     /** | ||||
|      * Creates a ResponseRunnerCard object from a runner card. | ||||
|      * @param card The card the response shall be build for. | ||||
|      */ | ||||
|     public constructor(card: RunnerCard) { | ||||
|         this.id = card.id; | ||||
|         if (!card.runner) { this.runner = null } | ||||
|         else { this.runner = card.runner.toResponse(); } | ||||
|         try { | ||||
|             this.code = card.code; | ||||
|         } catch (error) { | ||||
|             this.code = "0000000000000" | ||||
|         } | ||||
|  | ||||
|         this.enabled = card.enabled; | ||||
|     } | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user