Merge pull request 'Updated the put methods and cleaned up a shitload of comments' (#42) from feature/39-update_puts into dev
All checks were successful
continuous-integration/drone/push Build is passing

Reviewed-on: #42
closes #39
This commit is contained in:
Nicolai Ort 2020-12-21 16:39:19 +00:00
commit 1bf6d3d564
79 changed files with 593 additions and 372 deletions

View File

@ -96,7 +96,7 @@ export class PermissionController {
return new ResponsePermission(existingPermission); 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'] })); return new ResponsePermission(await this.permissionRepository.findOne({ id: permission.id }, { relations: ['principal'] }));
} }

View File

@ -81,7 +81,7 @@ export class RunnerController {
throw new RunnerIdsNotMatchingError(); 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'] })); return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] }));
} }

View File

@ -1,9 +1,9 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm'; import { getConnectionManager, Repository } from 'typeorm';
import { EntityFromBody } from 'typeorm-routing-controllers-extensions';
import { RunnerOrganisationHasRunnersError, RunnerOrganisationHasTeamsError, RunnerOrganisationIdsNotMatchingError, RunnerOrganisationNotFoundError } from '../errors/RunnerOrganisationErrors'; import { RunnerOrganisationHasRunnersError, RunnerOrganisationHasTeamsError, RunnerOrganisationIdsNotMatchingError, RunnerOrganisationNotFoundError } from '../errors/RunnerOrganisationErrors';
import { CreateRunnerOrganisation } from '../models/actions/CreateRunnerOrganisation'; import { CreateRunnerOrganisation } from '../models/actions/CreateRunnerOrganisation';
import { UpdateRunnerOrganisation } from '../models/actions/UpdateRunnerOrganisation';
import { RunnerOrganisation } from '../models/entities/RunnerOrganisation'; import { RunnerOrganisation } from '../models/entities/RunnerOrganisation';
import { ResponseEmpty } from '../models/responses/ResponseEmpty'; import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseRunnerOrganisation } from '../models/responses/ResponseRunnerOrganisation'; import { ResponseRunnerOrganisation } from '../models/responses/ResponseRunnerOrganisation';
@ -71,21 +71,20 @@ export class RunnerOrganisationController {
@ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 }) @ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerOrganisationIdsNotMatchingError, { statusCode: 406 }) @ResponseSchema(RunnerOrganisationIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update a runnerOrganisation object (id can't be changed)." }) @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 }); let oldRunnerOrganisation = await this.runnerOrganisationRepository.findOne({ id: id });
if (!oldRunnerOrganisation) { if (!oldRunnerOrganisation) {
throw new RunnerOrganisationNotFoundError(); throw new RunnerOrganisationNotFoundError();
} }
if (oldRunnerOrganisation.id != runnerOrganisation.id) { if (oldRunnerOrganisation.id != updateOrganisation.id) {
throw new RunnerOrganisationIdsNotMatchingError(); 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(await this.runnerOrganisationRepository.findOne(id, { relations: ['address', 'contact', 'teams'] }));
return new ResponseRunnerOrganisation(runnerOrganisation);
} }
@Delete('/:id') @Delete('/:id')

View File

@ -82,7 +82,7 @@ export class RunnerTeamController {
throw new RunnerTeamIdsNotMatchingError(); 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'] })); return new ResponseRunnerTeam(await this.runnerTeamRepository.findOne({ id: runnerTeam.id }, { relations: ['parentGroup', 'contact'] }));
} }

View File

@ -72,7 +72,7 @@ export class TrackController {
throw new TrackIdsNotMatchingError(); throw new TrackIdsNotMatchingError();
} }
await this.trackRepository.update(oldTrack, track); await this.trackRepository.save(track);
return new ResponseTrack(track); return new ResponseTrack(track);
} }

View File

