Updated the put methods and cleaned up a shitload of comments #42
| @@ -96,7 +96,7 @@ export class PermissionController { | ||||
|             return new ResponsePermission(existingPermission); | ||||
|         } | ||||
|  | ||||
|         await this.permissionRepository.update(oldPermission, await permission.toPermission()); | ||||
|         await this.permissionRepository.save(await permission.updatePermission(oldPermission)); | ||||
|  | ||||
|         return new ResponsePermission(await this.permissionRepository.findOne({ id: permission.id }, { relations: ['principal'] })); | ||||
|     } | ||||
|   | ||||
| @@ -81,7 +81,7 @@ export class RunnerController { | ||||
| 			throw new RunnerIdsNotMatchingError(); | ||||
| 		} | ||||
|  | ||||
| 		await this.runnerRepository.update(oldRunner, await runner.toRunner()); | ||||
| 		await this.runnerRepository.save(await runner.updateRunner(oldRunner)); | ||||
| 		return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] })); | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| 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 { RunnerOrganisationHasRunnersError, RunnerOrganisationHasTeamsError, RunnerOrganisationIdsNotMatchingError, RunnerOrganisationNotFoundError } from '../errors/RunnerOrganisationErrors'; | ||||
| import { CreateRunnerOrganisation } from '../models/actions/CreateRunnerOrganisation'; | ||||
| import { UpdateRunnerOrganisation } from '../models/actions/UpdateRunnerOrganisation'; | ||||
| import { RunnerOrganisation } from '../models/entities/RunnerOrganisation'; | ||||
| import { ResponseEmpty } from '../models/responses/ResponseEmpty'; | ||||
| import { ResponseRunnerOrganisation } from '../models/responses/ResponseRunnerOrganisation'; | ||||
| @@ -71,21 +71,20 @@ export class RunnerOrganisationController { | ||||
| 	@ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 }) | ||||
| 	@ResponseSchema(RunnerOrganisationIdsNotMatchingError, { statusCode: 406 }) | ||||
| 	@OpenAPI({ description: "Update a runnerOrganisation object (id can't be changed)." }) | ||||
| 	async put(@Param('id') id: number, @EntityFromBody() runnerOrganisation: RunnerOrganisation) { | ||||
| 	async put(@Param('id') id: number, @Body({ validate: true }) updateOrganisation: UpdateRunnerOrganisation) { | ||||
| 		let oldRunnerOrganisation = await this.runnerOrganisationRepository.findOne({ id: id }); | ||||
|  | ||||
| 		if (!oldRunnerOrganisation) { | ||||
| 			throw new RunnerOrganisationNotFoundError(); | ||||
| 		} | ||||
|  | ||||
| 		if (oldRunnerOrganisation.id != runnerOrganisation.id) { | ||||
| 		if (oldRunnerOrganisation.id != updateOrganisation.id) { | ||||
| 			throw new RunnerOrganisationIdsNotMatchingError(); | ||||
| 		} | ||||
|  | ||||
| 		await this.runnerOrganisationRepository.update(oldRunnerOrganisation, runnerOrganisation); | ||||
| 		await this.runnerOrganisationRepository.save(await updateOrganisation.updateRunnerOrganisation(oldRunnerOrganisation)); | ||||
|  | ||||
| 		runnerOrganisation = await this.runnerOrganisationRepository.findOne(runnerOrganisation, { relations: ['address', 'contact', 'teams'] }); | ||||
| 		return new ResponseRunnerOrganisation(runnerOrganisation); | ||||
| 		return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(id, { relations: ['address', 'contact', 'teams'] })); | ||||
| 	} | ||||
|  | ||||
| 	@Delete('/:id') | ||||
|   | ||||
| @@ -82,7 +82,7 @@ export class RunnerTeamController { | ||||
| 			throw new RunnerTeamIdsNotMatchingError(); | ||||
| 		} | ||||
|  | ||||
| 		await this.runnerTeamRepository.update(oldRunnerTeam, await runnerTeam.toRunnerTeam()); | ||||
| 		await this.runnerTeamRepository.save(await runnerTeam.updateRunnerTeam(oldRunnerTeam)); | ||||
|  | ||||
| 		return new ResponseRunnerTeam(await this.runnerTeamRepository.findOne({ id: runnerTeam.id }, { relations: ['parentGroup', 'contact'] })); | ||||
| 	} | ||||
|   | ||||
| @@ -72,7 +72,7 @@ export class TrackController { | ||||
| 			throw new TrackIdsNotMatchingError(); | ||||
| 		} | ||||
|  | ||||
| 		await this.trackRepository.update(oldTrack, track); | ||||
| 		await this.trackRepository.save(track); | ||||
| 		return new ResponseTrack(track); | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -73,9 +73,6 @@ export class UserController { | ||||
| 	@OpenAPI({ description: "Update a user object (id can't be changed)." }) | ||||
| 	async put(@Param('id') id: number, @Body({ validate: true }) updateUser: UpdateUser) { | ||||
| 		let oldUser = await this.userRepository.findOne({ id: id }); | ||||
| 		delete oldUser.permissions; | ||||
| 		delete oldUser.groups; | ||||
| 		delete oldUser.actions; | ||||
|  | ||||
| 		if (!oldUser) { | ||||
| 			throw new UserNotFoundError(); | ||||
| @@ -84,7 +81,7 @@ export class UserController { | ||||
| 		if (oldUser.id != updateUser.id) { | ||||
| 			throw new UserIdsNotMatchingError(); | ||||
| 		} | ||||
| 		await this.userRepository.save(await updateUser.toUser(oldUser)); | ||||
| 		await this.userRepository.save(await updateUser.updateUser(oldUser)); | ||||
|  | ||||
| 		return new ResponseUser(await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] })); | ||||
| 	} | ||||
|   | ||||
| @@ -73,7 +73,7 @@ export class UserGroupController { | ||||
| 			throw new UserGroupIdsNotMatchingError(); | ||||
| 		} | ||||
|  | ||||
| 		await this.userGroupsRepository.update(oldUserGroup, userGroup); | ||||
| 		await this.userGroupsRepository.save(userGroup); | ||||
| 		return userGroup; | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -1,63 +1,57 @@ | ||||
| import { IsString } from 'class-validator'; | ||||
| import { ForbiddenError, NotAcceptableError, NotFoundError, UnauthorizedError } from 'routing-controllers'; | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a jwt is expired. | ||||
|  */ | ||||
| export class ExpiredJWTError extends UnauthorizedError { | ||||
| 	@IsString() | ||||
| 	name = "ExpiredJWTError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "your provided jwt is expired" | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a jwt could not be parsed. | ||||
|  * For example: Wrong signature or expired. | ||||
|  */ | ||||
| export class IllegalJWTError extends UnauthorizedError { | ||||
| 	@IsString() | ||||
| 	name = "IllegalJWTError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "your provided jwt could not be parsed" | ||||
| 	message = "Your provided jwt could not be parsed." | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when user is nonexistant or refreshtoken is invalid. | ||||
|  * This can happen if someone provides a JWT with a invalid user id or the refreshTokenCount of the user is higher that the provided jwt's is. | ||||
|  */ | ||||
| export class UserNonexistantOrRefreshtokenInvalidError extends UnauthorizedError { | ||||
| 	@IsString() | ||||
| 	name = "UserNonexistantOrRefreshtokenInvalidError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "user is nonexistant or refreshtoken is invalid" | ||||
| 	message = "User is nonexistant or refreshtoken is invalid." | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when provided credentials are invalid. | ||||
|  * We don't have seperate errors for username/mail and passwords to protect against guessing attacks. | ||||
|  */ | ||||
| export class InvalidCredentialsError extends UnauthorizedError { | ||||
| 	@IsString() | ||||
| 	name = "InvalidCredentialsError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "your provided credentials are invalid" | ||||
| 	message = "Your provided credentials are invalid." | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a jwt does not have permission for this route/action. | ||||
|  * Mainly used be the @Authorized decorator (via the authchecker). | ||||
|  */ | ||||
| export class NoPermissionError extends ForbiddenError { | ||||
| 	@IsString() | ||||
| 	name = "NoPermissionError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "your provided jwt does not have permission for this route/ action" | ||||
| 	message = "Your provided jwt does not have permission for this route/ action." | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when no username and no email is set. | ||||
|  * Because we have to identify users somehow. | ||||
|  */ | ||||
| export class UsernameOrEmailNeededError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| @@ -68,47 +62,48 @@ export class UsernameOrEmailNeededError extends NotAcceptableError { | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when no password is provided. | ||||
|  * Error to throw when no password is provided for a new user. | ||||
|  * Passwords are the minimum we need for user security. | ||||
|  */ | ||||
| export class PasswordNeededError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "PasswordNeededError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "no password is provided - you need to provide it" | ||||
| 	message = "No password is provided - you need to provide it." | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when no user could be found mating the provided credential. | ||||
|  * Error to throw when no user could be found for a certain query. | ||||
|  */ | ||||
| export class UserNotFoundError extends NotFoundError { | ||||
| 	@IsString() | ||||
| 	name = "UserNotFoundError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "no user could be found for provided credential" | ||||
| 	message = "The user you provided couldn't be located in the system. \n Please check your request." | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when no jwt token was provided (but one had to be). | ||||
|  * Error to throw when no jwt was provided (but one had to be). | ||||
|  */ | ||||
| export class JwtNotProvidedError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "JwtNotProvidedError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "no jwt token was provided" | ||||
| 	message = "No jwt was provided." | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when user was not found or refresh token count was invalid. | ||||
|  * Error to throw when user was not found or the jwt's refresh token count was invalid. | ||||
|  */ | ||||
| export class UserNotFoundOrRefreshTokenCountInvalidError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "UserNotFoundOrRefreshTokenCountInvalidError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "user was not found or refresh token count was invalid" | ||||
| 	message = "User was not found or the refresh token count is invalid." | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -119,5 +114,5 @@ export class RefreshTokenCountInvalidError extends NotAcceptableError { | ||||
| 	name = "RefreshTokenCountInvalidError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "refresh token count was invalid" | ||||
| 	message = "Refresh token count is invalid." | ||||
| } | ||||
| @@ -13,7 +13,7 @@ export class PermissionNotFoundError extends NotFoundError { | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when two permission' ids don't match. | ||||
|  * Error to throw when two permissions' ids don't match. | ||||
|  * Usually occurs when a user tries to change a permission's id. | ||||
|  */ | ||||
| export class PermissionIdsNotMatchingError extends NotAcceptableError { | ||||
| @@ -21,11 +21,11 @@ export class PermissionIdsNotMatchingError extends NotAcceptableError { | ||||
| 	name = "PermissionIdsNotMatchingError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The id's don't match!! \n And if you wanted to change a permission's id: This isn't allowed" | ||||
| 	message = "The ids don't match! \n And if you wanted to change a permission's id: This isn't allowed!" | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a permission get's provided without a principal. | ||||
|  * Error to throw when a permission gets provided without a principal. | ||||
|  */ | ||||
| export class PermissionNeedsPrincipalError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
|   | ||||
| @@ -3,7 +3,6 @@ import { NotAcceptableError, NotFoundError } from 'routing-controllers'; | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a runner couldn't be found. | ||||
|  * Implemented this ways to work with the json-schema conversion for openapi. | ||||
|  */ | ||||
| export class RunnerNotFoundError extends NotFoundError { | ||||
| 	@IsString() | ||||
| @@ -16,14 +15,13 @@ export class RunnerNotFoundError extends NotFoundError { | ||||
| /** | ||||
|  * Error to throw when two runners' ids don't match. | ||||
|  * Usually occurs when a user tries to change a runner's id. | ||||
|  * Implemented this ways to work with the json-schema conversion for openapi. | ||||
|  */ | ||||
| export class RunnerIdsNotMatchingError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "RunnerIdsNotMatchingError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The id's don't match!! \n And if you wanted to change a runner's id: This isn't allowed" | ||||
| 	message = "The ids don't match! \n And if you wanted to change a runner's id: This isn't allowed!" | ||||
| } | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -3,7 +3,6 @@ import { NotFoundError } from 'routing-controllers'; | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a runner group couldn't be found. | ||||
|  * Implemented this ways to work with the json-schema conversion for openapi. | ||||
|  */ | ||||
| export class RunnerGroupNotFoundError extends NotFoundError { | ||||
| 	@IsString() | ||||
|   | ||||
| @@ -3,7 +3,6 @@ import { NotAcceptableError, NotFoundError } from 'routing-controllers'; | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a runner organisation couldn't be found. | ||||
|  * Implemented this ways to work with the json-schema conversion for openapi. | ||||
|  */ | ||||
| export class RunnerOrganisationNotFoundError extends NotFoundError { | ||||
| 	@IsString() | ||||
| @@ -15,39 +14,36 @@ export class RunnerOrganisationNotFoundError extends NotFoundError { | ||||
|  | ||||
| /** | ||||
|  * Error to throw when two runner organisations' ids don't match. | ||||
|  * Usually occurs when a user tries to change a runner's id. | ||||
|  * Implemented this way to work with the json-schema conversion for openapi. | ||||
|  * Usually occurs when a user tries to change a runner organisation's id. | ||||
|  */ | ||||
| export class RunnerOrganisationIdsNotMatchingError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "RunnerOrganisationIdsNotMatchingError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The id's don't match!! \n And if you wanted to change a runner's id: This isn't allowed" | ||||
| 	message = "The ids don't match! \n And if you wanted to change a runner organisation's id: This isn't allowed!" | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a organisation still has runners associated. | ||||
|  * Implemented this waysto work with the json-schema conversion for openapi. | ||||
|  */ | ||||
| export class RunnerOrganisationHasRunnersError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "RunnerOrganisationHasRunnersError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "This organisation still has runners associated with it. \n If you want to delete this organisation with all it's runners and teams ass `?force` to your query." | ||||
| 	message = "This organisation still has runners associated with it. \n If you want to delete this organisation with all it's runners and teams add `?force` to your query." | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a organisation still has runners associated. | ||||
|  * Implemented this waysto work with the json-schema conversion for openapi. | ||||
|  * Error to throw when a organisation still has teams associated. | ||||
|  */ | ||||
| export class RunnerOrganisationHasTeamsError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "RunnerOrganisationHasTeamsError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "This organisation still has teams associated with it. \n If you want to delete this organisation with all it's runners and teams ass `?force` to your query." | ||||
| 	message = "This organisation still has teams associated with it. \n If you want to delete this organisation with all it's runners and teams add `?force` to your query." | ||||
| } | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -3,7 +3,6 @@ import { NotAcceptableError, NotFoundError } from 'routing-controllers'; | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a runner team couldn't be found. | ||||
|  * Implemented this ways to work with the json-schema conversion for openapi. | ||||
|  */ | ||||
| export class RunnerTeamNotFoundError extends NotFoundError { | ||||
| 	@IsString() | ||||
| @@ -15,32 +14,29 @@ export class RunnerTeamNotFoundError extends NotFoundError { | ||||
|  | ||||
| /** | ||||
|  * Error to throw when two runner teams' ids don't match. | ||||
|  * Usually occurs when a user tries to change a runner's id. | ||||
|  * Implemented this way to work with the json-schema conversion for openapi. | ||||
|  * Usually occurs when a user tries to change a runner team's id. | ||||
|  */ | ||||
| export class RunnerTeamIdsNotMatchingError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "RunnerTeamIdsNotMatchingError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The id's don't match!! \n And if you wanted to change a runner's id: This isn't allowed" | ||||
| 	message = "The ids don't match! \n And if you wanted to change a runner's id: This isn't allowed!" | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a team still has runners associated. | ||||
|  * Implemented this waysto work with the json-schema conversion for openapi. | ||||
|  */ | ||||
| export class RunnerTeamHasRunnersError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "RunnerTeamHasRunnersError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "This team still has runners associated with it. \n If you want to delete this team with all it's runners and teams ass `?force` to your query." | ||||
| 	message = "This team still has runners associated with it. \n If you want to delete this team with all it's runners and teams add `?force` to your query." | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a team still has runners associated. | ||||
|  * Implemented this waysto work with the json-schema conversion for openapi. | ||||
|  */ | ||||
| export class RunnerTeamNeedsParentError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
|   | ||||
| @@ -1,9 +1,8 @@ | ||||
| import { JsonController, Param, Body, Get, Post, Put, Delete, NotFoundError, OnUndefined, NotAcceptableError } from 'routing-controllers'; | ||||
| import { IsInt, IsNotEmpty, IsPositive, IsString } from 'class-validator'; | ||||
| import { IsString } from 'class-validator'; | ||||
| import { NotAcceptableError, NotFoundError } from 'routing-controllers'; | ||||
|  | ||||
| /** | ||||
|  * Error to throw when a track couldn't be found. | ||||
|  * Implemented this ways to work with the json-schema conversion for openapi. | ||||
|  */ | ||||
| export class TrackNotFoundError extends NotFoundError { | ||||
| 	@IsString() | ||||
| @@ -16,12 +15,11 @@ export class TrackNotFoundError extends NotFoundError { | ||||
| /** | ||||
|  * Error to throw when two tracks' ids don't match. | ||||
|  * Usually occurs when a user tries to change a track's id. | ||||
|  * Implemented this ways to work with the json-schema conversion for openapi. | ||||
|  */ | ||||
| export class TrackIdsNotMatchingError extends NotAcceptableError { | ||||
| 	@IsString() | ||||
| 	name = "TrackIdsNotMatchingError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The id's don't match!! \n And if you wanted to change a track's id: This isn't allowed" | ||||
| 	message = "The ids don't match! \n And if you wanted to change a track's id: This isn't allowed" | ||||
| } | ||||
| @@ -3,14 +3,15 @@ import { NotAcceptableError, NotFoundError } from 'routing-controllers'; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Error to throw when no username or email is set | ||||
|  * Error to throw when no username or email is set. | ||||
|  * We somehow need to identify you :) | ||||
|  */ | ||||
| export class UsernameOrEmailNeededError extends NotFoundError { | ||||
| 	@IsString() | ||||
| 	name = "UsernameOrEmailNeededError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "no username or email is set!" | ||||
| 	message = "No username or email is set!" | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -33,5 +34,5 @@ export class UserIdsNotMatchingError extends NotAcceptableError { | ||||
| 	name = "UserIdsNotMatchingError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The id's don't match!! \n And if you wanted to change a user's id: This isn't allowed" | ||||
| 	message = "The ids don't match!! \n And if you wanted to change a user's id: This isn't allowed!" | ||||
| } | ||||
| @@ -2,14 +2,14 @@ import { IsString } from 'class-validator'; | ||||
| import { NotAcceptableError, NotFoundError } from 'routing-controllers'; | ||||
|  | ||||
| /** | ||||
|  * Error to throw when no groupname is set | ||||
|  * Error to throw when no groupname is set. | ||||
|  */ | ||||
| export class GroupNameNeededError extends NotFoundError { | ||||
| 	@IsString() | ||||
| 	name = "GroupNameNeededError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "no groupname is set!" | ||||
| 	message = "No name is set for this group!" | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -32,5 +32,5 @@ export class UserGroupIdsNotMatchingError extends NotAcceptableError { | ||||
| 	name = "UserGroupIdsNotMatchingError" | ||||
|  | ||||
| 	@IsString() | ||||
| 	message = "The id's 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 usergroup's id: This isn't allowed!" | ||||
| } | ||||
| @@ -4,6 +4,7 @@ import { User } from '../models/entities/User'; | ||||
| import SeedUsers from '../seeds/SeedUsers'; | ||||
| /** | ||||
|  * Loader for the database that creates the database connection and initializes the database tabels. | ||||
|  * It also triggers the seeding process if no users got detected in the database. | ||||
|  */ | ||||
| export default async () => { | ||||
|     const connection = await createConnection(); | ||||
|   | ||||
| @@ -2,7 +2,7 @@ import cookieParser from "cookie-parser"; | ||||
| import { Application } from "express"; | ||||
| /** | ||||
|  * Loader for express related configurations. | ||||
|  * Currently only enables the proxy trust. | ||||
|  * Configures proxy trusts, globally used middlewares and other express features. | ||||
|  */ | ||||
| export default async (app: Application) => { | ||||
| 	app.enable('trust proxy'); | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import openapiLoader from "./openapi"; | ||||
|  | ||||
| /** | ||||
|  * Index Loader that executes the other loaders in the right order. | ||||
|  * This basicly exists for abstraction and a overall better dev experience. | ||||
|  */ | ||||
| export default async (app: Application) => { | ||||
|     await databaseLoader(); | ||||
|   | ||||
| @@ -5,7 +5,8 @@ import { routingControllersToSpec } from "routing-controllers-openapi"; | ||||
| import * as swaggerUiExpress from "swagger-ui-express"; | ||||
|  | ||||
| /** | ||||
|  * Loader for everything openapi related - from creating the schema to serving it via a static route. | ||||
|  * Loader for everything openapi related - from creating the schema to serving it via a static route and swaggerUiExpress. | ||||
|  * All auth schema related stuff also has to be configured here | ||||
|  */ | ||||
| export default async (app: Application) => { | ||||
|   const storage = getMetadataArgsStorage(); | ||||
| @@ -26,7 +27,8 @@ export default async (app: Application) => { | ||||
|           "AuthToken": { | ||||
|             "type": "http", | ||||
|             "scheme": "bearer", | ||||
|             "bearerFormat": "JWT" | ||||
|             "bearerFormat": "JWT", | ||||
|             description: "A JWT based access token. Use /api/auth/login or /api/auth/refresh to get one." | ||||
|           } | ||||
|         } | ||||
|       }, | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { ExpressErrorMiddlewareInterface, Middleware } from "routing-controllers"; | ||||
|  | ||||
| /** | ||||
|  * Our Error handling middlware that returns our custom httperrors to the user | ||||
|  * Our Error handling middlware that returns our custom httperrors to the user. | ||||
|  */ | ||||
| @Middleware({ type: "after" }) | ||||
| export class ErrorHandler implements ExpressErrorMiddlewareInterface { | ||||
|   | ||||
| @@ -1,5 +1,10 @@ | ||||
| import { Request, Response } from 'express'; | ||||
|  | ||||
| /** | ||||
|  * Custom express middleware that appends the raw body to the request obeject. | ||||
|  * Mainly used for parsing csvs from boddies. | ||||
|  */ | ||||
|  | ||||
| const RawBodyMiddleware = (req: Request, res: Response, next: () => void) => { | ||||
|     const body = [] | ||||
|     req.on('data', chunk => { | ||||
| @@ -8,15 +13,6 @@ const RawBodyMiddleware = (req: Request, res: Response, next: () => void) => { | ||||
|     req.on('end', () => { | ||||
|         const rawBody = Buffer.concat(body) | ||||
|         req['rawBody'] = rawBody | ||||
|         /* | ||||
|         switch (req.header('content-type')) { | ||||
|             case 'application/json': | ||||
|                 req.body = JSON.parse(rawBody.toString()) | ||||
|                 break | ||||
|             // add more body parsing if needs be | ||||
|             default: | ||||
|         } | ||||
|         */ | ||||
|         next() | ||||
|     }) | ||||
|     req.on('error', () => { | ||||
|   | ||||
| @@ -1,16 +1,19 @@ | ||||
| import { IsNotEmpty, IsOptional, IsPostalCode, IsString } from 'class-validator'; | ||||
| import { Address } from '../entities/Address'; | ||||
|  | ||||
| /** | ||||
|  * This classed is used to create a new Address entity from a json body (post request). | ||||
|  */ | ||||
| export class CreateAddress { | ||||
|     /** | ||||
|    * The address's description. | ||||
|    */ | ||||
|      * The newaddress's description. | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsOptional() | ||||
|     description?: string; | ||||
|  | ||||
|     /** | ||||
|      * The address's first line. | ||||
|      * The new address's first line. | ||||
|      * Containing the street and house number. | ||||
|      */ | ||||
|     @IsString() | ||||
| @@ -18,7 +21,7 @@ export class CreateAddress { | ||||
|     address1: string; | ||||
|  | ||||
|     /** | ||||
|      * The address's second line. | ||||
|      * The new address's second line. | ||||
|      * Containing optional information. | ||||
|      */ | ||||
|     @IsString() | ||||
| @@ -26,7 +29,9 @@ export class CreateAddress { | ||||
|     address2?: string; | ||||
|  | ||||
|     /** | ||||
|      * The address's postal code. | ||||
|      * The new address's postal code. | ||||
|      * This will get checked against the postal code syntax for the configured country. | ||||
|      * TODO: Implement the config option.  | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsNotEmpty() | ||||
| @@ -34,21 +39,21 @@ export class CreateAddress { | ||||
|     postalcode: string; | ||||
|  | ||||
|     /** | ||||
|      * The address's city. | ||||
|      * The new address's city. | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsNotEmpty() | ||||
|     city: string; | ||||
|  | ||||
|     /** | ||||
|      * The address's country. | ||||
|      * The new address's country. | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsNotEmpty() | ||||
|     country: string; | ||||
|  | ||||
|     /** | ||||
|      * Creates a Address object based on this. | ||||
|      * Creates a new Address entity from this. | ||||
|      */ | ||||
|     public toAddress(): Address { | ||||
|         let newAddress: Address = new Address(); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import * as argon2 from "argon2"; | ||||
| import { IsEmail, IsOptional, IsString } from 'class-validator'; | ||||
| import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { InvalidCredentialsError, PasswordNeededError, UserNotFoundError } from '../../errors/AuthError'; | ||||
| import { UsernameOrEmailNeededError } from '../../errors/UserErrors'; | ||||
| @@ -7,17 +7,41 @@ import { JwtCreator } from '../../jwtcreator'; | ||||
| import { User } from '../entities/User'; | ||||
| import { Auth } from '../responses/ResponseAuth'; | ||||
|  | ||||
| /** | ||||
|  * This class is used to create auth credentials based on user credentials provided in a json body (post request). | ||||
|  * To be a little bit more exact: Is takes in a username/email + password and creates a new access and refresh token for the user. | ||||
|  * It of course checks for user existance, password validity and so on. | ||||
|  */ | ||||
| export class CreateAuth { | ||||
|     /** | ||||
|      * The username of the user that want's to login. | ||||
|      * Either username or email have to be provided. | ||||
|      */ | ||||
|     @IsOptional() | ||||
|     @IsString() | ||||
|     username?: string; | ||||
|     @IsString() | ||||
|     password: string; | ||||
|  | ||||
|     /** | ||||
|      * The email address of the user that want's to login. | ||||
|      * Either username or email have to be provided. | ||||
|      */ | ||||
|     @IsOptional() | ||||
|     @IsEmail() | ||||
|     @IsString() | ||||
|     email?: string; | ||||
|  | ||||
|     /** | ||||
|      * The user's password. | ||||
|      * Will be checked against an argon2 hash. | ||||
|      */ | ||||
|     @IsNotEmpty() | ||||
|     @IsString() | ||||
|     password: string; | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Creates a new auth object based on this. | ||||
|      */ | ||||
|     public async toAuth(): Promise<Auth> { | ||||
|         let newAuth: Auth = new Auth(); | ||||
|  | ||||
|   | ||||
| @@ -5,32 +5,34 @@ import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/Addres | ||||
| 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). | ||||
|  */ | ||||
| export class CreateGroupContact { | ||||
|     /** | ||||
|        * The contact's first name. | ||||
|        */ | ||||
|      * The new contact's first name. | ||||
|      */ | ||||
|     @IsNotEmpty() | ||||
|     @IsString() | ||||
|     firstname: string; | ||||
|  | ||||
|     /** | ||||
|      * The contact's middle name. | ||||
|      * Optional | ||||
|      * The new contact's middle name. | ||||
|      */ | ||||
|     @IsOptional() | ||||
|     @IsString() | ||||
|     middlename?: string; | ||||
|  | ||||
|     /** | ||||
|      * The contact's last name. | ||||
|      * The new contact's last name. | ||||
|      */ | ||||
|     @IsNotEmpty() | ||||
|     @IsString() | ||||
|     lastname: string; | ||||
|  | ||||
|     /** | ||||
|      * The contact's address. | ||||
|      * Optional | ||||
|      * The new contact's address. | ||||
|      * Must be the address's id. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsOptional() | ||||
| @@ -38,7 +40,7 @@ export class CreateGroupContact { | ||||
|  | ||||
|     /** | ||||
|      * The contact's phone number. | ||||
|      * Optional | ||||
|      * This will be validated against the configured country phone numer syntax (default: international). | ||||
|      */ | ||||
|     @IsOptional() | ||||
|     @IsPhoneNumber(config.phone_validation_countrycode) | ||||
| @@ -46,17 +48,16 @@ export class CreateGroupContact { | ||||
|  | ||||
|     /** | ||||
|      * The contact's email address. | ||||
|      * Optional | ||||
|      */ | ||||
|     @IsOptional() | ||||
|     @IsEmail() | ||||
|     email?: string; | ||||
|  | ||||
|     /** | ||||
|      * Get's this participant's address from this.address. | ||||
|      * Gets the new contact's address by it's id. | ||||
|      */ | ||||
|     public async getAddress(): Promise<Address> { | ||||
|         if (this.address === undefined) { | ||||
|         if (this.address === undefined || this.address === null) { | ||||
|             return null; | ||||
|         } | ||||
|         if (!isNaN(this.address)) { | ||||
| @@ -69,7 +70,7 @@ export class CreateGroupContact { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a Address object based on this. | ||||
|      * Creates a new Address entity from this. | ||||
|      */ | ||||
|     public async toGroupContact(): Promise<GroupContact> { | ||||
|         let contact: GroupContact = new GroupContact(); | ||||
|   | ||||
| @@ -4,6 +4,9 @@ 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). | ||||
|  */ | ||||
| export abstract class CreateParticipant { | ||||
|     /** | ||||
|      * The new participant's first name. | ||||
| @@ -14,7 +17,6 @@ export abstract class CreateParticipant { | ||||
|  | ||||
|     /** | ||||
|      * The new participant's middle name. | ||||
|      * Optional. | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsOptional() | ||||
| @@ -29,7 +31,7 @@ export abstract class CreateParticipant { | ||||
|  | ||||
|     /** | ||||
|      * The new participant's phone number. | ||||
|      * Optional. | ||||
|      * This will be validated against the configured country phone numer syntax (default: international). | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsOptional() | ||||
| @@ -38,7 +40,6 @@ export abstract class CreateParticipant { | ||||
|  | ||||
|     /** | ||||
|      * The new participant's e-mail address. | ||||
|      * Optional. | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsOptional() | ||||
| @@ -48,17 +49,16 @@ export abstract class CreateParticipant { | ||||
|     /** | ||||
|      * The new participant's address. | ||||
|      * Must be of type number (address id). | ||||
|      * Optional. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsOptional() | ||||
|     address?: number; | ||||
|  | ||||
|     /** | ||||
|      * Get's this participant's address from this.address. | ||||
|      * Gets the new participant's address by it's address. | ||||
|      */ | ||||
|     public async getAddress(): Promise<Address> { | ||||
|         if (this.address === undefined) { | ||||
|         if (this.address === undefined || this.address === null) { | ||||
|             return null; | ||||
|         } | ||||
|         if (!isNaN(this.address)) { | ||||
|   | ||||
| @@ -11,33 +11,33 @@ import { PermissionAction } from '../enums/PermissionAction'; | ||||
| import { PermissionTarget } from '../enums/PermissionTargets'; | ||||
|  | ||||
| /** | ||||
|  * Defines a track of given length. | ||||
| */ | ||||
|  * This classed is used to create a new Permission entity from a json body (post request). | ||||
|  */ | ||||
| export class CreatePermission { | ||||
|  | ||||
|     /** | ||||
|      * The permissions's principal's id. | ||||
|      * The new permissions's principal's id. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsNotEmpty() | ||||
|     principal: number; | ||||
|  | ||||
|     /** | ||||
|      * The permissions's target. | ||||
|      * The new permissions's target. | ||||
|      */ | ||||
|     @IsNotEmpty() | ||||
|     @IsEnum(PermissionTarget) | ||||
|     target: PermissionTarget; | ||||
|  | ||||
|     /** | ||||
|      * The permissions's action. | ||||
|      * The new permissions's action. | ||||
|      */ | ||||
|     @IsNotEmpty() | ||||
|     @IsEnum(PermissionAction) | ||||
|     action: PermissionAction; | ||||
|  | ||||
|     /** | ||||
|      * Converts a Permission object based on this. | ||||
|      * Creates a new Permission entity from this. | ||||
|      */ | ||||
|     public async toPermission(): Promise<Permission> { | ||||
|         let newPermission: Permission = new Permission(); | ||||
| @@ -49,6 +49,9 @@ export class CreatePermission { | ||||
|         return newPermission; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Gets the new permission's principal by it's id. | ||||
|      */ | ||||
|     public async getPrincipal(): Promise<Principal> { | ||||
|         let principal = await getConnectionManager().get().getRepository(Principal).findOne({ id: this.principal }) | ||||
|         if (!principal) { throw new PrincipalNotFoundError(); } | ||||
|   | ||||
| @@ -7,6 +7,9 @@ import { Runner } from '../entities/Runner'; | ||||
| import { RunnerGroup } from '../entities/RunnerGroup'; | ||||
| import { CreateParticipant } from './CreateParticipant'; | ||||
|  | ||||
| /** | ||||
|  * This classed is used to create a new Runner entity from a json body (post request). | ||||
|  */ | ||||
| export class CreateRunner extends CreateParticipant { | ||||
|  | ||||
|     /** | ||||
| @@ -16,7 +19,7 @@ export class CreateRunner extends CreateParticipant { | ||||
|     group: number; | ||||
|  | ||||
|     /** | ||||
|      * Creates a Runner entity from this. | ||||
|      * Creates a new Runner entity from this. | ||||
|      */ | ||||
|     public async toRunner(): Promise<Runner> { | ||||
|         let newRunner: Runner = new Runner(); | ||||
| @@ -33,10 +36,10 @@ export class CreateRunner extends CreateParticipant { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Manages all the different ways a group can be provided. | ||||
|      * Gets the new runner's group by it's id. | ||||
|      */ | ||||
|     public async getGroup(): Promise<RunnerGroup> { | ||||
|         if (this.group === undefined) { | ||||
|         if (this.group === undefined || this.group === null) { | ||||
|             throw new RunnerTeamNeedsParentError(); | ||||
|         } | ||||
|         if (!isNaN(this.group)) { | ||||
|   | ||||
| @@ -3,16 +3,19 @@ import { getConnectionManager } from 'typeorm'; | ||||
| 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). | ||||
|  */ | ||||
| export abstract class CreateRunnerGroup { | ||||
|     /** | ||||
|      * The group's name. | ||||
|      * The new group's name. | ||||
|      */ | ||||
|     @IsNotEmpty() | ||||
|     @IsString() | ||||
|     name: string; | ||||
|  | ||||
|     /** | ||||
|      * The group's contact. | ||||
|      * The new group's contact. | ||||
|      * Optional | ||||
|      */ | ||||
|     @IsInt() | ||||
| @@ -20,7 +23,7 @@ export abstract class CreateRunnerGroup { | ||||
|     contact?: number; | ||||
|  | ||||
|     /** | ||||
|      * Get's this group's contact from this.address. | ||||
|      * Gets the new group's contact by it's id. | ||||
|      */ | ||||
|     public async getContact(): Promise<GroupContact> { | ||||
|         if (this.contact === undefined || this.contact === null) { | ||||
|   | ||||
| @@ -5,21 +5,23 @@ import { Address } from '../entities/Address'; | ||||
| import { RunnerOrganisation } from '../entities/RunnerOrganisation'; | ||||
| import { CreateRunnerGroup } from './CreateRunnerGroup'; | ||||
|  | ||||
| /** | ||||
|  * This classed is used to create a new RunnerOrganisation entity from a json body (post request). | ||||
|  */ | ||||
| export class CreateRunnerOrganisation extends CreateRunnerGroup { | ||||
|     /** | ||||
|      * The new organisation's address. | ||||
|      * Must be of type number (address id). | ||||
|      * Optional. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsOptional() | ||||
|     address?: number; | ||||
|  | ||||
|     /** | ||||
|      * Get's this org's address from this.address. | ||||
|      * Gets the org's address by it's id. | ||||
|      */ | ||||
|     public async getAddress(): Promise<Address> { | ||||
|         if (this.address === undefined) { | ||||
|         if (this.address === undefined || this.address === null) { | ||||
|             return null; | ||||
|         } | ||||
|         if (!isNaN(this.address)) { | ||||
| @@ -32,7 +34,7 @@ export class CreateRunnerOrganisation extends CreateRunnerGroup { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a RunnerOrganisation entity from this. | ||||
|      * Creates a new RunnerOrganisation entity from this. | ||||
|      */ | ||||
|     public async toRunnerOrganisation(): Promise<RunnerOrganisation> { | ||||
|         let newRunnerOrganisation: RunnerOrganisation = new RunnerOrganisation(); | ||||
|   | ||||
| @@ -6,17 +6,23 @@ import { RunnerOrganisation } from '../entities/RunnerOrganisation'; | ||||
| import { RunnerTeam } from '../entities/RunnerTeam'; | ||||
| import { CreateRunnerGroup } from './CreateRunnerGroup'; | ||||
|  | ||||
| /** | ||||
|  * This classed is used to create a new RunnerTeam entity from a json body (post request). | ||||
|  */ | ||||
| export class CreateRunnerTeam extends CreateRunnerGroup { | ||||
|  | ||||
|     /** | ||||
|      * The team's parent group (organisation). | ||||
|      * The new team's parent group (organisation). | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsNotEmpty() | ||||
|     parentGroup: number; | ||||
|  | ||||
|     /** | ||||
|      * Gets the new team's parent org based on it's id. | ||||
|      */ | ||||
|     public async getParent(): Promise<RunnerOrganisation> { | ||||
|         if (this.parentGroup === undefined) { | ||||
|         if (this.parentGroup === undefined || this.parentGroup === null) { | ||||
|             throw new RunnerTeamNeedsParentError(); | ||||
|         } | ||||
|         if (!isNaN(this.parentGroup)) { | ||||
| @@ -29,7 +35,7 @@ export class CreateRunnerTeam extends CreateRunnerGroup { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a RunnerTeam entity from this. | ||||
|      * Creates a new RunnerTeam entity from this. | ||||
|      */ | ||||
|     public async toRunnerTeam(): Promise<RunnerTeam> { | ||||
|         let newRunnerTeam: RunnerTeam = new RunnerTeam(); | ||||
|   | ||||
| @@ -1,23 +1,26 @@ | ||||
| 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 track's name. | ||||
|      * The new track's name. | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsNotEmpty() | ||||
|     name: string; | ||||
|  | ||||
|     /** | ||||
|      * The track's distance in meters (must be greater than 0). | ||||
|      * The new track's distance in meters (must be greater than 0). | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsPositive() | ||||
|     distance: number; | ||||
|  | ||||
|     /** | ||||
|      * Converts a Track object based on this. | ||||
|      * Creates a new Track entity from this. | ||||
|      */ | ||||
|     public toTrack(): Track { | ||||
|         let newTrack: Track = new Track(); | ||||
|   | ||||
| @@ -8,6 +8,9 @@ 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). | ||||
|  */ | ||||
| export class CreateUser { | ||||
|     /** | ||||
|      * The new user's first name. | ||||
| @@ -17,7 +20,6 @@ export class CreateUser { | ||||
|  | ||||
|     /** | ||||
|      * The new user's middle name. | ||||
|      * Optinal. | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsOptional() | ||||
| @@ -48,7 +50,7 @@ export class CreateUser { | ||||
|  | ||||
|     /** | ||||
|      * The new user's phone number. | ||||
|      * Optional | ||||
|      * This will be validated against the configured country phone numer syntax (default: international). | ||||
|      */ | ||||
|     @IsPhoneNumber(config.phone_validation_countrycode) | ||||
|     @IsOptional() | ||||
| @@ -64,7 +66,6 @@ export class CreateUser { | ||||
|     /** | ||||
|      * The new user's groups' id(s). | ||||
|      * You can provide either one groupId or an array of groupIDs. | ||||
|      * Optional. | ||||
|      */ | ||||
|     @IsOptional() | ||||
|     groups?: number[] | number | ||||
| @@ -72,7 +73,7 @@ export class CreateUser { | ||||
|     //TODO: ProfilePics | ||||
|  | ||||
|     /** | ||||
|      * Converts this to a User Entity. | ||||
|      * Converts this to a User entity. | ||||
|      */ | ||||
|     public async toUser(): Promise<User> { | ||||
|         let newUser: User = new User(); | ||||
|   | ||||
| @@ -1,6 +1,9 @@ | ||||
| import { IsOptional, IsString } from 'class-validator'; | ||||
| import { UserGroup } from '../entities/UserGroup'; | ||||
|  | ||||
| /** | ||||
|  * This classed is used to create a new UserGroup entity from a json body (post request). | ||||
|  */ | ||||
| export class CreateUserGroup { | ||||
|     /** | ||||
|      * The new group's name. | ||||
| @@ -17,7 +20,7 @@ export class CreateUserGroup { | ||||
|     description?: string; | ||||
|  | ||||
|     /** | ||||
|      * Converts this to a UserGroup entity. | ||||
|      * Creates a new UserGroup entity from this. | ||||
|      */ | ||||
|     public async toUserGroup(): Promise<UserGroup> { | ||||
|         let newUserGroup: UserGroup = new UserGroup(); | ||||
|   | ||||
| @@ -6,11 +6,23 @@ import { IllegalJWTError, JwtNotProvidedError, RefreshTokenCountInvalidError, Us | ||||
| import { User } from '../entities/User'; | ||||
| import { Logout } from '../responses/ResponseLogout'; | ||||
|  | ||||
| /** | ||||
|  * This class handels a user logging out of the system. | ||||
|  * Of course it check's the user's provided credential (token) before logging him out. | ||||
|  */ | ||||
| export class HandleLogout { | ||||
|     /** | ||||
|      * A stringyfied jwt access token. | ||||
|      * Will get checked for validity. | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsOptional() | ||||
|     token?: string; | ||||
|  | ||||
|     /** | ||||
|      * Logs the user out. | ||||
|      * This gets achived by increasing the user's refresh token count, thereby invalidateing all currently existing jwts for that user. | ||||
|      */ | ||||
|     public async logout(): Promise<Logout> { | ||||
|         let logout: Logout = new Logout(); | ||||
|         if (!this.token || this.token === undefined) { | ||||
|   | ||||
| @@ -7,6 +7,10 @@ import { RunnerOrganisation } from '../entities/RunnerOrganisation'; | ||||
| import { RunnerTeam } from '../entities/RunnerTeam'; | ||||
| import { CreateRunner } from './CreateRunner'; | ||||
|  | ||||
| /** | ||||
|  * Special class used to import runners from csv files - or json arrays created from csv to be exact. | ||||
|  * Why you ask? Because the past has shown us that a non excel/csv based workflow is too much for most schools. | ||||
|  */ | ||||
| export class ImportRunner { | ||||
|  | ||||
|     /** | ||||
| @@ -18,7 +22,6 @@ export class ImportRunner { | ||||
|  | ||||
|     /** | ||||
|      * The new runner's middle name. | ||||
|      * Optional. | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsOptional() | ||||
| @@ -32,18 +35,26 @@ export class ImportRunner { | ||||
|     lastname: string; | ||||
|  | ||||
|     /** | ||||
|      * The new runner's class (if not provided otherwise). | ||||
|      * The new runner's team's name (if not provided otherwise). | ||||
|      * The team will automaticly get generated if it doesn't exist in this org yet. | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsOptional() | ||||
|     team?: string; | ||||
|  | ||||
|     /** | ||||
|      * Just an alias for team, because this is usually only used for importing data from schools. | ||||
|      */ | ||||
|     @IsOptional() | ||||
|     @IsString() | ||||
|     public set class(value: string) { | ||||
|         this.team = value; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a CreateRunner object based on this. | ||||
|      * @param groupID Either the id of the new runner's group or the id of the org that the new runner's team is a part of. | ||||
|      */ | ||||
|     public async toCreateRunner(groupID: number): Promise<CreateRunner> { | ||||
|         let newRunner: CreateRunner = new CreateRunner(); | ||||
|  | ||||
| @@ -55,6 +66,10 @@ export class ImportRunner { | ||||
|         return newRunner; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get's the new runners group. | ||||
|      * @param groupID Either the id of the new runner's group or the id of the org that the new runner's team is a part of. | ||||
|      */ | ||||
|     public async getGroup(groupID: number): Promise<RunnerGroup> { | ||||
|         if (this.team === undefined && groupID === undefined) { | ||||
|             throw new RunnerGroupNeededError(); | ||||
|   | ||||
| @@ -7,11 +7,23 @@ import { JwtCreator } from "../../jwtcreator"; | ||||
| import { User } from '../entities/User'; | ||||
| import { Auth } from '../responses/ResponseAuth'; | ||||
|  | ||||
| /** | ||||
|  * This class is used to create refreshed auth credentials. | ||||
|  * To be a little bit more exact: Is takes in a refresh token and creates a new access and refresh token for it's user. | ||||
|  * It of course checks for user existance, jwt validity and so on. | ||||
|  */ | ||||
| export class RefreshAuth { | ||||
|     /** | ||||
|      * A stringyfied jwt refresh token. | ||||
|      * Will get checked for validity. | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsOptional() | ||||
|     token?: string; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new auth object based on this. | ||||
|      */ | ||||
|     public async toAuth(): Promise<Auth> { | ||||
|         let newAuth: Auth = new Auth(); | ||||
|         if (!this.token || this.token === undefined) { | ||||
|   | ||||
| @@ -7,16 +7,21 @@ 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). | ||||
|  */ | ||||
| export class UpdatePermission { | ||||
|  | ||||
|     /** | ||||
|      * The updated runner's id. | ||||
|      * The updated permission'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 permissions's principal's id. | ||||
|      * The updated permissions's principal. | ||||
|      * Just has to contain the principal's id -everything else won't be checked or changed. | ||||
|      */ | ||||
|     @IsObject() | ||||
|     @IsNotEmpty() | ||||
| @@ -35,23 +40,21 @@ export class UpdatePermission { | ||||
|     action: PermissionAction; | ||||
|  | ||||
|     /** | ||||
|      * Converts a Permission object based on this. | ||||
|      * Updates a provided Permission entity based on this. | ||||
|      */ | ||||
|     public async toPermission(): Promise<Permission> { | ||||
|         let newPermission: Permission = new Permission(); | ||||
|     public async updatePermission(permission: Permission): Promise<Permission> { | ||||
|         permission.principal = await this.getPrincipal(); | ||||
|         permission.target = this.target; | ||||
|         permission.action = this.action; | ||||
|  | ||||
|         newPermission.principal = await this.getPrincipal(); | ||||
|         newPermission.target = this.target; | ||||
|         newPermission.action = this.action; | ||||
|  | ||||
|         return newPermission; | ||||
|         return permission; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Manages all the different ways a group can be provided. | ||||
|      * Loads the updated permission's principal based on it's id. | ||||
|      */ | ||||
|     public async getPrincipal(): Promise<Principal> { | ||||
|         if (this.principal === undefined) { | ||||
|         if (this.principal === undefined || this.principal === null) { | ||||
|             throw new PermissionNeedsPrincipalError(); | ||||
|         } | ||||
|         if (!isNaN(this.principal.id)) { | ||||
|   | ||||
| @@ -7,43 +7,45 @@ import { Runner } from '../entities/Runner'; | ||||
| import { RunnerGroup } from '../entities/RunnerGroup'; | ||||
| import { CreateParticipant } from './CreateParticipant'; | ||||
|  | ||||
| /** | ||||
|  * This class is used to update a Runner entity (via put request). | ||||
|  */ | ||||
| export class UpdateRunner extends CreateParticipant { | ||||
|  | ||||
|     /** | ||||
|      * The updated runner'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 runner's new team/org. | ||||
|      * Just has to contain the group's id -everything else won't be checked or changed. | ||||
|      */ | ||||
|     @IsObject() | ||||
|     group: RunnerGroup; | ||||
|  | ||||
|     /** | ||||
|      * Creates a Runner entity from this. | ||||
|      * Updates a provided Runner entity based on this. | ||||
|      */ | ||||
|     public async toRunner(): Promise<Runner> { | ||||
|         let newRunner: Runner = new Runner(); | ||||
|     public async updateRunner(runner: Runner): Promise<Runner> { | ||||
|         runner.firstname = this.firstname; | ||||
|         runner.middlename = this.middlename; | ||||
|         runner.lastname = this.lastname; | ||||
|         runner.phone = this.phone; | ||||
|         runner.email = this.email; | ||||
|         runner.group = await this.getGroup(); | ||||
|         runner.address = await this.getAddress(); | ||||
|  | ||||
|         newRunner.id = this.id; | ||||
|         newRunner.firstname = this.firstname; | ||||
|         newRunner.middlename = this.middlename; | ||||
|         newRunner.lastname = this.lastname; | ||||
|         newRunner.phone = this.phone; | ||||
|         newRunner.email = this.email; | ||||
|         newRunner.group = await this.getGroup(); | ||||
|         newRunner.address = await this.getAddress(); | ||||
|  | ||||
|         return newRunner; | ||||
|         return runner; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Manages all the different ways a group can be provided. | ||||
|      * Loads the updated runner's group based on it's id. | ||||
|      */ | ||||
|     public async getGroup(): Promise<RunnerGroup> { | ||||
|         if (this.group === undefined) { | ||||
|         if (this.group === undefined || this.group === null) { | ||||
|             throw new RunnerTeamNeedsParentError(); | ||||
|         } | ||||
|         if (!isNaN(this.group.id)) { | ||||
|   | ||||
							
								
								
									
										52
									
								
								src/models/actions/UpdateRunnerOrganisation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/models/actions/UpdateRunnerOrganisation.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| 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'; | ||||
|  | ||||
| /** | ||||
|  * This class is used to update a RunnerOrganisation entity (via put request). | ||||
|  */ | ||||
| export class UpdateRunnerOrganisation extends CreateRunnerGroup { | ||||
|  | ||||
|     /** | ||||
|      * The updated orgs's id. | ||||
|      * This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to). | ||||
|      */ | ||||
|     @IsInt() | ||||
|     id: number; | ||||
|  | ||||
|     /** | ||||
|      * The updated organisation's address. | ||||
|      * Just has to contain the address's id - everything else won't be checked or changed. | ||||
|      * Optional. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsOptional() | ||||
|     address?: Address; | ||||
|  | ||||
|     /** | ||||
|      * Loads the organisation's address based on it's id. | ||||
|      */ | ||||
|     public async getAddress(): Promise<Address> { | ||||
|         if (this.address === undefined || this.address === null) { | ||||
|             return null; | ||||
|         } | ||||
|         let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address.id }); | ||||
|         if (!address) { throw new AddressNotFoundError; } | ||||
|         return address; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Updates a provided RunnerOrganisation entity based on this. | ||||
|      */ | ||||
|     public async updateRunnerOrganisation(organisation: RunnerOrganisation): Promise<RunnerOrganisation> { | ||||
|  | ||||
|         organisation.name = this.name; | ||||
|         organisation.contact = await this.getContact(); | ||||
|         organisation.address = await this.getAddress(); | ||||
|  | ||||
|         return organisation; | ||||
|     } | ||||
| } | ||||
| @@ -6,23 +6,31 @@ import { RunnerOrganisation } from '../entities/RunnerOrganisation'; | ||||
| import { RunnerTeam } from '../entities/RunnerTeam'; | ||||
| import { CreateRunnerGroup } from './CreateRunnerGroup'; | ||||
|  | ||||
| /** | ||||
|  * This class is used to update a RunnerTeam entity (via put request). | ||||
|  */ | ||||
| export class UpdateRunnerTeam extends CreateRunnerGroup { | ||||
|  | ||||
|     /** | ||||
|      * The updated team'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 team's parent group (organisation). | ||||
|      * The updated team's parentGroup. | ||||
|      * Just has to contain the organisation's id - everything else won't be checked or changed. | ||||
|      */ | ||||
|     @IsObject() | ||||
|     @IsNotEmpty() | ||||
|     parentGroup: RunnerOrganisation; | ||||
|  | ||||
|     /** | ||||
|      * Loads the updated teams's parentGroup based on it's id. | ||||
|      */ | ||||
|     public async getParent(): Promise<RunnerOrganisation> { | ||||
|         if (this.parentGroup === undefined) { | ||||
|         if (this.parentGroup === undefined || this.parentGroup === null) { | ||||
|             throw new RunnerTeamNeedsParentError(); | ||||
|         } | ||||
|         if (!isNaN(this.parentGroup.id)) { | ||||
| @@ -35,16 +43,14 @@ export class UpdateRunnerTeam extends CreateRunnerGroup { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates a RunnerTeam entity from this. | ||||
|      * Updates a provided RunnerTeam entity based on this. | ||||
|      */ | ||||
|     public async toRunnerTeam(): Promise<RunnerTeam> { | ||||
|         let newRunnerTeam: RunnerTeam = new RunnerTeam(); | ||||
|     public async updateRunnerTeam(team: RunnerTeam): Promise<RunnerTeam> { | ||||
|  | ||||
|         newRunnerTeam.id = this.id; | ||||
|         newRunnerTeam.name = this.name; | ||||
|         newRunnerTeam.parentGroup = await this.getParent(); | ||||
|         newRunnerTeam.contact = await this.getContact() | ||||
|         team.name = this.name; | ||||
|         team.parentGroup = await this.getParent(); | ||||
|         team.contact = await this.getContact() | ||||
|  | ||||
|         return newRunnerTeam; | ||||
|         return team; | ||||
|     } | ||||
| } | ||||
| @@ -3,14 +3,18 @@ import { IsBoolean, IsEmail, IsInt, IsOptional, IsPhoneNumber, IsString } from ' | ||||
| import { getConnectionManager } from 'typeorm'; | ||||
| import { config } from '../../config'; | ||||
| import { UsernameOrEmailNeededError } from '../../errors/AuthError'; | ||||
| import { RunnerGroupNotFoundError } from '../../errors/RunnerGroupErrors'; | ||||
| 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). | ||||
|  */ | ||||
| export class UpdateUser { | ||||
|  | ||||
|     /** | ||||
|      * The updated users's id. | ||||
|      * The updated user'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; | ||||
| @@ -23,7 +27,6 @@ export class UpdateUser { | ||||
|  | ||||
|     /** | ||||
|      * The updated user's middle name. | ||||
|      * Optinal. | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsOptional() | ||||
| @@ -54,14 +57,16 @@ export class UpdateUser { | ||||
|  | ||||
|     /** | ||||
|      * The updated user's phone number. | ||||
|      * Optional | ||||
|      * This will be validated against the configured country phone numer syntax (default: international). | ||||
|      */ | ||||
|     @IsPhoneNumber(config.phone_validation_countrycode) | ||||
|     @IsOptional() | ||||
|     phone?: string; | ||||
|  | ||||
|     /** | ||||
|      * The new updated's password. Only provide if you want it updated. | ||||
|      * The new updated's password.  | ||||
|      * Only provide it if you want it updated. | ||||
|      * Changeing the password will invalidate all of the user's jwts. | ||||
|      * This will of course not be saved in plaintext :) | ||||
|      */ | ||||
|     @IsString() | ||||
| @@ -75,20 +80,19 @@ export class UpdateUser { | ||||
|     enabled: boolean = true; | ||||
|  | ||||
|     /** | ||||
|      * The new user's groups' id(s). | ||||
|      * You can provide either one groupId or an array of groupIDs. | ||||
|      * Optional. | ||||
|      * The updated user's groups. | ||||
|      * This just has to contain the group's id - everything else won't be changed. | ||||
|      */ | ||||
|     @IsOptional() | ||||
|     groups?: UserGroup[] | ||||
|  | ||||
|     /** | ||||
|      * Creates a User entity from this. | ||||
|      * Updates a provided User entity based on this. | ||||
|      */ | ||||
|     public async toUser(user: User): Promise<User> { | ||||
|     public async updateUser(user: User): Promise<User> { | ||||
|         user.email = this.email; | ||||
|         user.username = this.username; | ||||
|         if (user.email === undefined && user.username === undefined) { | ||||
|         if ((user.email === undefined || user.email === null) && (user.username === undefined || user.username === null)) { | ||||
|             throw new UsernameOrEmailNeededError(); | ||||
|         } | ||||
|         if (this.password) { | ||||
| @@ -107,6 +111,9 @@ export class UpdateUser { | ||||
|         return user; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Loads the updated user's groups based on their ids. | ||||
|      */ | ||||
|     public async getGroups() { | ||||
|         if (!this.groups) { return null; } | ||||
|         let groups = new Array<UserGroup>(); | ||||
| @@ -115,7 +122,7 @@ export class UpdateUser { | ||||
|         } | ||||
|         for (let group of this.groups) { | ||||
|             let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group.id }); | ||||
|             if (!found) { throw new RunnerGroupNotFoundError(); } | ||||
|             if (!found) { throw new UserGroupNotFoundError(); } | ||||
|             groups.push(found); | ||||
|         } | ||||
|         return groups; | ||||
|   | ||||
| @@ -10,7 +10,8 @@ import { Participant } from "./Participant"; | ||||
| import { RunnerOrganisation } from "./RunnerOrganisation"; | ||||
|  | ||||
| /** | ||||
|  * Defines a address (to be used for contact information). | ||||
|  * Defines the Address entity. | ||||
|  * Implemented this way to prevent any formatting differences. | ||||
| */ | ||||
| @Entity() | ||||
| export class Address { | ||||
| @@ -23,6 +24,7 @@ export class Address { | ||||
|  | ||||
|   /** | ||||
|    * The address's description. | ||||
|    * Optional and mostly for UX. | ||||
|    */ | ||||
|   @Column({ nullable: true }) | ||||
|   @IsString() | ||||
| @@ -49,6 +51,8 @@ export class Address { | ||||
|  | ||||
|   /** | ||||
|    * The address's postal code. | ||||
|    * This will get checked against the postal code syntax for the configured country. | ||||
|    * TODO: Implement the config option.  | ||||
|    */ | ||||
|   @Column() | ||||
|   @IsString() | ||||
|   | ||||
| @@ -4,19 +4,21 @@ import { Donation } from "./Donation"; | ||||
| import { Runner } from "./Runner"; | ||||
|  | ||||
| /** | ||||
|  * Defines a distance based donation. | ||||
|  * Here people donate a certain amout per kilometer | ||||
|  * Defines the DistanceDonation entity. | ||||
|  * For distanceDonations a donor pledges to donate a certain amount for each kilometer ran by a runner. | ||||
| */ | ||||
| @ChildEntity() | ||||
| export class DistanceDonation extends Donation { | ||||
|   /** | ||||
|    * The runner associated. | ||||
|    * The donation's associated runner. | ||||
|    * Used as the source of the donation's distance. | ||||
|    */ | ||||
|   @IsNotEmpty() | ||||
|   @ManyToOne(() => Runner, runner => runner.distanceDonations) | ||||
|   runner: Runner; | ||||
|  | ||||
|   /** | ||||
|    * The donation's amount donated per distance. | ||||
|    * The amount the donor set to be donated per kilometer that the runner ran. | ||||
|    */ | ||||
|   @Column() | ||||
| @@ -26,12 +28,12 @@ export class DistanceDonation extends Donation { | ||||
|  | ||||
|   /** | ||||
|    * The donation's amount in cents (or whatever your currency's smallest unit is.). | ||||
|    * The exact implementation may differ for each type of donation. | ||||
|    * Get's calculated from the runner's distance ran and the amount donated per kilometer. | ||||
|    */ | ||||
|   public get amount(): number { | ||||
|     let calculatedAmount = -1; | ||||
|     try { | ||||
|       calculatedAmount = this.amountPerDistance * this.runner.distance; | ||||
|       calculatedAmount = this.amountPerDistance * (this.runner.distance / 1000); | ||||
|     } catch (error) { | ||||
|       throw error; | ||||
|     } | ||||
|   | ||||
| @@ -6,7 +6,9 @@ import { Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typ | ||||
| import { Participant } from "./Participant"; | ||||
|  | ||||
| /** | ||||
|  * Defines the donation interface. | ||||
|  * Defines the Donation entity. | ||||
|  * A donation just associates a donor with a donation amount. | ||||
|  * The specifics of the amoun's determination has to be implemented in child classes. | ||||
| */ | ||||
| @Entity() | ||||
| @TableInheritance({ column: { name: "type", type: "varchar" } }) | ||||
|   | ||||
| @@ -3,13 +3,13 @@ import { ChildEntity, Column } from "typeorm"; | ||||
| import { Participant } from "./Participant"; | ||||
|  | ||||
| /** | ||||
|  * Defines a donor. | ||||
|  * Defines the Donor entity. | ||||
| */ | ||||
| @ChildEntity() | ||||
| export class Donor extends Participant { | ||||
|   /** | ||||
|    * Does this donor need a receipt?. | ||||
|    * Default: false | ||||
|    * Does this donor need a receipt? | ||||
|    * Will later be used to automaticly generate donation receipts. | ||||
|    */ | ||||
|   @Column() | ||||
|   @IsBoolean() | ||||
|   | ||||
| @@ -3,7 +3,8 @@ import { ChildEntity, Column } from "typeorm"; | ||||
| import { Donation } from "./Donation"; | ||||
|  | ||||
| /** | ||||
|  * Defines a fixed donation. | ||||
|  * Defines the FixedDonation entity. | ||||
|  * In the past there was no easy way to track fixed donations (eg. for creating donation receipts). | ||||
| */ | ||||
| @ChildEntity() | ||||
| export class FixedDonation extends Donation { | ||||
|   | ||||
| @@ -13,13 +13,14 @@ import { Address } from "./Address"; | ||||
| import { RunnerGroup } from "./RunnerGroup"; | ||||
|  | ||||
| /** | ||||
|  * Defines a group's contact. | ||||
|  * Defines the GroupContact entity. | ||||
|  * Mainly it's own class to reduce duplicate code and enable contact's to be associated with multiple groups. | ||||
| */ | ||||
| @Entity() | ||||
| export class GroupContact { | ||||
|   /** | ||||
|  * Autogenerated unique id (primary key). | ||||
|  */ | ||||
|    * Autogenerated unique id (primary key). | ||||
|    */ | ||||
|   @PrimaryGeneratedColumn() | ||||
|   @IsInt() | ||||
|   id: number; | ||||
| @@ -34,7 +35,6 @@ export class GroupContact { | ||||
|  | ||||
|   /** | ||||
|    * The contact's middle name. | ||||
|    * Optional | ||||
|    */ | ||||
|   @Column({ nullable: true }) | ||||
|   @IsOptional() | ||||
| @@ -51,7 +51,7 @@ export class GroupContact { | ||||
|  | ||||
|   /** | ||||
|    * The contact's address. | ||||
|    * Optional | ||||
|    * This is a address object to prevent any formatting differences. | ||||
|    */ | ||||
|   @IsOptional() | ||||
|   @ManyToOne(() => Address, address => address.participants, { nullable: true }) | ||||
| @@ -59,7 +59,7 @@ export class GroupContact { | ||||
|  | ||||
|   /** | ||||
|    * The contact's phone number. | ||||
|    * Optional | ||||
|    * This will be validated against the configured country phone numer syntax (default: international). | ||||
|    */ | ||||
|   @Column({ nullable: true }) | ||||
|   @IsOptional() | ||||
| @@ -68,7 +68,7 @@ export class GroupContact { | ||||
|  | ||||
|   /** | ||||
|    * The contact's email address. | ||||
|    * Optional | ||||
|    * Could later be used to automaticly send mails concerning the contact's associated groups. | ||||
|    */ | ||||
|   @Column({ nullable: true }) | ||||
|   @IsOptional() | ||||
|   | ||||
| @@ -13,7 +13,8 @@ import { Address } from "./Address"; | ||||
| import { Donation } from "./Donation"; | ||||
|  | ||||
| /** | ||||
|  * Defines the participant interface. | ||||
|  * Defines the Participant entity. | ||||
|  * Participans can donate and therefor be associated with donation entities. | ||||
| */ | ||||
| @Entity() | ||||
| @TableInheritance({ column: { name: "type", type: "varchar" } }) | ||||
| @@ -35,7 +36,6 @@ export abstract class Participant { | ||||
|  | ||||
|   /** | ||||
|    * The participant's middle name. | ||||
|    * Optional | ||||
|    */ | ||||
|   @Column({ nullable: true }) | ||||
|   @IsOptional() | ||||
| @@ -52,14 +52,14 @@ export abstract class Participant { | ||||
|  | ||||
|   /** | ||||
|    * The participant's address. | ||||
|    * Optional | ||||
|    * This is a address object to prevent any formatting differences. | ||||
|    */ | ||||
|   @ManyToOne(() => Address, address => address.participants, { nullable: true }) | ||||
|   address?: Address; | ||||
|  | ||||
|   /** | ||||
|    * The participant's phone number. | ||||
|    * Optional | ||||
|    * This will be validated against the configured country phone numer syntax (default: international). | ||||
|    */ | ||||
|   @Column({ nullable: true }) | ||||
|   @IsOptional() | ||||
| @@ -68,7 +68,7 @@ export abstract class Participant { | ||||
|  | ||||
|   /** | ||||
|    * The participant's email address. | ||||
|    * Optional | ||||
|    * Can be used to contact the participant. | ||||
|    */ | ||||
|   @Column({ nullable: true }) | ||||
|   @IsOptional() | ||||
| @@ -77,6 +77,7 @@ export abstract class Participant { | ||||
|  | ||||
|   /** | ||||
|    * 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[]; | ||||
|   | ||||
| @@ -8,7 +8,9 @@ import { PermissionAction } from '../enums/PermissionAction'; | ||||
| import { PermissionTarget } from '../enums/PermissionTargets'; | ||||
| import { Principal } from './Principal'; | ||||
| /** | ||||
|  * Defines the Permission interface. | ||||
|  * Defines the Permission entity. | ||||
|  * Permissions can be granted to principals. | ||||
|  * The permissions possible targets and actions are defined in enums. | ||||
| */ | ||||
| @Entity() | ||||
| export class Permission { | ||||
| @@ -20,13 +22,14 @@ export class Permission { | ||||
|   id: number; | ||||
|  | ||||
|   /** | ||||
|    * The permissions principal | ||||
|    * The permission's principal. | ||||
|    */ | ||||
|   @ManyToOne(() => Principal, principal => principal.permissions) | ||||
|   principal: Principal; | ||||
|  | ||||
|   /** | ||||
|    * The target | ||||
|    * The permission's target. | ||||
|    * This get's stored as the enum value's string representation for compatability reasons. | ||||
|    */ | ||||
|   @Column({ type: 'varchar' }) | ||||
|   @IsNotEmpty() | ||||
| @@ -34,14 +37,16 @@ export class Permission { | ||||
|   target: PermissionTarget; | ||||
|  | ||||
|   /** | ||||
|    * The action type | ||||
|    * The permission's action. | ||||
|    * This get's stored as the enum value's string representation for compatability reasons. | ||||
|    */ | ||||
|   @Column({ type: 'varchar' }) | ||||
|   @IsEnum(PermissionAction) | ||||
|   action: PermissionAction; | ||||
|  | ||||
|   /** | ||||
|    * Turn this into a string for exporting (and jwts). | ||||
|    * Turn this into a string for exporting and jwts. | ||||
|    * Mainly used to shrink the size of jwts (otherwise the would contain entire objects). | ||||
|    */ | ||||
|   public toString(): string { | ||||
|     return this.target + ":" + this.action; | ||||
|   | ||||
| @@ -4,23 +4,27 @@ import { ResponsePrincipal } from '../responses/ResponsePrincipal'; | ||||
| import { Permission } from './Permission'; | ||||
|  | ||||
| /** | ||||
|  * Defines a admin user. | ||||
|  * Defines the principal entity. | ||||
|  * A principal basicly is any entity that can receive permissions for the api (users and their groups). | ||||
| */ | ||||
| @Entity() | ||||
| @TableInheritance({ column: { name: "type", type: "varchar" } }) | ||||
| export abstract class Principal { | ||||
|   /** | ||||
|   * autogenerated unique id (primary key). | ||||
|   * Autogenerated unique id (primary key). | ||||
|   */ | ||||
|   @PrimaryGeneratedColumn() | ||||
|   @IsInt() | ||||
|   id: number; | ||||
|  | ||||
|   /** | ||||
| * permissions | ||||
| */ | ||||
|   * The participant's permissions. | ||||
|   */ | ||||
|   @OneToMany(() => Permission, permission => permission.principal, { nullable: true }) | ||||
|   permissions: Permission[]; | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   public abstract toResponse(): ResponsePrincipal; | ||||
| } | ||||
| @@ -7,44 +7,52 @@ import { RunnerGroup } from "./RunnerGroup"; | ||||
| import { Scan } from "./Scan"; | ||||
|  | ||||
| /** | ||||
|  * Defines a runner. | ||||
|  * Defines the runner entity. | ||||
|  * Runners differ from participants in being able to actually accumulate a ran distance through scans. | ||||
|  * Runner's get organized in groups. | ||||
| */ | ||||
| @ChildEntity() | ||||
| export class Runner extends Participant { | ||||
|   /** | ||||
|    * The runner's associated group. | ||||
|    * Can be a runner team or organisation. | ||||
|    */ | ||||
|   @IsNotEmpty() | ||||
|   @ManyToOne(() => RunnerGroup, group => group.runners, { nullable: false }) | ||||
|   group: RunnerGroup; | ||||
|  | ||||
|   /** | ||||
|    * Used to link runners to donations. | ||||
|    * The runner's associated distanceDonations. | ||||
|    * Used to link runners to distanceDonations in order to calculate the donation's amount based on the distance the runner ran. | ||||
|    */ | ||||
|   @OneToMany(() => DistanceDonation, distanceDonation => distanceDonation.runner, { nullable: true }) | ||||
|   distanceDonations: DistanceDonation[]; | ||||
|  | ||||
|   /** | ||||
|    * Used to link runners to cards. | ||||
|    * The runner's associated cards. | ||||
|    * Used to link runners to cards - yes a runner be associated with multiple cards this came in handy in the past. | ||||
|    */ | ||||
|   @OneToMany(() => RunnerCard, card => card.runner, { nullable: true }) | ||||
|   cards: RunnerCard[]; | ||||
|  | ||||
|   /** | ||||
|    * Used to link runners to a scans | ||||
|    * The runner's associated scans. | ||||
|    * Used to link runners to scans (valid and fraudulant). | ||||
|    */ | ||||
|   @OneToMany(() => Scan, scan => scan.runner, { nullable: true }) | ||||
|   scans: Scan[]; | ||||
|  | ||||
|   /** | ||||
|    * Returns all valid scans associated with this runner. | ||||
|    * This is implemented here to avoid duplicate code in other files. | ||||
|    */ | ||||
|   public get validScans(): Scan[] { | ||||
|     return this.scans.filter(scan => { scan.valid === true }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Returns the total distance ran by this runner. | ||||
|    * Returns the total distance ran by this runner based on all his valid scans. | ||||
|    * This is implemented here to avoid duplicate code in other files. | ||||
|   */ | ||||
|   @IsInt() | ||||
|   public get distance(): number { | ||||
|   | ||||
| @@ -11,7 +11,9 @@ import { Runner } from "./Runner"; | ||||
| import { TrackScan } from "./TrackScan"; | ||||
|  | ||||
| /** | ||||
|  * Defines a card that can be scanned via a scanner station. | ||||
|  * Defines the RunnerCard entity. | ||||
|  * A runnerCard is a physical representation for a runner. | ||||
|  * It can be associated with a runner to create scans via the scan station's. | ||||
| */ | ||||
| @Entity() | ||||
| export class RunnerCard { | ||||
| @@ -23,7 +25,8 @@ export class RunnerCard { | ||||
|   id: number; | ||||
|  | ||||
|   /** | ||||
|    * The runner that is currently associated with this card. | ||||
|    * The card's currently associated runner. | ||||
|    * To increase reusability a card can be reassigned. | ||||
|    */ | ||||
|   @IsOptional() | ||||
|   @ManyToOne(() => Runner, runner => runner.cards, { nullable: true }) | ||||
| @@ -32,7 +35,7 @@ export class RunnerCard { | ||||
|   /** | ||||
|    * The card's code. | ||||
|    * This has to be able to being converted to something barcode compatible. | ||||
|    * could theoretically be autogenerated | ||||
|    * Will get automaticlly generated (not implemented yet). | ||||
|    */ | ||||
|   @Column() | ||||
|   @IsEAN() | ||||
| @@ -49,7 +52,8 @@ export class RunnerCard { | ||||
|   enabled: boolean = true; | ||||
|  | ||||
|   /** | ||||
|    * Used to link cards to a track scans. | ||||
|    * The card's associated scans. | ||||
|    * Used to link cards to track scans. | ||||
|    */ | ||||
|   @OneToMany(() => TrackScan, scan => scan.track, { nullable: true }) | ||||
|   scans: TrackScan[]; | ||||
|   | ||||
| @@ -9,7 +9,8 @@ import { GroupContact } from "./GroupContact"; | ||||
| import { Runner } from "./Runner"; | ||||
|  | ||||
| /** | ||||
|  * Defines the runnerGroup interface. | ||||
|  * Defines the RunnerGroup entity. | ||||
|  * This is used to group runners together (as the name suggests). | ||||
| */ | ||||
| @Entity() | ||||
| @TableInheritance({ column: { name: "type", type: "varchar" } }) | ||||
| @@ -31,13 +32,14 @@ export abstract class RunnerGroup { | ||||
|  | ||||
|   /** | ||||
|    * The group's contact. | ||||
|    * Optional | ||||
|    * This is mostly a feature for the group managers and public relations. | ||||
|    */ | ||||
|   @IsOptional() | ||||
|   @ManyToOne(() => GroupContact, contact => contact.groups, { nullable: true }) | ||||
|   contact?: GroupContact; | ||||
|  | ||||
|   /** | ||||
|    * The group's associated runners. | ||||
|    * Used to link runners to a runner group. | ||||
|    */ | ||||
|   @OneToMany(() => Runner, runner => runner.group, { nullable: true }) | ||||
|   | ||||
| @@ -5,22 +5,23 @@ import { RunnerGroup } from "./RunnerGroup"; | ||||
| import { RunnerTeam } from "./RunnerTeam"; | ||||
|  | ||||
| /** | ||||
|  * Defines a runner organisation (business or school for example). | ||||
|  * Defines the RunnerOrganisation entity. | ||||
|  * This usually is a school, club or company. | ||||
| */ | ||||
| @ChildEntity() | ||||
| export class RunnerOrganisation extends RunnerGroup { | ||||
|  | ||||
|   /** | ||||
|    * The organisations's address. | ||||
|    * Optional | ||||
|    */ | ||||
|   @IsOptional() | ||||
|   @ManyToOne(() => Address, address => address.groups, { nullable: true }) | ||||
|   address?: Address; | ||||
|  | ||||
|   /** | ||||
|  * Used to link teams to runner groups. | ||||
|  */ | ||||
|    * The organisation's teams. | ||||
|    * Used to link teams to a organisation. | ||||
|    */ | ||||
|   @OneToMany(() => RunnerTeam, team => team.parentGroup, { nullable: true }) | ||||
|   teams: RunnerTeam[]; | ||||
| } | ||||
| @@ -4,14 +4,15 @@ import { RunnerGroup } from "./RunnerGroup"; | ||||
| import { RunnerOrganisation } from "./RunnerOrganisation"; | ||||
|  | ||||
| /** | ||||
|  * Defines a runner team (class or deparment for example). | ||||
|  * Defines the RunnerTeam entity. | ||||
|  * This usually is a school class or department in a company. | ||||
| */ | ||||
| @ChildEntity() | ||||
| export class RunnerTeam extends RunnerGroup { | ||||
|  | ||||
|   /** | ||||
|    * The team's parent group. | ||||
|    * Optional | ||||
|    * Every team has to be part of a runnerOrganisation - this get's checked on creation and update. | ||||
|    */ | ||||
|   @IsNotEmpty() | ||||
|   @ManyToOne(() => RunnerOrganisation, org => org.teams, { nullable: true }) | ||||
|   | ||||
| @@ -9,7 +9,8 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } f | ||||
| import { Runner } from "./Runner"; | ||||
|  | ||||
| /** | ||||
|  * Defines the scan interface. | ||||
|  * Defines the Scan entity. | ||||
|  * A scan basicly adds a certain distance to a runner's total ran distance. | ||||
| */ | ||||
| @Entity() | ||||
| @TableInheritance({ column: { name: "type", type: "varchar" } }) | ||||
| @@ -22,7 +23,8 @@ export abstract class Scan { | ||||
|   id: number; | ||||
|  | ||||
|   /** | ||||
|    * The associated runner. | ||||
|    * The scan's associated runner. | ||||
|    * This is important to link ran distances to runners. | ||||
|    */ | ||||
|   @IsNotEmpty() | ||||
|   @ManyToOne(() => Runner, runner => runner.scans, { nullable: false }) | ||||
| @@ -30,15 +32,17 @@ export abstract class Scan { | ||||
|  | ||||
|   /** | ||||
|    * 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). | ||||
|  * Default: true | ||||
|  */ | ||||
|    * Is the scan valid (for fraud reasons). | ||||
|    * The determination of validity will work differently for every child class. | ||||
|    * Default: true | ||||
|    */ | ||||
|   @Column() | ||||
|   @IsBoolean() | ||||
|   valid: boolean = true; | ||||
|   | ||||
| @@ -10,7 +10,8 @@ import { Track } from "./Track"; | ||||
| import { TrackScan } from "./TrackScan"; | ||||
|  | ||||
| /** | ||||
|  * ScannerStations have the ability to create scans for specific tracks. | ||||
|  * Defines the ScanStation entity. | ||||
|  * ScanStations get used to create TrackScans for runners based on a scan of their runnerCard. | ||||
| */ | ||||
| @Entity() | ||||
| export class ScanStation { | ||||
| @@ -23,6 +24,7 @@ export class ScanStation { | ||||
|  | ||||
|   /** | ||||
|    * The station's description. | ||||
|    * Mostly for better UX when traceing back stuff. | ||||
|    */ | ||||
|   @Column({ nullable: true }) | ||||
|   @IsOptional() | ||||
| @@ -31,6 +33,7 @@ export class ScanStation { | ||||
|  | ||||
|   /** | ||||
|    * The track this station is associated with. | ||||
|    * All scans created by this station will also be associated with this track. | ||||
|    */ | ||||
|   @IsNotEmpty() | ||||
|   @ManyToOne(() => Track, track => track.stations, { nullable: false }) | ||||
| @@ -38,6 +41,7 @@ export class ScanStation { | ||||
|  | ||||
|   /** | ||||
|    * The station's api key. | ||||
|    * This is used to authorize a station against the api (not implemented yet). | ||||
|    */ | ||||
|   @Column() | ||||
|   @IsNotEmpty() | ||||
| @@ -45,7 +49,7 @@ export class ScanStation { | ||||
|   key: string; | ||||
|  | ||||
|   /** | ||||
|    * Is the station enabled (for fraud reasons)? | ||||
|    * Is the station enabled (for fraud and setup reasons)? | ||||
|    * Default: true | ||||
|    */ | ||||
|   @Column() | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| import { | ||||
|   IsInt, | ||||
|   IsNotEmpty, | ||||
|  | ||||
|   IsPositive, | ||||
|   IsString | ||||
| } from "class-validator"; | ||||
| @@ -10,7 +9,7 @@ import { ScanStation } from "./ScanStation"; | ||||
| import { TrackScan } from "./TrackScan"; | ||||
|  | ||||
| /** | ||||
|  * Defines a track of given length. | ||||
|  * Defines the Track entity. | ||||
| */ | ||||
| @Entity() | ||||
| export class Track { | ||||
| @@ -23,6 +22,7 @@ export class Track { | ||||
|  | ||||
|   /** | ||||
|    * The track's name. | ||||
|    * Mainly here for UX. | ||||
|    */ | ||||
|   @Column() | ||||
|   @IsString() | ||||
| @@ -31,6 +31,7 @@ export class Track { | ||||
|  | ||||
|   /** | ||||
|    * The track's length/distance in meters. | ||||
|    * Will be used to calculate runner's ran distances. | ||||
|    */ | ||||
|   @Column() | ||||
|   @IsInt() | ||||
| @@ -38,13 +39,15 @@ export class Track { | ||||
|   distance: number; | ||||
|  | ||||
|   /** | ||||
|    * Used to link scan stations to track. | ||||
|    * Used to link scan stations to a certain track. | ||||
|    * This makes the configuration of the scan stations easier. | ||||
|    */ | ||||
|   @OneToMany(() => ScanStation, station => station.track, { nullable: true }) | ||||
|   stations: ScanStation[]; | ||||
|  | ||||
|   /** | ||||
|    * Used to link track scans to a track. | ||||
|    * The scan will derive it's distance from the track's distance. | ||||
|    */ | ||||
|   @OneToMany(() => TrackScan, scan => scan.track, { nullable: true }) | ||||
|   scans: TrackScan[]; | ||||
|   | ||||
| @@ -12,26 +12,30 @@ import { ScanStation } from "./ScanStation"; | ||||
| import { Track } from "./Track"; | ||||
|  | ||||
| /** | ||||
|  * Defines the scan interface. | ||||
|  * Defines the TrackScan entity. | ||||
|  * A track scan usaually get's generated by a scan station. | ||||
| */ | ||||
| @ChildEntity() | ||||
| export class TrackScan extends Scan { | ||||
|   /** | ||||
|    * The associated track. | ||||
|    * The scan's associated track. | ||||
|    * This is used to determine the scan's distance. | ||||
|    */ | ||||
|   @IsNotEmpty() | ||||
|   @ManyToOne(() => Track, track => track.scans, { nullable: true }) | ||||
|   track: Track; | ||||
|  | ||||
|   /** | ||||
|    * The associated card. | ||||
|    * The runnerCard associated with the scan. | ||||
|    * This get's saved for documentation and management purposes. | ||||
|    */ | ||||
|   @IsNotEmpty() | ||||
|   @ManyToOne(() => RunnerCard, card => card.scans, { nullable: true }) | ||||
|   card: RunnerCard; | ||||
|  | ||||
|   /** | ||||
|    * The scanning station. | ||||
|    * The scanning station that created the scan. | ||||
|    * Mainly used for logging and traceing back scans (or errors) | ||||
|    */ | ||||
|   @IsNotEmpty() | ||||
|   @ManyToOne(() => ScanStation, station => station.scans, { nullable: true }) | ||||
| @@ -39,6 +43,7 @@ export class TrackScan extends Scan { | ||||
|  | ||||
|   /** | ||||
|    * The scan's distance in meters. | ||||
|    * This just get's loaded from it's track. | ||||
|    */ | ||||
|   @IsInt() | ||||
|   @IsPositive() | ||||
| @@ -48,6 +53,7 @@ export class TrackScan extends Scan { | ||||
|  | ||||
|   /** | ||||
|    * The scan's creation timestamp. | ||||
|    * Will be used to implement fraud detection. | ||||
|    */ | ||||
|   @Column() | ||||
|   @IsDateString() | ||||
|   | ||||
| @@ -8,26 +8,29 @@ import { UserAction } from './UserAction'; | ||||
| import { UserGroup } from './UserGroup'; | ||||
|  | ||||
| /** | ||||
|  * Defines a admin user. | ||||
|  * Defines the User entity. | ||||
|  * Users are the ones that can use the "admin" webui and do stuff in the backend. | ||||
| */ | ||||
| @ChildEntity() | ||||
| export class User extends Principal { | ||||
|   /** | ||||
|   * uuid | ||||
|   * The user's uuid. | ||||
|   * Mainly gets used as a per-user salt for the password hash. | ||||
|   */ | ||||
|   @Column({ unique: true }) | ||||
|   @IsUUID(4) | ||||
|   uuid: string; | ||||
|  | ||||
|   /** | ||||
|   * user email | ||||
|   * The user's e-mail address. | ||||
|   * Either username or email has to be set (otherwise the user couldn't log in). | ||||
|   */ | ||||
|   @Column({ nullable: true, unique: true }) | ||||
|   @IsEmail() | ||||
|   email?: string; | ||||
|  | ||||
|   /** | ||||
|   * user phone | ||||
|   * The user's phone number. | ||||
|   */ | ||||
|   @Column({ nullable: true }) | ||||
|   @IsOptional() | ||||
| @@ -35,14 +38,15 @@ export class User extends Principal { | ||||
|   phone?: string; | ||||
|  | ||||
|   /** | ||||
|   * username | ||||
|   * The user's username. | ||||
|   * Either username or email has to be set (otherwise the user couldn't log in). | ||||
|   */ | ||||
|   @Column({ nullable: true, unique: true }) | ||||
|   @IsString() | ||||
|   username?: string; | ||||
|  | ||||
|   /** | ||||
|   * firstname | ||||
|   * The user's first name. | ||||
|   */ | ||||
|   @Column() | ||||
|   @IsString() | ||||
| @@ -50,7 +54,7 @@ export class User extends Principal { | ||||
|   firstname: string; | ||||
|  | ||||
|   /** | ||||
|   * middlename | ||||
|   * The user's middle name. | ||||
|   */ | ||||
|   @Column({ nullable: true }) | ||||
|   @IsString() | ||||
| @@ -58,7 +62,7 @@ export class User extends Principal { | ||||
|   middlename?: string; | ||||
|  | ||||
|   /** | ||||
|   * lastname | ||||
|   * The user's last name. | ||||
|   */ | ||||
|   @Column() | ||||
|   @IsString() | ||||
| @@ -66,7 +70,8 @@ export class User extends Principal { | ||||
|   lastname: string; | ||||
|  | ||||
|   /** | ||||
|   * password | ||||
|   * The user's password. | ||||
|   * This is a argon2 hash salted with the user's uuid. | ||||
|   */ | ||||
|   @Column() | ||||
|   @IsString() | ||||
| @@ -74,7 +79,8 @@ export class User extends Principal { | ||||
|   password: string; | ||||
|  | ||||
|   /** | ||||
|   * groups | ||||
|   * The groups this user is a part of. | ||||
|   * The user will inherit the groups permissions (without overwriting his own). | ||||
|   */ | ||||
|   @IsOptional() | ||||
|   @ManyToMany(() => UserGroup, { nullable: true }) | ||||
| @@ -82,21 +88,23 @@ export class User extends Principal { | ||||
|   groups: UserGroup[]; | ||||
|  | ||||
|   /** | ||||
|   * is user enabled? | ||||
|   * Is this user enabled? | ||||
|   */ | ||||
|   @Column() | ||||
|   @IsBoolean() | ||||
|   enabled: boolean = true; | ||||
|  | ||||
|   /** | ||||
|   * jwt refresh count | ||||
|   * The user's jwt refresh token count. | ||||
|   * Used to invalidate jwts. | ||||
|   */ | ||||
|   @IsInt() | ||||
|   @Column({ default: 1 }) | ||||
|   refreshTokenCount?: number; | ||||
|  | ||||
|   /** | ||||
|   * profilepic | ||||
|   * 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: true }) | ||||
|   @IsString() | ||||
| @@ -104,14 +112,15 @@ export class User extends Principal { | ||||
|   profilePic?: string; | ||||
|  | ||||
|   /** | ||||
|    * actions | ||||
|    * The actions performed by this user. | ||||
|    * For documentation purposes only, will be implemented later. | ||||
|    */ | ||||
|   @IsOptional() | ||||
|   @OneToMany(() => UserAction, action => action.user, { nullable: true }) | ||||
|   actions: UserAction[] | ||||
|  | ||||
|   /** | ||||
|    * Turn this into a response. | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   public toResponse(): ResponsePrincipal { | ||||
|     return new ResponseUser(this); | ||||
|   | ||||
| @@ -1,14 +1,17 @@ | ||||
| import { | ||||
|   IsEnum, | ||||
|   IsInt, | ||||
|   IsNotEmpty, | ||||
|   IsOptional, | ||||
|   IsString | ||||
| } from "class-validator"; | ||||
| import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; | ||||
| import { PermissionAction } from '../enums/PermissionAction'; | ||||
| import { User } from './User'; | ||||
|  | ||||
| /** | ||||
|  * Defines the UserAction interface. | ||||
|  * Defines the UserAction entity. | ||||
|  * Will later be used to document a user's actions. | ||||
| */ | ||||
| @Entity() | ||||
| export class UserAction { | ||||
| @@ -20,7 +23,7 @@ export class UserAction { | ||||
|   id: number; | ||||
|  | ||||
|   /** | ||||
|    * user | ||||
|    * The user that performed the action. | ||||
|    */ | ||||
|   @ManyToOne(() => User, user => user.actions) | ||||
|   user: User | ||||
| @@ -34,15 +37,16 @@ export class UserAction { | ||||
|   target: string; | ||||
|  | ||||
|   /** | ||||
|    * The actions's action (e.g. UPDATE) | ||||
|    * The actions's action (e.g. UPDATE). | ||||
|    * Directly pulled from the PermissionAction Enum. | ||||
|    */ | ||||
|   @Column() | ||||
|   @IsNotEmpty() | ||||
|   @IsString() | ||||
|   action: string; | ||||
|   @Column({ type: 'varchar' }) | ||||
|   @IsEnum(PermissionAction) | ||||
|   action: PermissionAction; | ||||
|  | ||||
|   /** | ||||
|    * The description of change (before-> after; e.g. distance:15->17) | ||||
|    * The description of the change (before-> after; e.g. distance:15->17). | ||||
|    * Will later be defined in more detail. | ||||
|    */ | ||||
|   @Column({ nullable: true }) | ||||
|   @IsOptional() | ||||
|   | ||||
| @@ -9,7 +9,8 @@ import { ResponseUserGroup } from '../responses/ResponseUserGroup'; | ||||
| import { Principal } from './Principal'; | ||||
|  | ||||
| /** | ||||
|  * Defines the UserGroup interface. | ||||
|  * Defines the UserGroup entity. | ||||
|  * This entity describes a group of users with a set of permissions. | ||||
| */ | ||||
| @ChildEntity() | ||||
| export class UserGroup extends Principal { | ||||
| @@ -30,6 +31,9 @@ export class UserGroup extends Principal { | ||||
|   @IsString() | ||||
|   description?: string; | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   public toResponse(): ResponsePrincipal { | ||||
|     return new ResponseUserGroup(this); | ||||
|   } | ||||
|   | ||||
| @@ -1,3 +1,6 @@ | ||||
| /** | ||||
|  * This enum contains all posible actions for permissions. | ||||
|  */ | ||||
| export enum PermissionAction { | ||||
|     GET = 'GET', | ||||
|     CREATE = 'CREATE', | ||||
|   | ||||
| @@ -1,9 +1,12 @@ | ||||
| /** | ||||
|  * This enum contains all posible targets for permissions. | ||||
|  */ | ||||
| export enum PermissionTarget { | ||||
|     RUNNER = 'RUNNER', | ||||
|     ORGANISATION = 'ORGANISATION', | ||||
|     TEAM = 'TEAM', | ||||
|     TRACK = 'TRACK', | ||||
|     USER = 'USER', | ||||
|     GROUP = 'USERGROUP', | ||||
|     USERGROUP = 'USERGROUP', | ||||
|     PERMISSION = 'PERMISSION' | ||||
| } | ||||
| @@ -1,26 +1,29 @@ | ||||
| import { IsInt, IsString } from 'class-validator'; | ||||
|  | ||||
| /** | ||||
|  * Defines a auth object | ||||
|  * Defines the repsonse auth. | ||||
| */ | ||||
| export class Auth { | ||||
|   /** | ||||
|   * access_token - JWT shortterm access token | ||||
|   * The access_token - JWT shortterm access token. | ||||
|   */ | ||||
|   @IsString() | ||||
|   access_token: string; | ||||
|  | ||||
|   /** | ||||
|   * refresh_token - longterm refresh token (used for requesting new access tokens) | ||||
|   * The refresh_token - longterm refresh token (used for requesting new access tokens). | ||||
|   */ | ||||
|   @IsString() | ||||
|   refresh_token: string; | ||||
|  | ||||
|   /** | ||||
|   * access_token_expires_at - unix timestamp of access token expiry | ||||
|   * The unix timestamp for access the token's expiry. | ||||
|   */ | ||||
|   @IsInt() | ||||
|   access_token_expires_at: number; | ||||
|  | ||||
|   /** | ||||
|   * refresh_token_expires_at - unix timestamp of access token expiry | ||||
|   * The unix unix timestamp for the access token's expiry. | ||||
|   */ | ||||
|   @IsInt() | ||||
|   refresh_token_expires_at: number; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import { IsString } from 'class-validator'; | ||||
|  | ||||
| /** | ||||
|  * Defines a empty response object | ||||
|  * Defines a empty response object. | ||||
| */ | ||||
| export class ResponseEmpty { | ||||
|     @IsString() | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| import { IsString } from 'class-validator'; | ||||
|  | ||||
| /** | ||||
|  * Defines a Logout object | ||||
|  * Defines the logout response. | ||||
| */ | ||||
| export class Logout { | ||||
|   /** | ||||
|   * timestamp of logout | ||||
|   * The logout's timestamp. | ||||
|   */ | ||||
|   @IsString() | ||||
|   timestamp: number; | ||||
|   | ||||
| @@ -1,18 +1,12 @@ | ||||
| import { | ||||
|     IsInt, | ||||
|  | ||||
|  | ||||
|  | ||||
|     IsString | ||||
| } from "class-validator"; | ||||
| import { IsInt, IsString } from "class-validator"; | ||||
| import { Participant } from '../entities/Participant'; | ||||
|  | ||||
| /** | ||||
|  * Defines a participant response. | ||||
|  * Defines the participant response. | ||||
| */ | ||||
| export abstract class ResponseParticipant { | ||||
|     /** | ||||
|      * Autogenerated unique id (primary key). | ||||
|      * The participant's id. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     id: number; | ||||
| @@ -25,7 +19,6 @@ export abstract class ResponseParticipant { | ||||
|  | ||||
|     /** | ||||
|      * The participant's middle name. | ||||
|      * Optional. | ||||
|      */ | ||||
|     @IsString() | ||||
|     middlename?: string; | ||||
| @@ -38,18 +31,20 @@ export abstract class ResponseParticipant { | ||||
|  | ||||
|     /** | ||||
|      * The participant's phone number. | ||||
|      * Optional. | ||||
|      */ | ||||
|     @IsString() | ||||
|     phone?: string; | ||||
|  | ||||
|     /** | ||||
|      * The participant's e-mail address. | ||||
|      * Optional. | ||||
|      */ | ||||
|     @IsString() | ||||
|     email?: string; | ||||
|  | ||||
|     /** | ||||
|      * Creates a ResponseParticipant object from a participant. | ||||
|      * @param participant The participant the response shall be build for. | ||||
|      */ | ||||
|     public constructor(participant: Participant) { | ||||
|         this.id = participant.id; | ||||
|         this.firstname = participant.firstname; | ||||
|   | ||||
| @@ -10,11 +10,11 @@ import { PermissionTarget } from '../enums/PermissionTargets'; | ||||
| import { ResponsePrincipal } from './ResponsePrincipal'; | ||||
|  | ||||
| /** | ||||
|  * Defines a track of given length. | ||||
|  * Defines the permission response. | ||||
| */ | ||||
| export class ResponsePermission { | ||||
|     /** | ||||
|      * Autogenerated unique id (primary key). | ||||
|      * The permission's id. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     id: number;; | ||||
| @@ -40,6 +40,10 @@ export class ResponsePermission { | ||||
|     @IsEnum(PermissionAction) | ||||
|     action: PermissionAction; | ||||
|  | ||||
|     /** | ||||
|      * Creates a ResponsePermission object from a permission. | ||||
|      * @param permission The permission the response shall be build for. | ||||
|      */ | ||||
|     public constructor(permission: Permission) { | ||||
|         this.id = permission.id; | ||||
|         this.principal = permission.principal.toResponse(); | ||||
|   | ||||
| @@ -4,16 +4,20 @@ import { | ||||
| import { Principal } from '../entities/Principal'; | ||||
|  | ||||
| /** | ||||
|  * Defines Principal's response class. | ||||
|  * Defines the principal response. | ||||
| */ | ||||
| export abstract class ResponsePrincipal { | ||||
|  | ||||
|     /** | ||||
|      * Autogenerated unique id (primary key). | ||||
|      * The principal's id. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     id: number; | ||||
|  | ||||
|     /** | ||||
|      * Creates a ResponsePrincipal object from a principal. | ||||
|      * @param principal The principal the response shall be build for. | ||||
|      */ | ||||
|     public constructor(principal: Principal) { | ||||
|         this.id = principal.id; | ||||
|     } | ||||
|   | ||||
| @@ -7,13 +7,12 @@ import { RunnerGroup } from '../entities/RunnerGroup'; | ||||
| import { ResponseParticipant } from './ResponseParticipant'; | ||||
|  | ||||
| /** | ||||
|  * Defines RunnerTeam's response class. | ||||
|  * Defines the runner response. | ||||
| */ | ||||
| export class ResponseRunner extends ResponseParticipant { | ||||
|  | ||||
|     /** | ||||
|      * The runner's currently ran distance in meters. | ||||
|      * Optional. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     distance: number; | ||||
| @@ -24,6 +23,10 @@ export class ResponseRunner extends ResponseParticipant { | ||||
|     @IsObject() | ||||
|     group: RunnerGroup; | ||||
|  | ||||
|     /** | ||||
|      * Creates a ResponseRunner object from a runner. | ||||
|      * @param runner The user the response shall be build for. | ||||
|      */ | ||||
|     public constructor(runner: Runner) { | ||||
|         super(runner); | ||||
|         this.distance = runner.scans.filter(scan => { scan.valid === true }).reduce((sum, current) => sum + current.distance, 0); | ||||
|   | ||||
| @@ -1,38 +1,20 @@ | ||||
| import { | ||||
|     IsInt, | ||||
|  | ||||
|  | ||||
|  | ||||
|     IsNotEmpty, | ||||
|  | ||||
|  | ||||
|  | ||||
|     IsObject, | ||||
|  | ||||
|  | ||||
|  | ||||
|     IsOptional, | ||||
|  | ||||
|  | ||||
|  | ||||
|     IsString | ||||
| } from "class-validator"; | ||||
| import { IsInt, IsNotEmpty, IsObject, IsOptional, IsString } from "class-validator"; | ||||
| import { GroupContact } from '../entities/GroupContact'; | ||||
| import { RunnerGroup } from '../entities/RunnerGroup'; | ||||
|  | ||||
| /** | ||||
|  * Defines a track of given length. | ||||
|  * Defines the runnerGroup response. | ||||
| */ | ||||
| export abstract class ResponseRunnerGroup { | ||||
|     /** | ||||
|      * Autogenerated unique id (primary key). | ||||
|      * The runnerGroup's id. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     @IsNotEmpty() | ||||
|     id: number;; | ||||
|  | ||||
|     /** | ||||
|      * The groups's name. | ||||
|      * The runnerGroup's name. | ||||
|      */ | ||||
|     @IsString() | ||||
|     @IsNotEmpty() | ||||
| @@ -40,13 +22,16 @@ export abstract class ResponseRunnerGroup { | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * The group's contact. | ||||
|      * Optional. | ||||
|      * The runnerGroup's contact. | ||||
|      */ | ||||
|     @IsObject() | ||||
|     @IsOptional() | ||||
|     contact?: GroupContact; | ||||
|  | ||||
|     /** | ||||
|      * Creates a ResponseRunnerGroup object from a runnerGroup. | ||||
|      * @param group The runnerGroup the response shall be build for. | ||||
|      */ | ||||
|     public constructor(group: RunnerGroup) { | ||||
|         this.id = group.id; | ||||
|         this.name = group.name; | ||||
|   | ||||
| @@ -9,25 +9,27 @@ import { RunnerTeam } from '../entities/RunnerTeam'; | ||||
| import { ResponseRunnerGroup } from './ResponseRunnerGroup'; | ||||
|  | ||||
| /** | ||||
|  * Defines RunnerOrgs's response class. | ||||
|  * Defines the runnerOrganisation response. | ||||
| */ | ||||
| export class ResponseRunnerOrganisation extends ResponseRunnerGroup { | ||||
|  | ||||
|     /** | ||||
|      * The orgs's address. | ||||
|      * Optional. | ||||
|      * The runnerOrganisation's address. | ||||
|      */ | ||||
|     @IsObject() | ||||
|     @IsNotEmpty() | ||||
|     address?: Address; | ||||
|  | ||||
|     /** | ||||
|      * The orgs associated teams. | ||||
|      * The runnerOrganisation associated teams. | ||||
|      */ | ||||
|     @IsArray() | ||||
|     teams: RunnerTeam[]; | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Creates a ResponseRunnerOrganisation object from a runnerOrganisation. | ||||
|      * @param org The runnerOrganisation the response shall be build for. | ||||
|      */ | ||||
|     public constructor(org: RunnerOrganisation) { | ||||
|         super(org); | ||||
|         this.address = org.address; | ||||
|   | ||||
| @@ -1,24 +1,24 @@ | ||||
| import { | ||||
|     IsNotEmpty, | ||||
|     IsObject | ||||
| } from "class-validator"; | ||||
| import { IsNotEmpty, IsObject } from "class-validator"; | ||||
| import { RunnerOrganisation } from '../entities/RunnerOrganisation'; | ||||
| import { RunnerTeam } from '../entities/RunnerTeam'; | ||||
| import { ResponseRunnerGroup } from './ResponseRunnerGroup'; | ||||
|  | ||||
| /** | ||||
|  * Defines RunnerTeam's response class. | ||||
|  * Defines the runnerTeam response. | ||||
| */ | ||||
| export class ResponseRunnerTeam extends ResponseRunnerGroup { | ||||
|  | ||||
|     /** | ||||
|      * The team's parent group (organisation). | ||||
|      * Optional. | ||||
|      * The runnerTeam's parent group (organisation). | ||||
|      */ | ||||
|     @IsObject() | ||||
|     @IsNotEmpty() | ||||
|     parentGroup: RunnerOrganisation; | ||||
|  | ||||
|     /** | ||||
|      * Creates a ResponseRunnerTeam object from a runnerTeam. | ||||
|      * @param team The team the response shall be build for. | ||||
|      */ | ||||
|     public constructor(team: RunnerTeam) { | ||||
|         super(team); | ||||
|         this.parentGroup = team.parentGroup; | ||||
|   | ||||
| @@ -1,16 +1,12 @@ | ||||
| import { | ||||
|     IsInt, | ||||
|  | ||||
|     IsString | ||||
| } from "class-validator"; | ||||
| import { IsInt, IsString } from "class-validator"; | ||||
| import { Track } from '../entities/Track'; | ||||
|  | ||||
| /** | ||||
|  * Defines a track of given length. | ||||
|  * Defines the track response. | ||||
| */ | ||||
| export class ResponseTrack { | ||||
|     /** | ||||
|      * Autogenerated unique id (primary key). | ||||
|      * The track's id. | ||||
|      */ | ||||
|     @IsInt() | ||||
|     id: number;; | ||||
| @@ -27,6 +23,10 @@ export class ResponseTrack { | ||||
|     @IsInt() | ||||
|     distance: number; | ||||
|  | ||||
|     /** | ||||
|      * Creates a ResponseTrack object from a track. | ||||
|      * @param track The track the response shall be build for. | ||||
|      */ | ||||
|     public constructor(track: Track) { | ||||
|         this.id = track.id; | ||||
|         this.name = track.name; | ||||
|   | ||||
| @@ -11,7 +11,7 @@ import { UserGroup } from '../entities/UserGroup'; | ||||
| import { ResponsePrincipal } from './ResponsePrincipal'; | ||||
|  | ||||
| /** | ||||
|  * Defines a user response. | ||||
|  * Defines the user response. | ||||
| */ | ||||
| export class ResponseUser extends ResponsePrincipal { | ||||
|     /** | ||||
| @@ -22,7 +22,6 @@ export class ResponseUser extends ResponsePrincipal { | ||||
|  | ||||
|     /** | ||||
|      * The user's middle name. | ||||
|      * Optional. | ||||
|      */ | ||||
|     @IsString() | ||||
|     middlename?: string; | ||||
| @@ -35,45 +34,53 @@ export class ResponseUser extends ResponsePrincipal { | ||||
|  | ||||
|     /** | ||||
|      * The user's phone number. | ||||
|      * Optional. | ||||
|      */ | ||||
|     @IsString() | ||||
|     phone?: string; | ||||
|  | ||||
|     /** | ||||
|      * The user's e-mail address. | ||||
|      * Optional. | ||||
|      */ | ||||
|     @IsString() | ||||
|     email?: string; | ||||
|  | ||||
|     /** | ||||
|     * is user enabled? | ||||
|      * The user's username. | ||||
|      */ | ||||
|     @IsString() | ||||
|     username?: string; | ||||
|  | ||||
|     /** | ||||
|     * Is user enabled? | ||||
|     */ | ||||
|     @IsBoolean() | ||||
|     enabled: boolean = true; | ||||
|  | ||||
|     /** | ||||
|     * profilepic | ||||
|     * The user's profile pic. | ||||
|     */ | ||||
|     @IsString() | ||||
|     @IsOptional() | ||||
|     profilePic?: string; | ||||
|  | ||||
|     /** | ||||
|      * Groups | ||||
|      * The groups that the user is a part of. | ||||
|      */ | ||||
|     @IsArray() | ||||
|     @IsOptional() | ||||
|     groups: UserGroup[]; | ||||
|  | ||||
|     /** | ||||
|      * permissions | ||||
|      * The user's permissions. | ||||
|      */ | ||||
|     @IsArray() | ||||
|     @IsOptional() | ||||
|     permissions: Permission[]; | ||||
|  | ||||
|     /** | ||||
|      * Creates a ResponseUser object from a user. | ||||
|      * @param user The user the response shall be build for. | ||||
|      */ | ||||
|     public constructor(user: User) { | ||||
|         super(user); | ||||
|         this.firstname = user.firstname; | ||||
| @@ -81,6 +88,7 @@ export class ResponseUser extends ResponsePrincipal { | ||||
|         this.lastname = user.lastname; | ||||
|         this.phone = user.phone; | ||||
|         this.email = user.email; | ||||
|         this.username = user.username; | ||||
|         this.enabled = user.enabled; | ||||
|         this.profilePic = user.profilePic; | ||||
|         this.groups = user.groups; | ||||
|   | ||||
| @@ -1,41 +1,37 @@ | ||||
| import { | ||||
|     IsArray, | ||||
|  | ||||
|  | ||||
|     IsNotEmpty, | ||||
|  | ||||
|     IsOptional, | ||||
|     IsString | ||||
| } from "class-validator"; | ||||
| import { IsArray, IsNotEmpty, IsOptional, IsString } from "class-validator"; | ||||
| import { Permission } from '../entities/Permission'; | ||||
| import { UserGroup } from '../entities/UserGroup'; | ||||
| import { ResponsePrincipal } from './ResponsePrincipal'; | ||||
|  | ||||
| /** | ||||
|  * Defines a user response. | ||||
|  * Defines the userGroup response. | ||||
| */ | ||||
| export class ResponseUserGroup extends ResponsePrincipal { | ||||
|     /** | ||||
|      * The group's name | ||||
|      * The userGroup's name. | ||||
|      */ | ||||
|     @IsNotEmpty() | ||||
|     @IsString() | ||||
|     name: string; | ||||
|  | ||||
|     /** | ||||
|      * The group's description | ||||
|      * The userGroup's description. | ||||
|      */ | ||||
|     @IsOptional() | ||||
|     @IsString() | ||||
|     description?: string; | ||||
|  | ||||
|     /** | ||||
|      * permissions | ||||
|      * The userGroup's permissions. | ||||
|      */ | ||||
|     @IsArray() | ||||
|     @IsOptional() | ||||
|     permissions: Permission[]; | ||||
|  | ||||
|     /** | ||||
|      * Creates a ResponseUserGroup object from a userGroup. | ||||
|      * @param group The userGroup the response shall be build for. | ||||
|      */ | ||||
|     public constructor(group: UserGroup) { | ||||
|         super(group); | ||||
|         this.name = group.name; | ||||
|   | ||||
| @@ -9,6 +9,10 @@ import { UserGroup } from '../models/entities/UserGroup'; | ||||
| import { PermissionAction } from '../models/enums/PermissionAction'; | ||||
| import { PermissionTarget } from '../models/enums/PermissionTargets'; | ||||
|  | ||||
| /** | ||||
|  * Seeds a admin group with a demo user into the database for initial setup and auto recovery. | ||||
|  * We know that the nameing isn't perfectly fitting. Feel free to change it. | ||||
|  */ | ||||
| export default class SeedUsers implements Seeder { | ||||
|     public async run(factory: Factory, connection: Connection): Promise<any> { | ||||
|         let adminGroup: UserGroup = await this.createAdminGroup(connection); | ||||
| @@ -19,7 +23,7 @@ export default class SeedUsers implements Seeder { | ||||
|     public async createAdminGroup(connection: Connection) { | ||||
|         let adminGroup = new CreateUserGroup(); | ||||
|         adminGroup.name = "ADMINS"; | ||||
|         adminGroup.description = "Has all possible permissions"; | ||||
|         adminGroup.description = "Have all possible permissions"; | ||||
|         return await connection.getRepository(UserGroup).save(await adminGroup.toUserGroup()); | ||||
|     } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user