@ -73,9 +73,6 @@ export class UserController {
@OpenAPI({ description: "Update a user object (id can't be changed)." }) @OpenAPI({ description: "Update a user object (id can't be changed)." })
async put(@Param('id') id: number, @Body({ validate: true }) updateUser: UpdateUser) { async put(@Param('id') id: number, @Body({ validate: true }) updateUser: UpdateUser) {
let oldUser = await this.userRepository.findOne({ id: id }); let oldUser = await this.userRepository.findOne({ id: id });
delete oldUser.permissions;
delete oldUser.groups;
delete oldUser.actions;
if (!oldUser) { if (!oldUser) {
throw new UserNotFoundError(); throw new UserNotFoundError();
@ -84,7 +81,7 @@ export class UserController {
if (oldUser.id != updateUser.id) { if (oldUser.id != updateUser.id) {
throw new UserIdsNotMatchingError(); 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'] })); return new ResponseUser(await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] }));
} }

View File

@ -73,7 +73,7 @@ export class UserGroupController {
throw new UserGroupIdsNotMatchingError(); throw new UserGroupIdsNotMatchingError();
} }
await this.userGroupsRepository.update(oldUserGroup, userGroup); await this.userGroupsRepository.save(userGroup);
return userGroup; return userGroup;
} }

View File

@ -1,63 +1,57 @@
import { IsString } from 'class-validator'; import { IsString } from 'class-validator';
import { ForbiddenError, NotAcceptableError, NotFoundError, UnauthorizedError } from 'routing-controllers'; 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. * Error to throw when a jwt could not be parsed.
* For example: Wrong signature or expired.
*/ */
export class IllegalJWTError extends UnauthorizedError { export class IllegalJWTError extends UnauthorizedError {
@IsString() @IsString()
name = "IllegalJWTError" name = "IllegalJWTError"
@IsString() @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. * 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 { export class UserNonexistantOrRefreshtokenInvalidError extends UnauthorizedError {
@IsString() @IsString()
name = "UserNonexistantOrRefreshtokenInvalidError" name = "UserNonexistantOrRefreshtokenInvalidError"
@IsString() @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. * 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 { export class InvalidCredentialsError extends UnauthorizedError {
@IsString() @IsString()
name = "InvalidCredentialsError" name = "InvalidCredentialsError"
@IsString() @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. * 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 { export class NoPermissionError extends ForbiddenError {
@IsString() @IsString()
name = "NoPermissionError" name = "NoPermissionError"
@IsString() @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. * Error to throw when no username and no email is set.
* Because we have to identify users somehow.
*/ */
export class UsernameOrEmailNeededError extends NotAcceptableError { export class UsernameOrEmailNeededError extends NotAcceptableError {
@IsString() @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 { export class PasswordNeededError extends NotAcceptableError {
@IsString() @IsString()
name = "PasswordNeededError" name = "PasswordNeededError"
@IsString() @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 { export class UserNotFoundError extends NotFoundError {
@IsString() @IsString()
name = "UserNotFoundError" name = "UserNotFoundError"
@IsString() @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 { export class JwtNotProvidedError extends NotAcceptableError {
@IsString() @IsString()
name = "JwtNotProvidedError" name = "JwtNotProvidedError"
@IsString() @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 { export class UserNotFoundOrRefreshTokenCountInvalidError extends NotAcceptableError {
@IsString() @IsString()
name = "UserNotFoundOrRefreshTokenCountInvalidError" name = "UserNotFoundOrRefreshTokenCountInvalidError"
@IsString() @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" name = "RefreshTokenCountInvalidError"
@IsString() @IsString()
message = "refresh token count was invalid" message = "Refresh token count is invalid."
} }

View File

@ -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. * Usually occurs when a user tries to change a permission's id.
*/ */
export class PermissionIdsNotMatchingError extends NotAcceptableError { export class PermissionIdsNotMatchingError extends NotAcceptableError {
@ -21,11 +21,11 @@ export class PermissionIdsNotMatchingError extends NotAcceptableError {
name = "PermissionIdsNotMatchingError" name = "PermissionIdsNotMatchingError"
@IsString() @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 { export class PermissionNeedsPrincipalError extends NotAcceptableError {
@IsString() @IsString()

View File

@ -3,7 +3,6 @@ import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/** /**
* Error to throw when a runner couldn't be found. * 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 { export class RunnerNotFoundError extends NotFoundError {
@IsString() @IsString()
@ -16,14 +15,13 @@ export class RunnerNotFoundError extends NotFoundError {
/** /**
* Error to throw when two runners' ids don't match. * Error to throw when two runners' ids don't match.
* Usually occurs when a user tries to change a runner's id. * 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 { export class RunnerIdsNotMatchingError extends NotAcceptableError {
@IsString() @IsString()
name = "RunnerIdsNotMatchingError" name = "RunnerIdsNotMatchingError"
@IsString() @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!"
} }
/** /**

View File

@ -3,7 +3,6 @@ import { NotFoundError } from 'routing-controllers';
/** /**
* Error to throw when a runner group couldn't be found. * 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 { export class RunnerGroupNotFoundError extends NotFoundError {
@IsString() @IsString()

View File

@ -3,7 +3,6 @@ import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/** /**
* Error to throw when a runner organisation couldn't be found. * 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 { export class RunnerOrganisationNotFoundError extends NotFoundError {
@IsString() @IsString()
@ -15,39 +14,36 @@ export class RunnerOrganisationNotFoundError extends NotFoundError {
/** /**
* Error to throw when two runner organisations' ids don't match. * Error to throw when two runner organisations' ids don't match.
* Usually occurs when a user tries to change a runner's id. * Usually occurs when a user tries to change a runner organisation's id.
* Implemented this way to work with the json-schema conversion for openapi.
*/ */
export class RunnerOrganisationIdsNotMatchingError extends NotAcceptableError { export class RunnerOrganisationIdsNotMatchingError extends NotAcceptableError {
@IsString() @IsString()
name = "RunnerOrganisationIdsNotMatchingError" name = "RunnerOrganisationIdsNotMatchingError"
@IsString() @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. * 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 { export class RunnerOrganisationHasRunnersError extends NotAcceptableError {
@IsString() @IsString()
name = "RunnerOrganisationHasRunnersError" name = "RunnerOrganisationHasRunnersError"
@IsString() @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. * Error to throw when a organisation still has teams associated.
* Implemented this waysto work with the json-schema conversion for openapi.
*/ */
export class RunnerOrganisationHasTeamsError extends NotAcceptableError { export class RunnerOrganisationHasTeamsError extends NotAcceptableError {
@IsString() @IsString()
name = "RunnerOrganisationHasTeamsError" name = "RunnerOrganisationHasTeamsError"
@IsString() @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."
} }
/** /**

View File

@ -3,7 +3,6 @@ import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/** /**
* Error to throw when a runner team couldn't be found. * 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 { export class RunnerTeamNotFoundError extends NotFoundError {
@IsString() @IsString()
@ -15,32 +14,29 @@ export class RunnerTeamNotFoundError extends NotFoundError {
/** /**
* Error to throw when two runner teams' ids don't match. * Error to throw when two runner teams' ids don't match.
* Usually occurs when a user tries to change a runner's id. * Usually occurs when a user tries to change a runner team's id.
* Implemented this way to work with the json-schema conversion for openapi.
*/ */
export class RunnerTeamIdsNotMatchingError extends NotAcceptableError { export class RunnerTeamIdsNotMatchingError extends NotAcceptableError {
@IsString() @IsString()
name = "RunnerTeamIdsNotMatchingError" name = "RunnerTeamIdsNotMatchingError"
@IsString() @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. * 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 { export class RunnerTeamHasRunnersError extends NotAcceptableError {
@IsString() @IsString()
name = "RunnerTeamHasRunnersError" name = "RunnerTeamHasRunnersError"
@IsString() @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. * 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 { export class RunnerTeamNeedsParentError extends NotAcceptableError {
@IsString() @IsString()

View File

@ -1,9 +1,8 @@
import { JsonController, Param, Body, Get, Post, Put, Delete, NotFoundError, OnUndefined, NotAcceptableError } from 'routing-controllers'; import { IsString } from 'class-validator';
import { IsInt, IsNotEmpty, IsPositive, IsString } from 'class-validator'; import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/** /**
* Error to throw when a track couldn't be found. * 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 { export class TrackNotFoundError extends NotFoundError {
@IsString() @IsString()
@ -16,12 +15,11 @@ export class TrackNotFoundError extends NotFoundError {
/** /**
* Error to throw when two tracks' ids don't match. * Error to throw when two tracks' ids don't match.
* Usually occurs when a user tries to change a track's id. * 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 { export class TrackIdsNotMatchingError extends NotAcceptableError {
@IsString() @IsString()
name = "TrackIdsNotMatchingError" name = "TrackIdsNotMatchingError"
@IsString() @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"
} }

View File

@ -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 { export class UsernameOrEmailNeededError extends NotFoundError {
@IsString() @IsString()
name = "UsernameOrEmailNeededError" name = "UsernameOrEmailNeededError"
@IsString() @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" name = "UserIdsNotMatchingError"
@IsString() @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!"
} }

View File

@ -2,14 +2,14 @@ import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers'; 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 { export class GroupNameNeededError extends NotFoundError {
@IsString() @IsString()
name = "GroupNameNeededError" name = "GroupNameNeededError"
@IsString() @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" name = "UserGroupIdsNotMatchingError"
@IsString() @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!"
} }

View File

@ -4,6 +4,7 @@ import { User } from '../models/entities/User';
import SeedUsers from '../seeds/SeedUsers'; import SeedUsers from '../seeds/SeedUsers';
/** /**
* Loader for the database that creates the database connection and initializes the database tabels. * 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 () => { export default async () => {
const connection = await createConnection(); const connection = await createConnection();

View File

@ -2,7 +2,7 @@ import cookieParser from "cookie-parser";
import { Application } from "express"; import { Application } from "express";
/** /**
* Loader for express related configurations. * 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) => { export default async (app: Application) => {
app.enable('trust proxy'); app.enable('trust proxy');

View File

@ -5,6 +5,7 @@ import openapiLoader from "./openapi";
/** /**
* Index Loader that executes the other loaders in the right order. * 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) => { export default async (app: Application) => {
await databaseLoader(); await databaseLoader();

View File

@ -5,7 +5,8 @@ import { routingControllersToSpec } from "routing-controllers-openapi";
import * as swaggerUiExpress from "swagger-ui-express"; 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) => { export default async (app: Application) => {
const storage = getMetadataArgsStorage(); const storage = getMetadataArgsStorage();
@ -26,7 +27,8 @@ export default async (app: Application) => {
"AuthToken": { "AuthToken": {
"type": "http", "type": "http",
"scheme": "bearer", "scheme": "bearer",
"bearerFormat": "JWT" "bearerFormat": "JWT",
description: "A JWT based access token. Use /api/auth/login or /api/auth/refresh to get one."
} }
} }
}, },

View File

@ -1,7 +1,7 @@
import { ExpressErrorMiddlewareInterface, Middleware } from "routing-controllers"; 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" }) @Middleware({ type: "after" })
export class ErrorHandler implements ExpressErrorMiddlewareInterface { export class ErrorHandler implements ExpressErrorMiddlewareInterface {

View File

@ -1,5 +1,10 @@
import { Request, Response } from 'express'; 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 RawBodyMiddleware = (req: Request, res: Response, next: () => void) => {
const body = [] const body = []
req.on('data', chunk => { req.on('data', chunk => {
@ -8,15 +13,6 @@ const RawBodyMiddleware = (req: Request, res: Response, next: () => void) => {
req.on('end', () => { req.on('end', () => {
const rawBody = Buffer.concat(body) const rawBody = Buffer.concat(body)
req['rawBody'] = rawBody 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() next()
}) })
req.on('error', () => { req.on('error', () => {

View File

@ -1,16 +1,19 @@
import { IsNotEmpty, IsOptional, IsPostalCode, IsString } from 'class-validator'; import { IsNotEmpty, IsOptional, IsPostalCode, IsString } from 'class-validator';
import { Address } from '../entities/Address'; import { Address } from '../entities/Address';
/**
* This classed is used to create a new Address entity from a json body (post request).
*/
export class CreateAddress { export class CreateAddress {
/** /**
* The address's description. * The newaddress's description.
*/ */
@IsString() @IsString()
@IsOptional() @IsOptional()
description?: string; description?: string;
/** /**
* The address's first line. * The new address's first line.
* Containing the street and house number. * Containing the street and house number.
*/ */
@IsString() @IsString()
@ -18,7 +21,7 @@ export class CreateAddress {
address1: string; address1: string;
/** /**
* The address's second line. * The new address's second line.
* Containing optional information. * Containing optional information.
*/ */
@IsString() @IsString()
@ -26,7 +29,9 @@ export class CreateAddress {
address2?: string; 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() @IsString()
@IsNotEmpty() @IsNotEmpty()
@ -34,21 +39,21 @@ export class CreateAddress {
postalcode: string; postalcode: string;
/** /**
* The address's city. * The new address's city.
*/ */
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
city: string; city: string;
/** /**
* The address's country. * The new address's country.
*/ */
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
country: string; country: string;
/** /**
* Creates a Address object based on this. * Creates a new Address entity from this.
*/ */
public toAddress(): Address { public toAddress(): Address {
let newAddress: Address = new Address(); let newAddress: Address = new Address();

View File

@ -1,5 +1,5 @@
import * as argon2 from "argon2"; 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 { getConnectionManager } from 'typeorm';
import { InvalidCredentialsError, PasswordNeededError, UserNotFoundError } from '../../errors/AuthError'; import { InvalidCredentialsError, PasswordNeededError, UserNotFoundError } from '../../errors/AuthError';
import { UsernameOrEmailNeededError } from '../../errors/UserErrors'; import { UsernameOrEmailNeededError } from '../../errors/UserErrors';
@ -7,17 +7,41 @@ import { JwtCreator } from '../../jwtcreator';
import { User } from '../entities/User'; import { User } from '../entities/User';
import { Auth } from '../responses/ResponseAuth'; 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 { export class CreateAuth {
/**
* The username of the user that want's to login.
* Either username or email have to be provided.
*/
@IsOptional() @IsOptional()
@IsString() @IsString()
username?: string; 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() @IsOptional()
@IsEmail() @IsEmail()
@IsString() @IsString()
email?: string; 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> { public async toAuth(): Promise<Auth> {
let newAuth: Auth = new Auth(); let newAuth: Auth = new Auth();

View File

@ -5,32 +5,34 @@ import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/Addres
import { Address } from '../entities/Address'; import { Address } from '../entities/Address';
import { GroupContact } from '../entities/GroupContact'; import { GroupContact } from '../entities/GroupContact';
/**
* This classed is used to create a new Group entity from a json body (post request).
*/
export class CreateGroupContact { export class CreateGroupContact {
/** /**
* The contact's first name. * The new contact's first name.
*/ */
@IsNotEmpty() @IsNotEmpty()
@IsString() @IsString()
firstname: string; firstname: string;
/** /**
* The contact's middle name. * The new contact's middle name.
* Optional
*/ */
@IsOptional() @IsOptional()
@IsString() @IsString()
middlename?: string; middlename?: string;
/** /**
* The contact's last name. * The new contact's last name.
*/ */
@IsNotEmpty() @IsNotEmpty()
@IsString() @IsString()
lastname: string; lastname: string;
/** /**
* The contact's address. * The new contact's address.
* Optional * Must be the address's id.
*/ */
@IsInt() @IsInt()
@IsOptional() @IsOptional()
@ -38,7 +40,7 @@ export class CreateGroupContact {
/** /**
* The contact's phone number. * The contact's phone number.
* Optional * This will be validated against the configured country phone numer syntax (default: international).
*/ */
@IsOptional() @IsOptional()
@IsPhoneNumber(config.phone_validation_countrycode) @IsPhoneNumber(config.phone_validation_countrycode)
@ -46,17 +48,16 @@ export class CreateGroupContact {
/** /**
* The contact's email address. * The contact's email address.
* Optional
*/ */
@IsOptional() @IsOptional()
@IsEmail() @IsEmail()
email?: string; 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> { public async getAddress(): Promise<Address> {
if (this.address === undefined) { if (this.address === undefined || this.address === null) {
return null; return null;
} }
if (!isNaN(this.address)) { 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> { public async toGroupContact(): Promise<GroupContact> {
let contact: GroupContact = new GroupContact(); let contact: GroupContact = new GroupContact();

View File

@ -4,6 +4,9 @@ import { config } from '../../config';
import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors'; import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors';
import { Address } from '../entities/Address'; 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 { export abstract class CreateParticipant {
/** /**
* The new participant's first name. * The new participant's first name.
@ -14,7 +17,6 @@ export abstract class CreateParticipant {
/** /**
* The new participant's middle name. * The new participant's middle name.
* Optional.
*/ */
@IsString() @IsString()
@IsOptional() @IsOptional()
@ -29,7 +31,7 @@ export abstract class CreateParticipant {
/** /**
* The new participant's phone number. * The new participant's phone number.
* Optional. * This will be validated against the configured country phone numer syntax (default: international).
*/ */
@IsString() @IsString()
@IsOptional() @IsOptional()
@ -38,7 +40,6 @@ export abstract class CreateParticipant {
/** /**
* The new participant's e-mail address. * The new participant's e-mail address.
* Optional.
*/ */
@IsString() @IsString()
@IsOptional() @IsOptional()
@ -48,17 +49,16 @@ export abstract class CreateParticipant {
/** /**
* The new participant's address. * The new participant's address.
* Must be of type number (address id). * Must be of type number (address id).
* Optional.
*/ */
@IsInt() @IsInt()
@IsOptional() @IsOptional()
address?: number; 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> { public async getAddress(): Promise<Address> {
if (this.address === undefined) { if (this.address === undefined || this.address === null) {
return null; return null;
} }
if (!isNaN(this.address)) { if (!isNaN(this.address)) {

View File

@ -11,33 +11,33 @@ import { PermissionAction } from '../enums/PermissionAction';
import { PermissionTarget } from '../enums/PermissionTargets'; 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 { export class CreatePermission {
/** /**
* The permissions's principal's id. * The new permissions's principal's id.
*/ */
@IsInt() @IsInt()
@IsNotEmpty() @IsNotEmpty()
principal: number; principal: number;
/** /**
* The permissions's target. * The new permissions's target.
*/ */
@IsNotEmpty() @IsNotEmpty()
@IsEnum(PermissionTarget) @IsEnum(PermissionTarget)
target: PermissionTarget; target: PermissionTarget;
/** /**
* The permissions's action. * The new permissions's action.
*/ */
@IsNotEmpty() @IsNotEmpty()
@IsEnum(PermissionAction) @IsEnum(PermissionAction)
action: PermissionAction; action: PermissionAction;
/** /**
* Converts a Permission object based on this. * Creates a new Permission entity from this.
*/ */
public async toPermission(): Promise<Permission> { public async toPermission(): Promise<Permission> {
let newPermission: Permission = new Permission(); let newPermission: Permission = new Permission();
@ -49,6 +49,9 @@ export class CreatePermission {
return newPermission; return newPermission;
} }
/**
* Gets the new permission's principal by it's id.
*/
public async getPrincipal(): Promise<Principal> { public async getPrincipal(): Promise<Principal> {
let principal = await getConnectionManager().get().getRepository(Principal).findOne({ id: this.principal }) let principal = await getConnectionManager().get().getRepository(Principal).findOne({ id: this.principal })
if (!principal) { throw new PrincipalNotFoundError(); } if (!principal) { throw new PrincipalNotFoundError(); }

View File

@ -7,6 +7,9 @@ import { Runner } from '../entities/Runner';
import { RunnerGroup } from '../entities/RunnerGroup'; import { RunnerGroup } from '../entities/RunnerGroup';
import { CreateParticipant } from './CreateParticipant'; 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 { export class CreateRunner extends CreateParticipant {
/** /**
@ -16,7 +19,7 @@ export class CreateRunner extends CreateParticipant {
group: number; group: number;
/** /**
* Creates a Runner entity from this. * Creates a new Runner entity from this.
*/ */
public async toRunner(): Promise<Runner> { public async toRunner(): Promise<Runner> {
let newRunner: Runner = new 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> { public async getGroup(): Promise<RunnerGroup> {
if (this.group === undefined) { if (this.group === undefined || this.group === null) {
throw new RunnerTeamNeedsParentError(); throw new RunnerTeamNeedsParentError();
} }
if (!isNaN(this.group)) { if (!isNaN(this.group)) {

View File

@ -3,16 +3,19 @@ import { getConnectionManager } from 'typeorm';
import { GroupContactNotFoundError, GroupContactWrongTypeError } from '../../errors/GroupContactErrors'; import { GroupContactNotFoundError, GroupContactWrongTypeError } from '../../errors/GroupContactErrors';
import { GroupContact } from '../entities/GroupContact'; 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 { export abstract class CreateRunnerGroup {
/** /**
* The group's name. * The new group's name.
*/ */
@IsNotEmpty() @IsNotEmpty()
@IsString() @IsString()
name: string; name: string;
/** /**
* The group's contact. * The new group's contact.
* Optional * Optional
*/ */
@IsInt() @IsInt()
@ -20,7 +23,7 @@ export abstract class CreateRunnerGroup {
contact?: number; 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> { public async getContact(): Promise<GroupContact> {
if (this.contact === undefined || this.contact === null) { if (this.contact === undefined || this.contact === null) {

View File

@ -5,21 +5,23 @@ import { Address } from '../entities/Address';
import { RunnerOrganisation } from '../entities/RunnerOrganisation'; import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { CreateRunnerGroup } from './CreateRunnerGroup'; 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 { export class CreateRunnerOrganisation extends CreateRunnerGroup {
/** /**
* The new organisation's address. * The new organisation's address.
* Must be of type number (address id). * Must be of type number (address id).
* Optional.
*/ */
@IsInt() @IsInt()
@IsOptional() @IsOptional()
address?: number; 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> { public async getAddress(): Promise<Address> {
if (this.address === undefined) { if (this.address === undefined || this.address === null) {
return null; return null;
} }
if (!isNaN(this.address)) { 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> { public async toRunnerOrganisation(): Promise<RunnerOrganisation> {
let newRunnerOrganisation: RunnerOrganisation = new RunnerOrganisation(); let newRunnerOrganisation: RunnerOrganisation = new RunnerOrganisation();

View File

@ -6,17 +6,23 @@ import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { RunnerTeam } from '../entities/RunnerTeam'; import { RunnerTeam } from '../entities/RunnerTeam';
import { CreateRunnerGroup } from './CreateRunnerGroup'; 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 { export class CreateRunnerTeam extends CreateRunnerGroup {
/** /**
* The team's parent group (organisation). * The new team's parent group (organisation).
*/ */
@IsInt() @IsInt()
@IsNotEmpty() @IsNotEmpty()
parentGroup: number; parentGroup: number;
/**
* Gets the new team's parent org based on it's id.
*/
public async getParent(): Promise<RunnerOrganisation> { public async getParent(): Promise<RunnerOrganisation> {
if (this.parentGroup === undefined) { if (this.parentGroup === undefined || this.parentGroup === null) {
throw new RunnerTeamNeedsParentError(); throw new RunnerTeamNeedsParentError();
} }
if (!isNaN(this.parentGroup)) { 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> { public async toRunnerTeam(): Promise<RunnerTeam> {
let newRunnerTeam: RunnerTeam = new RunnerTeam(); let newRunnerTeam: RunnerTeam = new RunnerTeam();

View File

@ -1,23 +1,26 @@
import { IsInt, IsNotEmpty, IsPositive, IsString } from 'class-validator'; import { IsInt, IsNotEmpty, IsPositive, IsString } from 'class-validator';
import { Track } from '../entities/Track'; import { Track } from '../entities/Track';
/**
* This classed is used to create a new Track entity from a json body (post request).
*/
export class CreateTrack { export class CreateTrack {
/** /**
* The track's name. * The new track's name.
*/ */
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
name: string; 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() @IsInt()
@IsPositive() @IsPositive()
distance: number; distance: number;
/** /**
* Converts a Track object based on this. * Creates a new Track entity from this.
*/ */
public toTrack(): Track { public toTrack(): Track {
let newTrack: Track = new Track(); let newTrack: Track = new Track();

View File

@ -8,6 +8,9 @@ import { UserGroupNotFoundError } from '../../errors/UserGroupErrors';
import { User } from '../entities/User'; import { User } from '../entities/User';
import { UserGroup } from '../entities/UserGroup'; import { UserGroup } from '../entities/UserGroup';
/**
* This classed is used to create a new User entity from a json body (post request).
*/
export class CreateUser { export class CreateUser {
/** /**
* The new user's first name. * The new user's first name.
@ -17,7 +20,6 @@ export class CreateUser {
/** /**
* The new user's middle name. * The new user's middle name.
* Optinal.
*/ */
@IsString() @IsString()
@IsOptional() @IsOptional()
@ -48,7 +50,7 @@ export class CreateUser {
/** /**
* The new user's phone number. * 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) @IsPhoneNumber(config.phone_validation_countrycode)
@IsOptional() @IsOptional()
@ -64,7 +66,6 @@ export class CreateUser {
/** /**
* The new user's groups' id(s). * The new user's groups' id(s).
* You can provide either one groupId or an array of groupIDs. * You can provide either one groupId or an array of groupIDs.
* Optional.
*/ */
@IsOptional() @IsOptional()
groups?: number[] | number groups?: number[] | number
@ -72,7 +73,7 @@ export class CreateUser {
//TODO: ProfilePics //TODO: ProfilePics
/** /**
* Converts this to a User Entity. * Converts this to a User entity.
*/ */
public async toUser(): Promise<User> { public async toUser(): Promise<User> {
let newUser: User = new User(); let newUser: User = new User();

View File

@ -1,6 +1,9 @@
import { IsOptional, IsString } from 'class-validator'; import { IsOptional, IsString } from 'class-validator';
import { UserGroup } from '../entities/UserGroup'; import { UserGroup } from '../entities/UserGroup';
/**
* This classed is used to create a new UserGroup entity from a json body (post request).
*/
export class CreateUserGroup { export class CreateUserGroup {
/** /**
* The new group's name. * The new group's name.
@ -17,7 +20,7 @@ export class CreateUserGroup {
description?: string; description?: string;
/** /**
* Converts this to a UserGroup entity. * Creates a new UserGroup entity from this.
*/ */
public async toUserGroup(): Promise<UserGroup> { public async toUserGroup(): Promise<UserGroup> {
let newUserGroup: UserGroup = new UserGroup(); let newUserGroup: UserGroup = new UserGroup();

View File

@ -6,11 +6,23 @@ import { IllegalJWTError, JwtNotProvidedError, RefreshTokenCountInvalidError, Us
import { User } from '../entities/User'; import { User } from '../entities/User';
import { Logout } from '../responses/ResponseLogout'; 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 { export class HandleLogout {
/**
* A stringyfied jwt access token.
* Will get checked for validity.
*/
@IsString() @IsString()
@IsOptional() @IsOptional()
token?: string; 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> { public async logout(): Promise<Logout> {
let logout: Logout = new Logout(); let logout: Logout = new Logout();
if (!this.token || this.token === undefined) { if (!this.token || this.token === undefined) {

View File

@ -7,6 +7,10 @@ import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { RunnerTeam } from '../entities/RunnerTeam'; import { RunnerTeam } from '../entities/RunnerTeam';
import { CreateRunner } from './CreateRunner'; 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 { export class ImportRunner {
/** /**
@ -18,7 +22,6 @@ export class ImportRunner {
/** /**
* The new runner's middle name. * The new runner's middle name.
* Optional.
*/ */
@IsString() @IsString()
@IsOptional() @IsOptional()
@ -32,18 +35,26 @@ export class ImportRunner {
lastname: string; 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() @IsString()
@IsOptional() @IsOptional()
team?: string; team?: string;
/**
* Just an alias for team, because this is usually only used for importing data from schools.
*/
@IsOptional() @IsOptional()
@IsString() @IsString()
public set class(value: string) { public set class(value: string) {
this.team = value; 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> { public async toCreateRunner(groupID: number): Promise<CreateRunner> {
let newRunner: CreateRunner = new CreateRunner(); let newRunner: CreateRunner = new CreateRunner();
@ -55,6 +66,10 @@ export class ImportRunner {
return newRunner; 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> { public async getGroup(groupID: number): Promise<RunnerGroup> {
if (this.team === undefined && groupID === undefined) { if (this.team === undefined && groupID === undefined) {
throw new RunnerGroupNeededError(); throw new RunnerGroupNeededError();

View File

@ -7,11 +7,23 @@ import { JwtCreator } from "../../jwtcreator";
import { User } from '../entities/User'; import { User } from '../entities/User';
import { Auth } from '../responses/ResponseAuth'; 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 { export class RefreshAuth {
/**
* A stringyfied jwt refresh token.
* Will get checked for validity.
*/
@IsString() @IsString()
@IsOptional() @IsOptional()
token?: string; token?: string;
/**
* Creates a new auth object based on this.
*/
public async toAuth(): Promise<Auth> { public async toAuth(): Promise<Auth> {
let newAuth: Auth = new Auth(); let newAuth: Auth = new Auth();
if (!this.token || this.token === undefined) { if (!this.token || this.token === undefined) {

View File

@ -7,16 +7,21 @@ import { Principal } from '../entities/Principal';
import { PermissionAction } from '../enums/PermissionAction'; import { PermissionAction } from '../enums/PermissionAction';
import { PermissionTarget } from '../enums/PermissionTargets'; import { PermissionTarget } from '../enums/PermissionTargets';
/**
* This class is used to update a Permission entity (via put request).
*/
export class UpdatePermission { 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() @IsInt()
id: number; 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() @IsObject()
@IsNotEmpty() @IsNotEmpty()
@ -35,23 +40,21 @@ export class UpdatePermission {
action: PermissionAction; action: PermissionAction;
/** /**
* Converts a Permission object based on this. * Updates a provided Permission entity based on this.
*/ */
public async toPermission(): Promise<Permission> { public async updatePermission(permission: Permission): Promise<Permission> {
let newPermission: Permission = new Permission(); permission.principal = await this.getPrincipal();
permission.target = this.target;
permission.action = this.action;
newPermission.principal = await this.getPrincipal(); return permission;
newPermission.target = this.target;
newPermission.action = this.action;
return newPermission;
} }
/** /**
* 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> { public async getPrincipal(): Promise<Principal> {
if (this.principal === undefined) { if (this.principal === undefined || this.principal === null) {
throw new PermissionNeedsPrincipalError(); throw new PermissionNeedsPrincipalError();
} }
if (!isNaN(this.principal.id)) { if (!isNaN(this.principal.id)) {

View File

@ -7,43 +7,45 @@ import { Runner } from '../entities/Runner';
import { RunnerGroup } from '../entities/RunnerGroup'; import { RunnerGroup } from '../entities/RunnerGroup';
import { CreateParticipant } from './CreateParticipant'; import { CreateParticipant } from './CreateParticipant';
/**
* This class is used to update a Runner entity (via put request).
*/
export class UpdateRunner extends CreateParticipant { export class UpdateRunner extends CreateParticipant {
/** /**
* The updated runner's id. * 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() @IsInt()
id: number; id: number;
/** /**
* The updated runner's new team/org. * The updated runner's new team/org.
* Just has to contain the group's id -everything else won't be checked or changed.
*/ */
@IsObject() @IsObject()
group: RunnerGroup; group: RunnerGroup;
/** /**
* Creates a Runner entity from this. * Updates a provided Runner entity based on this.
*/ */
public async toRunner(): Promise<Runner> { public async updateRunner(runner: Runner): Promise<Runner> {
let newRunner: Runner = new 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; return runner;
newRunner.firstname = this.firstname;
newRunner.middlename = this.middlename;
newRunner.lastname = this.lastname;
newRunner.phone = this.phone;
newRunner.email = this.email;
newRunner.group = await this.getGroup();
newRunner.address = await this.getAddress();
return newRunner;
} }
/** /**
* 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> { public async getGroup(): Promise<RunnerGroup> {
if (this.group === undefined) { if (this.group === undefined || this.group === null) {
throw new RunnerTeamNeedsParentError(); throw new RunnerTeamNeedsParentError();
} }
if (!isNaN(this.group.id)) { if (!isNaN(this.group.id)) {

View 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;
}
}

View File

@ -6,23 +6,31 @@ import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { RunnerTeam } from '../entities/RunnerTeam'; import { RunnerTeam } from '../entities/RunnerTeam';
import { CreateRunnerGroup } from './CreateRunnerGroup'; import { CreateRunnerGroup } from './CreateRunnerGroup';
/**
* This class is used to update a RunnerTeam entity (via put request).
*/
export class UpdateRunnerTeam extends CreateRunnerGroup { export class UpdateRunnerTeam extends CreateRunnerGroup {
/** /**
* The updated team's id. * 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() @IsInt()
id: number; 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() @IsObject()
@IsNotEmpty() @IsNotEmpty()
parentGroup: RunnerOrganisation; parentGroup: RunnerOrganisation;
/**
* Loads the updated teams's parentGroup based on it's id.
*/
public async getParent(): Promise<RunnerOrganisation> { public async getParent(): Promise<RunnerOrganisation> {
if (this.parentGroup === undefined) { if (this.parentGroup === undefined || this.parentGroup === null) {
throw new RunnerTeamNeedsParentError(); throw new RunnerTeamNeedsParentError();
} }
if (!isNaN(this.parentGroup.id)) { 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> { public async updateRunnerTeam(team: RunnerTeam): Promise<RunnerTeam> {
let newRunnerTeam: RunnerTeam = new RunnerTeam();
newRunnerTeam.id = this.id; team.name = this.name;
newRunnerTeam.name = this.name; team.parentGroup = await this.getParent();
newRunnerTeam.parentGroup = await this.getParent(); team.contact = await this.getContact()
newRunnerTeam.contact = await this.getContact()
return newRunnerTeam; return team;
} }
} }

View File

@ -3,14 +3,18 @@ import { IsBoolean, IsEmail, IsInt, IsOptional, IsPhoneNumber, IsString } from '
import { getConnectionManager } from 'typeorm'; import { getConnectionManager } from 'typeorm';
import { config } from '../../config'; import { config } from '../../config';
import { UsernameOrEmailNeededError } from '../../errors/AuthError'; import { UsernameOrEmailNeededError } from '../../errors/AuthError';
import { RunnerGroupNotFoundError } from '../../errors/RunnerGroupErrors'; import { UserGroupNotFoundError } from '../../errors/UserGroupErrors';
import { User } from '../entities/User'; import { User } from '../entities/User';
import { UserGroup } from '../entities/UserGroup'; import { UserGroup } from '../entities/UserGroup';
/**
* This class is used to update a User entity (via put request).
*/
export class UpdateUser { 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() @IsInt()
id: number; id: number;
@ -23,7 +27,6 @@ export class UpdateUser {
/** /**
* The updated user's middle name. * The updated user's middle name.
* Optinal.
*/ */
@IsString() @IsString()
@IsOptional() @IsOptional()
@ -54,14 +57,16 @@ export class UpdateUser {
/** /**
* The updated user's phone number. * 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) @IsPhoneNumber(config.phone_validation_countrycode)
@IsOptional() @IsOptional()
phone?: string; 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 :) * This will of course not be saved in plaintext :)
*/ */
@IsString() @IsString()
@ -75,20 +80,19 @@ export class UpdateUser {
enabled: boolean = true; enabled: boolean = true;
/** /**
* The new user's groups' id(s). * The updated user's groups.
* You can provide either one groupId or an array of groupIDs. * This just has to contain the group's id - everything else won't be changed.
* Optional.
*/ */
@IsOptional() @IsOptional()
groups?: UserGroup[] 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.email = this.email;
user.username = this.username; 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(); throw new UsernameOrEmailNeededError();
} }
if (this.password) { if (this.password) {
@ -107,6 +111,9 @@ export class UpdateUser {
return user; return user;
} }
/**
* Loads the updated user's groups based on their ids.
*/
public async getGroups() { public async getGroups() {
if (!this.groups) { return null; } if (!this.groups) { return null; }
let groups = new Array<UserGroup>(); let groups = new Array<UserGroup>();
@ -115,7 +122,7 @@ export class UpdateUser {
} }
for (let group of this.groups) { for (let group of this.groups) {
let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group.id }); let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group.id });
if (!found) { throw new RunnerGroupNotFoundError(); } if (!found) { throw new UserGroupNotFoundError(); }
groups.push(found); groups.push(found);
} }
return groups; return groups;

View File

@ -10,7 +10,8 @@ import { Participant } from "./Participant";
import { RunnerOrganisation } from "./RunnerOrganisation"; 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() @Entity()
export class Address { export class Address {
@ -23,6 +24,7 @@ export class Address {
/** /**
* The address's description. * The address's description.
* Optional and mostly for UX.
*/ */
@Column({ nullable: true }) @Column({ nullable: true })
@IsString() @IsString()
@ -49,6 +51,8 @@ export class Address {
/** /**
* The address's postal code. * The address's postal code.
* This will get checked against the postal code syntax for the configured country.
* TODO: Implement the config option.
*/ */
@Column() @Column()
@IsString() @IsString()

View File

@ -4,19 +4,21 @@ import { Donation } from "./Donation";
import { Runner } from "./Runner"; import { Runner } from "./Runner";
/** /**
* Defines a distance based donation. * Defines the DistanceDonation entity.
* Here people donate a certain amout per kilometer * For distanceDonations a donor pledges to donate a certain amount for each kilometer ran by a runner.
*/ */
@ChildEntity() @ChildEntity()
export class DistanceDonation extends Donation { export class DistanceDonation extends Donation {
/** /**
* The runner associated. * The donation's associated runner.
* Used as the source of the donation's distance.
*/ */
@IsNotEmpty() @IsNotEmpty()
@ManyToOne(() => Runner, runner => runner.distanceDonations) @ManyToOne(() => Runner, runner => runner.distanceDonations)
runner: Runner; runner: Runner;
/** /**
* The donation's amount donated per distance.
* The amount the donor set to be donated per kilometer that the runner ran. * The amount the donor set to be donated per kilometer that the runner ran.
*/ */
@Column() @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 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 { public get amount(): number {
let calculatedAmount = -1; let calculatedAmount = -1;
try { try {
calculatedAmount = this.amountPerDistance * this.runner.distance; calculatedAmount = this.amountPerDistance * (this.runner.distance / 1000);
} catch (error) { } catch (error) {
throw error; throw error;
} }

View File

@ -6,7 +6,9 @@ import { Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typ
import { Participant } from "./Participant"; 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() @Entity()
@TableInheritance({ column: { name: "type", type: "varchar" } }) @TableInheritance({ column: { name: "type", type: "varchar" } })

View File

@ -3,13 +3,13 @@ import { ChildEntity, Column } from "typeorm";
import { Participant } from "./Participant"; import { Participant } from "./Participant";
/** /**
* Defines a donor. * Defines the Donor entity.
*/ */
@ChildEntity() @ChildEntity()
export class Donor extends Participant { export class Donor extends Participant {
/** /**
* Does this donor need a receipt?. * Does this donor need a receipt?
* Default: false * Will later be used to automaticly generate donation receipts.
*/ */
@Column() @Column()
@IsBoolean() @IsBoolean()

View File

@ -3,7 +3,8 @@ import { ChildEntity, Column } from "typeorm";
import { Donation } from "./Donation"; 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() @ChildEntity()
export class FixedDonation extends Donation { export class FixedDonation extends Donation {

View File

@ -13,13 +13,14 @@ import { Address } from "./Address";
import { RunnerGroup } from "./RunnerGroup"; 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() @Entity()
export class GroupContact { export class GroupContact {
/** /**
* Autogenerated unique id (primary key). * Autogenerated unique id (primary key).
*/ */
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
@IsInt() @IsInt()
id: number; id: number;
@ -34,7 +35,6 @@ export class GroupContact {
/** /**
* The contact's middle name. * The contact's middle name.
* Optional
*/ */
@Column({ nullable: true }) @Column({ nullable: true })
@IsOptional() @IsOptional()
@ -51,7 +51,7 @@ export class GroupContact {
/** /**
* The contact's address. * The contact's address.
* Optional * This is a address object to prevent any formatting differences.
*/ */
@IsOptional() @IsOptional()
@ManyToOne(() => Address, address => address.participants, { nullable: true }) @ManyToOne(() => Address, address => address.participants, { nullable: true })
@ -59,7 +59,7 @@ export class GroupContact {
/** /**
* The contact's phone number. * The contact's phone number.
* Optional * This will be validated against the configured country phone numer syntax (default: international).
*/ */
@Column({ nullable: true }) @Column({ nullable: true })
@IsOptional() @IsOptional()
@ -68,7 +68,7 @@ export class GroupContact {
/** /**
* The contact's email address. * The contact's email address.
* Optional * Could later be used to automaticly send mails concerning the contact's associated groups.
*/ */
@Column({ nullable: true }) @Column({ nullable: true })
@IsOptional() @IsOptional()

View File

@ -13,7 +13,8 @@ import { Address } from "./Address";
import { Donation } from "./Donation"; import { Donation } from "./Donation";
/** /**
* Defines the participant interface. * Defines the Participant entity.
* Participans can donate and therefor be associated with donation entities.
*/ */
@Entity() @Entity()
@TableInheritance({ column: { name: "type", type: "varchar" } }) @TableInheritance({ column: { name: "type", type: "varchar" } })
@ -35,7 +36,6 @@ export abstract class Participant {
/** /**
* The participant's middle name. * The participant's middle name.
* Optional
*/ */
@Column({ nullable: true }) @Column({ nullable: true })
@IsOptional() @IsOptional()
@ -52,14 +52,14 @@ export abstract class Participant {
/** /**
* The participant's address. * The participant's address.
* Optional * This is a address object to prevent any formatting differences.
*/ */
@ManyToOne(() => Address, address => address.participants, { nullable: true }) @ManyToOne(() => Address, address => address.participants, { nullable: true })
address?: Address; address?: Address;
/** /**
* The participant's phone number. * The participant's phone number.
* Optional * This will be validated against the configured country phone numer syntax (default: international).
*/ */
@Column({ nullable: true }) @Column({ nullable: true })
@IsOptional() @IsOptional()
@ -68,7 +68,7 @@ export abstract class Participant {
/** /**
* The participant's email address. * The participant's email address.
* Optional * Can be used to contact the participant.
*/ */
@Column({ nullable: true }) @Column({ nullable: true })
@IsOptional() @IsOptional()
@ -77,6 +77,7 @@ export abstract class Participant {
/** /**
* Used to link the participant as the donor of a donation. * 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 }) @OneToMany(() => Donation, donation => donation.donor, { nullable: true })
donations: Donation[]; donations: Donation[];

View File

@ -8,7 +8,9 @@ import { PermissionAction } from '../enums/PermissionAction';
import { PermissionTarget } from '../enums/PermissionTargets'; import { PermissionTarget } from '../enums/PermissionTargets';
import { Principal } from './Principal'; 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() @Entity()
export class Permission { export class Permission {
@ -20,13 +22,14 @@ export class Permission {
id: number; id: number;
/** /**
* The permissions principal * The permission's principal.
*/ */
@ManyToOne(() => Principal, principal => principal.permissions) @ManyToOne(() => Principal, principal => principal.permissions)
principal: Principal; 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' }) @Column({ type: 'varchar' })
@IsNotEmpty() @IsNotEmpty()
@ -34,14 +37,16 @@ export class Permission {
target: PermissionTarget; 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' }) @Column({ type: 'varchar' })
@IsEnum(PermissionAction) @IsEnum(PermissionAction)
action: 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 { public toString(): string {
return this.target + ":" + this.action; return this.target + ":" + this.action;

View File

@ -4,23 +4,27 @@ import { ResponsePrincipal } from '../responses/ResponsePrincipal';
import { Permission } from './Permission'; 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() @Entity()
@TableInheritance({ column: { name: "type", type: "varchar" } }) @TableInheritance({ column: { name: "type", type: "varchar" } })
export abstract class Principal { export abstract class Principal {
/** /**
* autogenerated unique id (primary key). * Autogenerated unique id (primary key).
*/ */
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
@IsInt() @IsInt()
id: number; id: number;
/** /**
* permissions * The participant's permissions.
*/ */
@OneToMany(() => Permission, permission => permission.principal, { nullable: true }) @OneToMany(() => Permission, permission => permission.principal, { nullable: true })
permissions: Permission[]; permissions: Permission[];
/**
* Turns this entity into it's response class.
*/
public abstract toResponse(): ResponsePrincipal; public abstract toResponse(): ResponsePrincipal;
} }

View File

@ -7,44 +7,52 @@ import { RunnerGroup } from "./RunnerGroup";
import { Scan } from "./Scan"; 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() @ChildEntity()
export class Runner extends Participant { export class Runner extends Participant {
/** /**
* The runner's associated group. * The runner's associated group.
* Can be a runner team or organisation.
*/ */
@IsNotEmpty() @IsNotEmpty()
@ManyToOne(() => RunnerGroup, group => group.runners, { nullable: false }) @ManyToOne(() => RunnerGroup, group => group.runners, { nullable: false })
group: RunnerGroup; 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 }) @OneToMany(() => DistanceDonation, distanceDonation => distanceDonation.runner, { nullable: true })
distanceDonations: DistanceDonation[]; 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 }) @OneToMany(() => RunnerCard, card => card.runner, { nullable: true })
cards: RunnerCard[]; 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 }) @OneToMany(() => Scan, scan => scan.runner, { nullable: true })
scans: Scan[]; scans: Scan[];
/** /**
* Returns all valid scans associated with this runner. * Returns all valid scans associated with this runner.
* This is implemented here to avoid duplicate code in other files.
*/ */
public get validScans(): Scan[] { public get validScans(): Scan[] {
return this.scans.filter(scan => { scan.valid === true }); 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() @IsInt()
public get distance(): number { public get distance(): number {

View File

@ -11,7 +11,9 @@ import { Runner } from "./Runner";
import { TrackScan } from "./TrackScan"; 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() @Entity()
export class RunnerCard { export class RunnerCard {
@ -23,7 +25,8 @@ export class RunnerCard {
id: number; 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() @IsOptional()
@ManyToOne(() => Runner, runner => runner.cards, { nullable: true }) @ManyToOne(() => Runner, runner => runner.cards, { nullable: true })
@ -32,7 +35,7 @@ export class RunnerCard {
/** /**
* The card's code. * The card's code.
* This has to be able to being converted to something barcode compatible. * This has to be able to being converted to something barcode compatible.
* could theoretically be autogenerated * Will get automaticlly generated (not implemented yet).
*/ */
@Column() @Column()
@IsEAN() @IsEAN()
@ -49,7 +52,8 @@ export class RunnerCard {
enabled: boolean = true; 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 }) @OneToMany(() => TrackScan, scan => scan.track, { nullable: true })
scans: TrackScan[]; scans: TrackScan[];

View File

@ -9,7 +9,8 @@ import { GroupContact } from "./GroupContact";
import { Runner } from "./Runner"; import { Runner } from "./Runner";
/** /**
* Defines the runnerGroup interface. * Defines the RunnerGroup entity.
* This is used to group runners together (as the name suggests).
*/ */
@Entity() @Entity()
@TableInheritance({ column: { name: "type", type: "varchar" } }) @TableInheritance({ column: { name: "type", type: "varchar" } })
@ -31,13 +32,14 @@ export abstract class RunnerGroup {
/** /**
* The group's contact. * The group's contact.
* Optional * This is mostly a feature for the group managers and public relations.
*/ */
@IsOptional() @IsOptional()
@ManyToOne(() => GroupContact, contact => contact.groups, { nullable: true }) @ManyToOne(() => GroupContact, contact => contact.groups, { nullable: true })
contact?: GroupContact; contact?: GroupContact;
/** /**
* The group's associated runners.
* Used to link runners to a runner group. * Used to link runners to a runner group.
*/ */
@OneToMany(() => Runner, runner => runner.group, { nullable: true }) @OneToMany(() => Runner, runner => runner.group, { nullable: true })

View File

@ -5,22 +5,23 @@ import { RunnerGroup } from "./RunnerGroup";
import { RunnerTeam } from "./RunnerTeam"; 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() @ChildEntity()
export class RunnerOrganisation extends RunnerGroup { export class RunnerOrganisation extends RunnerGroup {
/** /**
* The organisations's address. * The organisations's address.
* Optional
*/ */
@IsOptional() @IsOptional()
@ManyToOne(() => Address, address => address.groups, { nullable: true }) @ManyToOne(() => Address, address => address.groups, { nullable: true })
address?: Address; 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 }) @OneToMany(() => RunnerTeam, team => team.parentGroup, { nullable: true })
teams: RunnerTeam[]; teams: RunnerTeam[];
} }

View File

@ -4,14 +4,15 @@ import { RunnerGroup } from "./RunnerGroup";
import { RunnerOrganisation } from "./RunnerOrganisation"; 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() @ChildEntity()
export class RunnerTeam extends RunnerGroup { export class RunnerTeam extends RunnerGroup {
/** /**
* The team's parent group. * The team's parent group.
* Optional * Every team has to be part of a runnerOrganisation - this get's checked on creation and update.
*/ */
@IsNotEmpty() @IsNotEmpty()
@ManyToOne(() => RunnerOrganisation, org => org.teams, { nullable: true }) @ManyToOne(() => RunnerOrganisation, org => org.teams, { nullable: true })

View File

@ -9,7 +9,8 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } f
import { Runner } from "./Runner"; 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() @Entity()
@TableInheritance({ column: { name: "type", type: "varchar" } }) @TableInheritance({ column: { name: "type", type: "varchar" } })
@ -22,7 +23,8 @@ export abstract class Scan {
id: number; id: number;
/** /**
* The associated runner. * The scan's associated runner.
* This is important to link ran distances to runners.
*/ */
@IsNotEmpty() @IsNotEmpty()
@ManyToOne(() => Runner, runner => runner.scans, { nullable: false }) @ManyToOne(() => Runner, runner => runner.scans, { nullable: false })
@ -30,15 +32,17 @@ export abstract class Scan {
/** /**
* The scan's distance in meters. * The scan's distance in meters.
* Can be set manually or derived from another object.
*/ */
@IsInt() @IsInt()
@IsPositive() @IsPositive()
abstract distance: number; abstract distance: number;
/** /**
* Is the scan valid (for fraud reasons). * Is the scan valid (for fraud reasons).
* Default: true * The determination of validity will work differently for every child class.
*/ * Default: true
*/
@Column() @Column()
@IsBoolean() @IsBoolean()
valid: boolean = true; valid: boolean = true;

View File

@ -10,7 +10,8 @@ import { Track } from "./Track";
import { TrackScan } from "./TrackScan"; 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() @Entity()
export class ScanStation { export class ScanStation {
@ -23,6 +24,7 @@ export class ScanStation {
/** /**
* The station's description. * The station's description.
* Mostly for better UX when traceing back stuff.
*/ */
@Column({ nullable: true }) @Column({ nullable: true })
@IsOptional() @IsOptional()
@ -31,6 +33,7 @@ export class ScanStation {
/** /**
* The track this station is associated with. * The track this station is associated with.
* All scans created by this station will also be associated with this track.
*/ */
@IsNotEmpty() @IsNotEmpty()
@ManyToOne(() => Track, track => track.stations, { nullable: false }) @ManyToOne(() => Track, track => track.stations, { nullable: false })
@ -38,6 +41,7 @@ export class ScanStation {
/** /**
* The station's api key. * The station's api key.
* This is used to authorize a station against the api (not implemented yet).
*/ */
@Column() @Column()
@IsNotEmpty() @IsNotEmpty()
@ -45,7 +49,7 @@ export class ScanStation {
key: string; key: string;
/** /**
* Is the station enabled (for fraud reasons)? * Is the station enabled (for fraud and setup reasons)?
* Default: true * Default: true
*/ */
@Column() @Column()

View File

@ -1,7 +1,6 @@
import { import {
IsInt, IsInt,
IsNotEmpty, IsNotEmpty,
IsPositive, IsPositive,
IsString IsString
} from "class-validator"; } from "class-validator";
@ -10,7 +9,7 @@ import { ScanStation } from "./ScanStation";
import { TrackScan } from "./TrackScan"; import { TrackScan } from "./TrackScan";
/** /**
* Defines a track of given length. * Defines the Track entity.
*/ */
@Entity() @Entity()
export class Track { export class Track {
@ -23,6 +22,7 @@ export class Track {
/** /**
* The track's name. * The track's name.
* Mainly here for UX.
*/ */
@Column() @Column()
@IsString() @IsString()
@ -31,6 +31,7 @@ export class Track {
/** /**
* The track's length/distance in meters. * The track's length/distance in meters.
* Will be used to calculate runner's ran distances.
*/ */
@Column() @Column()
@IsInt() @IsInt()
@ -38,13 +39,15 @@ export class Track {
distance: number; 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 }) @OneToMany(() => ScanStation, station => station.track, { nullable: true })
stations: ScanStation[]; stations: ScanStation[];
/** /**
* Used to link track scans to a track. * 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 }) @OneToMany(() => TrackScan, scan => scan.track, { nullable: true })
scans: TrackScan[]; scans: TrackScan[];

View File

@ -12,26 +12,30 @@ import { ScanStation } from "./ScanStation";
import { Track } from "./Track"; import { Track } from "./Track";
/** /**
* Defines the scan interface. * Defines the TrackScan entity.
* A track scan usaually get's generated by a scan station.
*/ */
@ChildEntity() @ChildEntity()
export class TrackScan extends Scan { export class TrackScan extends Scan {
/** /**
* The associated track. * The scan's associated track.
* This is used to determine the scan's distance.
*/ */
@IsNotEmpty() @IsNotEmpty()
@ManyToOne(() => Track, track => track.scans, { nullable: true }) @ManyToOne(() => Track, track => track.scans, { nullable: true })
track: Track; track: Track;
/** /**
* The associated card. * The runnerCard associated with the scan.
* This get's saved for documentation and management purposes.
*/ */
@IsNotEmpty() @IsNotEmpty()
@ManyToOne(() => RunnerCard, card => card.scans, { nullable: true }) @ManyToOne(() => RunnerCard, card => card.scans, { nullable: true })
card: RunnerCard; card: RunnerCard;
/** /**
* The scanning station. * The scanning station that created the scan.
* Mainly used for logging and traceing back scans (or errors)
*/ */
@IsNotEmpty() @IsNotEmpty()
@ManyToOne(() => ScanStation, station => station.scans, { nullable: true }) @ManyToOne(() => ScanStation, station => station.scans, { nullable: true })
@ -39,6 +43,7 @@ export class TrackScan extends Scan {
/** /**
* The scan's distance in meters. * The scan's distance in meters.
* This just get's loaded from it's track.
*/ */
@IsInt() @IsInt()
@IsPositive() @IsPositive()
@ -48,6 +53,7 @@ export class TrackScan extends Scan {
/** /**
* The scan's creation timestamp. * The scan's creation timestamp.
* Will be used to implement fraud detection.
*/ */
@Column() @Column()
@IsDateString() @IsDateString()

View File

@ -8,26 +8,29 @@ import { UserAction } from './UserAction';
import { UserGroup } from './UserGroup'; 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() @ChildEntity()
export class User extends Principal { 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 }) @Column({ unique: true })
@IsUUID(4) @IsUUID(4)
uuid: string; 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 }) @Column({ nullable: true, unique: true })
@IsEmail() @IsEmail()
email?: string; email?: string;
/** /**
* user phone * The user's phone number.
*/ */
@Column({ nullable: true }) @Column({ nullable: true })
@IsOptional() @IsOptional()
@ -35,14 +38,15 @@ export class User extends Principal {
phone?: string; 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 }) @Column({ nullable: true, unique: true })
@IsString() @IsString()
username?: string; username?: string;
/** /**
* firstname * The user's first name.
*/ */
@Column() @Column()
@IsString() @IsString()
@ -50,7 +54,7 @@ export class User extends Principal {
firstname: string; firstname: string;
/** /**
* middlename * The user's middle name.
*/ */
@Column({ nullable: true }) @Column({ nullable: true })
@IsString() @IsString()
@ -58,7 +62,7 @@ export class User extends Principal {
middlename?: string; middlename?: string;
/** /**
* lastname * The user's last name.
*/ */
@Column() @Column()
@IsString() @IsString()
@ -66,7 +70,8 @@ export class User extends Principal {
lastname: string; lastname: string;
/** /**
* password * The user's password.
* This is a argon2 hash salted with the user's uuid.
*/ */
@Column() @Column()
@IsString() @IsString()
@ -74,7 +79,8 @@ export class User extends Principal {
password: string; password: string;
/** /**
* groups * The groups this user is a part of.
* The user will inherit the groups permissions (without overwriting his own).
*/ */
@IsOptional() @IsOptional()
@ManyToMany(() => UserGroup, { nullable: true }) @ManyToMany(() => UserGroup, { nullable: true })
@ -82,21 +88,23 @@ export class User extends Principal {
groups: UserGroup[]; groups: UserGroup[];
/** /**
* is user enabled? * Is this user enabled?
*/ */
@Column() @Column()
@IsBoolean() @IsBoolean()
enabled: boolean = true; enabled: boolean = true;
/** /**
* jwt refresh count * The user's jwt refresh token count.
* Used to invalidate jwts.
*/ */
@IsInt() @IsInt()
@Column({ default: 1 }) @Column({ default: 1 })
refreshTokenCount?: number; 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 }) @Column({ nullable: true, unique: true })
@IsString() @IsString()
@ -104,14 +112,15 @@ export class User extends Principal {
profilePic?: string; profilePic?: string;
/** /**
* actions * The actions performed by this user.
* For documentation purposes only, will be implemented later.
*/ */
@IsOptional() @IsOptional()
@OneToMany(() => UserAction, action => action.user, { nullable: true }) @OneToMany(() => UserAction, action => action.user, { nullable: true })
actions: UserAction[] actions: UserAction[]
/** /**
* Turn this into a response. * Turns this entity into it's response class.
*/ */
public toResponse(): ResponsePrincipal { public toResponse(): ResponsePrincipal {
return new ResponseUser(this); return new ResponseUser(this);

View File

@ -1,14 +1,17 @@
import { import {
IsEnum,
IsInt, IsInt,
IsNotEmpty, IsNotEmpty,
IsOptional, IsOptional,
IsString IsString
} from "class-validator"; } from "class-validator";
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import { PermissionAction } from '../enums/PermissionAction';
import { User } from './User'; import { User } from './User';
/** /**
* Defines the UserAction interface. * Defines the UserAction entity.
* Will later be used to document a user's actions.
*/ */
@Entity() @Entity()
export class UserAction { export class UserAction {
@ -20,7 +23,7 @@ export class UserAction {
id: number; id: number;
/** /**
* user * The user that performed the action.
*/ */
@ManyToOne(() => User, user => user.actions) @ManyToOne(() => User, user => user.actions)
user: User user: User
@ -34,15 +37,16 @@ export class UserAction {
target: string; target: string;
/** /**
* The actions's action (e.g. UPDATE) * The actions's action (e.g. UPDATE).
* Directly pulled from the PermissionAction Enum.
*/ */
@Column() @Column({ type: 'varchar' })
@IsNotEmpty() @IsEnum(PermissionAction)
@IsString() action: PermissionAction;
action: string;
/** /**
* 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 }) @Column({ nullable: true })
@IsOptional() @IsOptional()

View File

@ -9,7 +9,8 @@ import { ResponseUserGroup } from '../responses/ResponseUserGroup';
import { Principal } from './Principal'; 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() @ChildEntity()
export class UserGroup extends Principal { export class UserGroup extends Principal {
@ -30,6 +31,9 @@ export class UserGroup extends Principal {
@IsString() @IsString()
description?: string; description?: string;
/**
* Turns this entity into it's response class.
*/
public toResponse(): ResponsePrincipal { public toResponse(): ResponsePrincipal {
return new ResponseUserGroup(this); return new ResponseUserGroup(this);
} }

View File

@ -1,3 +1,6 @@
/**
* This enum contains all posible actions for permissions.
*/
export enum PermissionAction { export enum PermissionAction {
GET = 'GET', GET = 'GET',
CREATE = 'CREATE', CREATE = 'CREATE',

View File

@ -1,9 +1,12 @@
/**
* This enum contains all posible targets for permissions.
*/
export enum PermissionTarget { export enum PermissionTarget {
RUNNER = 'RUNNER', RUNNER = 'RUNNER',
ORGANISATION = 'ORGANISATION', ORGANISATION = 'ORGANISATION',
TEAM = 'TEAM', TEAM = 'TEAM',
TRACK = 'TRACK', TRACK = 'TRACK',
USER = 'USER', USER = 'USER',
GROUP = 'USERGROUP', USERGROUP = 'USERGROUP',
PERMISSION = 'PERMISSION' PERMISSION = 'PERMISSION'
} }

View File

@ -1,26 +1,29 @@
import { IsInt, IsString } from 'class-validator'; import { IsInt, IsString } from 'class-validator';
/** /**
* Defines a auth object * Defines the repsonse auth.
*/ */
export class Auth { export class Auth {
/** /**
* access_token - JWT shortterm access token * The access_token - JWT shortterm access token.
*/ */
@IsString() @IsString()
access_token: string; 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() @IsString()
refresh_token: string; refresh_token: string;
/** /**
* access_token_expires_at - unix timestamp of access token expiry * The unix timestamp for access the token's expiry.
*/ */
@IsInt() @IsInt()
access_token_expires_at: number; 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() @IsInt()
refresh_token_expires_at: number; refresh_token_expires_at: number;

View File

@ -1,7 +1,7 @@
import { IsString } from 'class-validator'; import { IsString } from 'class-validator';
/** /**
* Defines a empty response object * Defines a empty response object.
*/ */
export class ResponseEmpty { export class ResponseEmpty {
@IsString() @IsString()

View File

@ -1,11 +1,11 @@
import { IsString } from 'class-validator'; import { IsString } from 'class-validator';
/** /**
* Defines a Logout object * Defines the logout response.
*/ */
export class Logout { export class Logout {
/** /**
* timestamp of logout * The logout's timestamp.
*/ */
@IsString() @IsString()
timestamp: number; timestamp: number;

View File

@ -1,18 +1,12 @@
import { import { IsInt, IsString } from "class-validator";
IsInt,
IsString
} from "class-validator";
import { Participant } from '../entities/Participant'; import { Participant } from '../entities/Participant';
/** /**
* Defines a participant response. * Defines the participant response.
*/ */
export abstract class ResponseParticipant { export abstract class ResponseParticipant {
/** /**
* Autogenerated unique id (primary key). * The participant's id.
*/ */
@IsInt() @IsInt()
id: number; id: number;
@ -25,7 +19,6 @@ export abstract class ResponseParticipant {
/** /**
* The participant's middle name. * The participant's middle name.
* Optional.
*/ */
@IsString() @IsString()
middlename?: string; middlename?: string;
@ -38,18 +31,20 @@ export abstract class ResponseParticipant {
/** /**
* The participant's phone number. * The participant's phone number.
* Optional.
*/ */
@IsString() @IsString()
phone?: string; phone?: string;
/** /**
* The participant's e-mail address. * The participant's e-mail address.
* Optional.
*/ */
@IsString() @IsString()
email?: string; email?: string;
/**
* Creates a ResponseParticipant object from a participant.
* @param participant The participant the response shall be build for.
*/
public constructor(participant: Participant) { public constructor(participant: Participant) {
this.id = participant.id; this.id = participant.id;
this.firstname = participant.firstname; this.firstname = participant.firstname;

View File

@ -10,11 +10,11 @@ import { PermissionTarget } from '../enums/PermissionTargets';
import { ResponsePrincipal } from './ResponsePrincipal'; import { ResponsePrincipal } from './ResponsePrincipal';
/** /**
* Defines a track of given length. * Defines the permission response.
*/ */
export class ResponsePermission { export class ResponsePermission {
/** /**
* Autogenerated unique id (primary key). * The permission's id.
*/ */
@IsInt() @IsInt()
id: number;; id: number;;
@ -40,6 +40,10 @@ export class ResponsePermission {
@IsEnum(PermissionAction) @IsEnum(PermissionAction)
action: PermissionAction; action: PermissionAction;
/**
* Creates a ResponsePermission object from a permission.
* @param permission The permission the response shall be build for.
*/
public constructor(permission: Permission) { public constructor(permission: Permission) {
this.id = permission.id; this.id = permission.id;
this.principal = permission.principal.toResponse(); this.principal = permission.principal.toResponse();

View File

@ -4,16 +4,20 @@ import {
import { Principal } from '../entities/Principal'; import { Principal } from '../entities/Principal';
/** /**
* Defines Principal's response class. * Defines the principal response.
*/ */
export abstract class ResponsePrincipal { export abstract class ResponsePrincipal {
/** /**
* Autogenerated unique id (primary key). * The principal's id.
*/ */
@IsInt() @IsInt()
id: number; id: number;
/**
* Creates a ResponsePrincipal object from a principal.
* @param principal The principal the response shall be build for.
*/
public constructor(principal: Principal) { public constructor(principal: Principal) {
this.id = principal.id; this.id = principal.id;
} }

View File

@ -7,13 +7,12 @@ import { RunnerGroup } from '../entities/RunnerGroup';
import { ResponseParticipant } from './ResponseParticipant'; import { ResponseParticipant } from './ResponseParticipant';
/** /**
* Defines RunnerTeam's response class. * Defines the runner response.
*/ */
export class ResponseRunner extends ResponseParticipant { export class ResponseRunner extends ResponseParticipant {
/** /**
* The runner's currently ran distance in meters. * The runner's currently ran distance in meters.
* Optional.
*/ */
@IsInt() @IsInt()
distance: number; distance: number;
@ -24,6 +23,10 @@ export class ResponseRunner extends ResponseParticipant {
@IsObject() @IsObject()
group: RunnerGroup; group: RunnerGroup;
/**
* Creates a ResponseRunner object from a runner.
* @param runner The user the response shall be build for.
*/
public constructor(runner: Runner) { public constructor(runner: Runner) {
super(runner); super(runner);
this.distance = runner.scans.filter(scan => { scan.valid === true }).reduce((sum, current) => sum + current.distance, 0); this.distance = runner.scans.filter(scan => { scan.valid === true }).reduce((sum, current) => sum + current.distance, 0);

View File

@ -1,38 +1,20 @@
import { import { IsInt, IsNotEmpty, IsObject, IsOptional, IsString } from "class-validator";
IsInt,
IsNotEmpty,
IsObject,
IsOptional,
IsString
} from "class-validator";
import { GroupContact } from '../entities/GroupContact'; import { GroupContact } from '../entities/GroupContact';
import { RunnerGroup } from '../entities/RunnerGroup'; import { RunnerGroup } from '../entities/RunnerGroup';
/** /**
* Defines a track of given length. * Defines the runnerGroup response.
*/ */
export abstract class ResponseRunnerGroup { export abstract class ResponseRunnerGroup {
/** /**
* Autogenerated unique id (primary key). * The runnerGroup's id.
*/ */
@IsInt() @IsInt()
@IsNotEmpty() @IsNotEmpty()
id: number;; id: number;;
/** /**
* The groups's name. * The runnerGroup's name.
*/ */
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
@ -40,13 +22,16 @@ export abstract class ResponseRunnerGroup {
/** /**
* The group's contact. * The runnerGroup's contact.
* Optional.
*/ */
@IsObject() @IsObject()
@IsOptional() @IsOptional()
contact?: GroupContact; contact?: GroupContact;
/**
* Creates a ResponseRunnerGroup object from a runnerGroup.
* @param group The runnerGroup the response shall be build for.
*/
public constructor(group: RunnerGroup) { public constructor(group: RunnerGroup) {
this.id = group.id; this.id = group.id;
this.name = group.name; this.name = group.name;

View File

@ -9,25 +9,27 @@ import { RunnerTeam } from '../entities/RunnerTeam';
import { ResponseRunnerGroup } from './ResponseRunnerGroup'; import { ResponseRunnerGroup } from './ResponseRunnerGroup';
/** /**
* Defines RunnerOrgs's response class. * Defines the runnerOrganisation response.
*/ */
export class ResponseRunnerOrganisation extends ResponseRunnerGroup { export class ResponseRunnerOrganisation extends ResponseRunnerGroup {
/** /**
* The orgs's address. * The runnerOrganisation's address.
* Optional.
*/ */
@IsObject() @IsObject()
@IsNotEmpty() @IsNotEmpty()
address?: Address; address?: Address;
/** /**
* The orgs associated teams. * The runnerOrganisation associated teams.
*/ */
@IsArray() @IsArray()
teams: RunnerTeam[]; teams: RunnerTeam[];
/**
* Creates a ResponseRunnerOrganisation object from a runnerOrganisation.
* @param org The runnerOrganisation the response shall be build for.
*/
public constructor(org: RunnerOrganisation) { public constructor(org: RunnerOrganisation) {
super(org); super(org);
this.address = org.address; this.address = org.address;

View File

@ -1,24 +1,24 @@
import { import { IsNotEmpty, IsObject } from "class-validator";
IsNotEmpty,
IsObject
} from "class-validator";
import { RunnerOrganisation } from '../entities/RunnerOrganisation'; import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { RunnerTeam } from '../entities/RunnerTeam'; import { RunnerTeam } from '../entities/RunnerTeam';
import { ResponseRunnerGroup } from './ResponseRunnerGroup'; import { ResponseRunnerGroup } from './ResponseRunnerGroup';
/** /**
* Defines RunnerTeam's response class. * Defines the runnerTeam response.
*/ */
export class ResponseRunnerTeam extends ResponseRunnerGroup { export class ResponseRunnerTeam extends ResponseRunnerGroup {
/** /**
* The team's parent group (organisation). * The runnerTeam's parent group (organisation).
* Optional.
*/ */
@IsObject() @IsObject()
@IsNotEmpty() @IsNotEmpty()
parentGroup: RunnerOrganisation; parentGroup: RunnerOrganisation;
/**
* Creates a ResponseRunnerTeam object from a runnerTeam.
* @param team The team the response shall be build for.
*/
public constructor(team: RunnerTeam) { public constructor(team: RunnerTeam) {
super(team); super(team);
this.parentGroup = team.parentGroup; this.parentGroup = team.parentGroup;

View File

@ -1,16 +1,12 @@
import { import { IsInt, IsString } from "class-validator";
IsInt,
IsString
} from "class-validator";
import { Track } from '../entities/Track'; import { Track } from '../entities/Track';
/** /**
* Defines a track of given length. * Defines the track response.
*/ */
export class ResponseTrack { export class ResponseTrack {
/** /**
* Autogenerated unique id (primary key). * The track's id.
*/ */
@IsInt() @IsInt()
id: number;; id: number;;
@ -27,6 +23,10 @@ export class ResponseTrack {
@IsInt() @IsInt()
distance: number; distance: number;
/**
* Creates a ResponseTrack object from a track.
* @param track The track the response shall be build for.
*/
public constructor(track: Track) { public constructor(track: Track) {
this.id = track.id; this.id = track.id;
this.name = track.name; this.name = track.name;

View File

@ -11,7 +11,7 @@ import { UserGroup } from '../entities/UserGroup';
import { ResponsePrincipal } from './ResponsePrincipal'; import { ResponsePrincipal } from './ResponsePrincipal';
/** /**
* Defines a user response. * Defines the user response.
*/ */
export class ResponseUser extends ResponsePrincipal { export class ResponseUser extends ResponsePrincipal {
/** /**
@ -22,7 +22,6 @@ export class ResponseUser extends ResponsePrincipal {
/** /**
* The user's middle name. * The user's middle name.
* Optional.
*/ */
@IsString() @IsString()
middlename?: string; middlename?: string;
@ -35,45 +34,53 @@ export class ResponseUser extends ResponsePrincipal {
/** /**
* The user's phone number. * The user's phone number.
* Optional.
*/ */
@IsString() @IsString()
phone?: string; phone?: string;
/** /**
* The user's e-mail address. * The user's e-mail address.
* Optional.
*/ */
@IsString() @IsString()
email?: string; email?: string;
/** /**
* is user enabled? * The user's username.
*/
@IsString()
username?: string;
/**
* Is user enabled?
*/ */
@IsBoolean() @IsBoolean()
enabled: boolean = true; enabled: boolean = true;
/** /**
* profilepic * The user's profile pic.
*/ */
@IsString() @IsString()
@IsOptional() @IsOptional()
profilePic?: string; profilePic?: string;
/** /**
* Groups * The groups that the user is a part of.
*/ */
@IsArray() @IsArray()
@IsOptional() @IsOptional()
groups: UserGroup[]; groups: UserGroup[];
/** /**
* permissions * The user's permissions.
*/ */
@IsArray() @IsArray()
@IsOptional() @IsOptional()
permissions: Permission[]; permissions: Permission[];
/**
* Creates a ResponseUser object from a user.
* @param user The user the response shall be build for.
*/
public constructor(user: User) { public constructor(user: User) {
super(user); super(user);
this.firstname = user.firstname; this.firstname = user.firstname;
@ -81,6 +88,7 @@ export class ResponseUser extends ResponsePrincipal {
this.lastname = user.lastname; this.lastname = user.lastname;
this.phone = user.phone; this.phone = user.phone;
this.email = user.email; this.email = user.email;
this.username = user.username;
this.enabled = user.enabled; this.enabled = user.enabled;
this.profilePic = user.profilePic; this.profilePic = user.profilePic;
this.groups = user.groups; this.groups = user.groups;

View File

@ -1,41 +1,37 @@
import { import { IsArray, IsNotEmpty, IsOptional, IsString } from "class-validator";
IsArray,
IsNotEmpty,
IsOptional,
IsString
} from "class-validator";
import { Permission } from '../entities/Permission'; import { Permission } from '../entities/Permission';
import { UserGroup } from '../entities/UserGroup'; import { UserGroup } from '../entities/UserGroup';
import { ResponsePrincipal } from './ResponsePrincipal'; import { ResponsePrincipal } from './ResponsePrincipal';
/** /**
* Defines a user response. * Defines the userGroup response.
*/ */
export class ResponseUserGroup extends ResponsePrincipal { export class ResponseUserGroup extends ResponsePrincipal {
/** /**
* The group's name * The userGroup's name.
*/ */
@IsNotEmpty() @IsNotEmpty()
@IsString() @IsString()
name: string; name: string;
/** /**
* The group's description * The userGroup's description.
*/ */
@IsOptional() @IsOptional()
@IsString() @IsString()
description?: string; description?: string;
/** /**
* permissions * The userGroup's permissions.
*/ */
@IsArray() @IsArray()
@IsOptional() @IsOptional()
permissions: Permission[]; permissions: Permission[];
/**
* Creates a ResponseUserGroup object from a userGroup.
* @param group The userGroup the response shall be build for.
*/
public constructor(group: UserGroup) { public constructor(group: UserGroup) {
super(group); super(group);
this.name = group.name; this.name = group.name;

View File

@ -9,6 +9,10 @@ import { UserGroup } from '../models/entities/UserGroup';
import { PermissionAction } from '../models/enums/PermissionAction'; import { PermissionAction } from '../models/enums/PermissionAction';
import { PermissionTarget } from '../models/enums/PermissionTargets'; 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 { export default class SeedUsers implements Seeder {
public async run(factory: Factory, connection: Connection): Promise<any> { public async run(factory: Factory, connection: Connection): Promise<any> {
let adminGroup: UserGroup = await this.createAdminGroup(connection); let adminGroup: UserGroup = await this.createAdminGroup(connection);
@ -19,7 +23,7 @@ export default class SeedUsers implements Seeder {
public async createAdminGroup(connection: Connection) { public async createAdminGroup(connection: Connection) {
let adminGroup = new CreateUserGroup(); let adminGroup = new CreateUserGroup();
adminGroup.name = "ADMINS"; adminGroup.name = "ADMINS";
adminGroup.description = "Has all possible permissions"; adminGroup.description = "Have all possible permissions";
return await connection.getRepository(UserGroup).save(await adminGroup.toUserGroup()); return await connection.getRepository(UserGroup).save(await adminGroup.toUserGroup());
} }