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);
}
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'] }));
}

View File

@ -81,7 +81,7 @@ export class RunnerController {
throw new RunnerIdsNotMatchingError();
}
await this.runnerRepository.update(oldRunner, await runner.toRunner());
await this.runnerRepository.save(await runner.updateRunner(oldRunner));
return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] }));
}

View File

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

View File

@ -82,7 +82,7 @@ export class RunnerTeamController {
throw new RunnerTeamIdsNotMatchingError();
}
await this.runnerTeamRepository.update(oldRunnerTeam, await runnerTeam.toRunnerTeam());
await this.runnerTeamRepository.save(await runnerTeam.updateRunnerTeam(oldRunnerTeam));
return new ResponseRunnerTeam(await this.runnerTeamRepository.findOne({ id: runnerTeam.id }, { relations: ['parentGroup', 'contact'] }));
}

View File

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

View File

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

View File

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

View File

@ -1,63 +1,57 @@
import { IsString } from 'class-validator';
import { ForbiddenError, NotAcceptableError, NotFoundError, UnauthorizedError } from 'routing-controllers';
/**
* Error to throw when a jwt is expired.
*/
export class ExpiredJWTError extends UnauthorizedError {
@IsString()
name = "ExpiredJWTError"
@IsString()
message = "your provided jwt is expired"
}
/**
* Error to throw when a jwt could not be parsed.
* For example: Wrong signature or expired.
*/
export class IllegalJWTError extends UnauthorizedError {
@IsString()
name = "IllegalJWTError"
@IsString()
message = "your provided jwt could not be parsed"
message = "Your provided jwt could not be parsed."
}
/**
* Error to throw when user is nonexistant or refreshtoken is invalid.
* This can happen if someone provides a JWT with a invalid user id or the refreshTokenCount of the user is higher that the provided jwt's is.
*/
export class UserNonexistantOrRefreshtokenInvalidError extends UnauthorizedError {
@IsString()
name = "UserNonexistantOrRefreshtokenInvalidError"
@IsString()
message = "user is nonexistant or refreshtoken is invalid"
message = "User is nonexistant or refreshtoken is invalid."
}
/**
* Error to throw when provided credentials are invalid.
* We don't have seperate errors for username/mail and passwords to protect against guessing attacks.
*/
export class InvalidCredentialsError extends UnauthorizedError {
@IsString()
name = "InvalidCredentialsError"
@IsString()
message = "your provided credentials are invalid"
message = "Your provided credentials are invalid."
}
/**
* Error to throw when a jwt does not have permission for this route/action.
* Mainly used be the @Authorized decorator (via the authchecker).
*/
export class NoPermissionError extends ForbiddenError {
@IsString()
name = "NoPermissionError"
@IsString()
message = "your provided jwt does not have permission for this route/ action"
message = "Your provided jwt does not have permission for this route/ action."
}
/**
* Error to throw when no username and no email is set.
* Because we have to identify users somehow.
*/
export class UsernameOrEmailNeededError extends NotAcceptableError {
@IsString()
@ -68,47 +62,48 @@ export class UsernameOrEmailNeededError extends NotAcceptableError {
}
/**
* Error to throw when no password is provided.
* Error to throw when no password is provided for a new user.
* Passwords are the minimum we need for user security.
*/
export class PasswordNeededError extends NotAcceptableError {
@IsString()
name = "PasswordNeededError"
@IsString()
message = "no password is provided - you need to provide it"
message = "No password is provided - you need to provide it."
}
/**
* Error to throw when no user could be found mating the provided credential.
* Error to throw when no user could be found for a certain query.
*/
export class UserNotFoundError extends NotFoundError {
@IsString()
name = "UserNotFoundError"
@IsString()
message = "no user could be found for provided credential"
message = "The user you provided couldn't be located in the system. \n Please check your request."
}
/**
* Error to throw when no jwt token was provided (but one had to be).
* Error to throw when no jwt was provided (but one had to be).
*/
export class JwtNotProvidedError extends NotAcceptableError {
@IsString()
name = "JwtNotProvidedError"
@IsString()
message = "no jwt token was provided"
message = "No jwt was provided."
}
/**
* Error to throw when user was not found or refresh token count was invalid.
* Error to throw when user was not found or the jwt's refresh token count was invalid.
*/
export class UserNotFoundOrRefreshTokenCountInvalidError extends NotAcceptableError {
@IsString()
name = "UserNotFoundOrRefreshTokenCountInvalidError"
@IsString()
message = "user was not found or refresh token count was invalid"
message = "User was not found or the refresh token count is invalid."
}
/**
@ -119,5 +114,5 @@ export class RefreshTokenCountInvalidError extends NotAcceptableError {
name = "RefreshTokenCountInvalidError"
@IsString()
message = "refresh token count was invalid"
message = "Refresh token count is invalid."
}

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.
*/
export class PermissionIdsNotMatchingError extends NotAcceptableError {
@ -21,11 +21,11 @@ export class PermissionIdsNotMatchingError extends NotAcceptableError {
name = "PermissionIdsNotMatchingError"
@IsString()
message = "The id's don't match!! \n And if you wanted to change a permission's id: This isn't allowed"
message = "The ids don't match! \n And if you wanted to change a permission's id: This isn't allowed!"
}
/**
* Error to throw when a permission get's provided without a principal.
* Error to throw when a permission gets provided without a principal.
*/
export class PermissionNeedsPrincipalError extends NotAcceptableError {
@IsString()

View File

@ -3,7 +3,6 @@ import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when a runner couldn't be found.
* Implemented this ways to work with the json-schema conversion for openapi.
*/
export class RunnerNotFoundError extends NotFoundError {
@IsString()
@ -16,14 +15,13 @@ export class RunnerNotFoundError extends NotFoundError {
/**
* Error to throw when two runners' ids don't match.
* Usually occurs when a user tries to change a runner's id.
* Implemented this ways to work with the json-schema conversion for openapi.
*/
export class RunnerIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "RunnerIdsNotMatchingError"
@IsString()
message = "The id's don't match!! \n And if you wanted to change a runner's id: This isn't allowed"
message = "The ids don't match! \n And if you wanted to change a runner's id: This isn't allowed!"
}
/**

View File

@ -3,7 +3,6 @@ import { NotFoundError } from 'routing-controllers';
/**
* Error to throw when a runner group couldn't be found.
* Implemented this ways to work with the json-schema conversion for openapi.
*/
export class RunnerGroupNotFoundError extends NotFoundError {
@IsString()

View File

@ -3,7 +3,6 @@ import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when a runner organisation couldn't be found.
* Implemented this ways to work with the json-schema conversion for openapi.
*/
export class RunnerOrganisationNotFoundError extends NotFoundError {
@IsString()
@ -15,39 +14,36 @@ export class RunnerOrganisationNotFoundError extends NotFoundError {
/**
* Error to throw when two runner organisations' ids don't match.
* Usually occurs when a user tries to change a runner's id.
* Implemented this way to work with the json-schema conversion for openapi.
* Usually occurs when a user tries to change a runner organisation's id.
*/
export class RunnerOrganisationIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "RunnerOrganisationIdsNotMatchingError"
@IsString()
message = "The id's don't match!! \n And if you wanted to change a runner's id: This isn't allowed"
message = "The ids don't match! \n And if you wanted to change a runner organisation's id: This isn't allowed!"
}
/**
* Error to throw when a organisation still has runners associated.
* Implemented this waysto work with the json-schema conversion for openapi.
*/
export class RunnerOrganisationHasRunnersError extends NotAcceptableError {
@IsString()
name = "RunnerOrganisationHasRunnersError"
@IsString()
message = "This organisation still has runners associated with it. \n If you want to delete this organisation with all it's runners and teams ass `?force` to your query."
message = "This organisation still has runners associated with it. \n If you want to delete this organisation with all it's runners and teams add `?force` to your query."
}
/**
* Error to throw when a organisation still has runners associated.
* Implemented this waysto work with the json-schema conversion for openapi.
* Error to throw when a organisation still has teams associated.
*/
export class RunnerOrganisationHasTeamsError extends NotAcceptableError {
@IsString()
name = "RunnerOrganisationHasTeamsError"
@IsString()
message = "This organisation still has teams associated with it. \n If you want to delete this organisation with all it's runners and teams ass `?force` to your query."
message = "This organisation still has teams associated with it. \n If you want to delete this organisation with all it's runners and teams add `?force` to your query."
}
/**

View File

@ -3,7 +3,6 @@ import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when a runner team couldn't be found.
* Implemented this ways to work with the json-schema conversion for openapi.
*/
export class RunnerTeamNotFoundError extends NotFoundError {
@IsString()
@ -15,32 +14,29 @@ export class RunnerTeamNotFoundError extends NotFoundError {
/**
* Error to throw when two runner teams' ids don't match.
* Usually occurs when a user tries to change a runner's id.
* Implemented this way to work with the json-schema conversion for openapi.
* Usually occurs when a user tries to change a runner team's id.
*/
export class RunnerTeamIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "RunnerTeamIdsNotMatchingError"
@IsString()
message = "The id's don't match!! \n And if you wanted to change a runner's id: This isn't allowed"
message = "The ids don't match! \n And if you wanted to change a runner's id: This isn't allowed!"
}
/**
* Error to throw when a team still has runners associated.
* Implemented this waysto work with the json-schema conversion for openapi.
*/
export class RunnerTeamHasRunnersError extends NotAcceptableError {
@IsString()
name = "RunnerTeamHasRunnersError"
@IsString()
message = "This team still has runners associated with it. \n If you want to delete this team with all it's runners and teams ass `?force` to your query."
message = "This team still has runners associated with it. \n If you want to delete this team with all it's runners and teams add `?force` to your query."
}
/**
* Error to throw when a team still has runners associated.
* Implemented this waysto work with the json-schema conversion for openapi.
*/
export class RunnerTeamNeedsParentError extends NotAcceptableError {
@IsString()

View File

@ -1,9 +1,8 @@
import { JsonController, Param, Body, Get, Post, Put, Delete, NotFoundError, OnUndefined, NotAcceptableError } from 'routing-controllers';
import { IsInt, IsNotEmpty, IsPositive, IsString } from 'class-validator';
import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when a track couldn't be found.
* Implemented this ways to work with the json-schema conversion for openapi.
*/
export class TrackNotFoundError extends NotFoundError {
@IsString()
@ -16,12 +15,11 @@ export class TrackNotFoundError extends NotFoundError {
/**
* Error to throw when two tracks' ids don't match.
* Usually occurs when a user tries to change a track's id.
* Implemented this ways to work with the json-schema conversion for openapi.
*/
export class TrackIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "TrackIdsNotMatchingError"
@IsString()
message = "The id's don't match!! \n And if you wanted to change a track's id: This isn't allowed"
message = "The ids don't match! \n And if you wanted to change a track's id: This isn't allowed"
}

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 {
@IsString()
name = "UsernameOrEmailNeededError"
@IsString()
message = "no username or email is set!"
message = "No username or email is set!"
}
/**
@ -33,5 +34,5 @@ export class UserIdsNotMatchingError extends NotAcceptableError {
name = "UserIdsNotMatchingError"
@IsString()
message = "The id's don't match!! \n And if you wanted to change a user's id: This isn't allowed"
message = "The ids don't match!! \n And if you wanted to change a user's id: This isn't allowed!"
}

View File

@ -2,14 +2,14 @@ import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when no groupname is set
* Error to throw when no groupname is set.
*/
export class GroupNameNeededError extends NotFoundError {
@IsString()
name = "GroupNameNeededError"
@IsString()
message = "no groupname is set!"
message = "No name is set for this group!"
}
/**
@ -32,5 +32,5 @@ export class UserGroupIdsNotMatchingError extends NotAcceptableError {
name = "UserGroupIdsNotMatchingError"
@IsString()
message = "The id's don't match!! \n If you wanted to change a usergroup's id: This isn't allowed"
message = "The ids don't match!! \n If you wanted to change a usergroup's id: This isn't allowed!"
}

View File

@ -4,6 +4,7 @@ import { User } from '../models/entities/User';
import SeedUsers from '../seeds/SeedUsers';
/**
* Loader for the database that creates the database connection and initializes the database tabels.
* It also triggers the seeding process if no users got detected in the database.
*/
export default async () => {
const connection = await createConnection();

View File

@ -2,7 +2,7 @@ import cookieParser from "cookie-parser";
import { Application } from "express";
/**
* Loader for express related configurations.
* Currently only enables the proxy trust.
* Configures proxy trusts, globally used middlewares and other express features.
*/
export default async (app: Application) => {
app.enable('trust proxy');

View File

@ -5,6 +5,7 @@ import openapiLoader from "./openapi";
/**
* Index Loader that executes the other loaders in the right order.
* This basicly exists for abstraction and a overall better dev experience.
*/
export default async (app: Application) => {
await databaseLoader();

View File

@ -5,7 +5,8 @@ import { routingControllersToSpec } from "routing-controllers-openapi";
import * as swaggerUiExpress from "swagger-ui-express";
/**
* Loader for everything openapi related - from creating the schema to serving it via a static route.
* Loader for everything openapi related - from creating the schema to serving it via a static route and swaggerUiExpress.
* All auth schema related stuff also has to be configured here
*/
export default async (app: Application) => {
const storage = getMetadataArgsStorage();
@ -26,7 +27,8 @@ export default async (app: Application) => {
"AuthToken": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT"
"bearerFormat": "JWT",
description: "A JWT based access token. Use /api/auth/login or /api/auth/refresh to get one."
}
}
},

View File

@ -1,7 +1,7 @@
import { ExpressErrorMiddlewareInterface, Middleware } from "routing-controllers";
/**
* Our Error handling middlware that returns our custom httperrors to the user
* Our Error handling middlware that returns our custom httperrors to the user.
*/
@Middleware({ type: "after" })
export class ErrorHandler implements ExpressErrorMiddlewareInterface {

View File

@ -1,5 +1,10 @@
import { Request, Response } from 'express';
/**
* Custom express middleware that appends the raw body to the request obeject.
* Mainly used for parsing csvs from boddies.
*/
const RawBodyMiddleware = (req: Request, res: Response, next: () => void) => {
const body = []
req.on('data', chunk => {
@ -8,15 +13,6 @@ const RawBodyMiddleware = (req: Request, res: Response, next: () => void) => {
req.on('end', () => {
const rawBody = Buffer.concat(body)
req['rawBody'] = rawBody
/*
switch (req.header('content-type')) {
case 'application/json':
req.body = JSON.parse(rawBody.toString())
break
// add more body parsing if needs be
default:
}
*/
next()
})
req.on('error', () => {

View File

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

View File

@ -1,5 +1,5 @@
import * as argon2 from "argon2";
import { IsEmail, IsOptional, IsString } from 'class-validator';
import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { InvalidCredentialsError, PasswordNeededError, UserNotFoundError } from '../../errors/AuthError';
import { UsernameOrEmailNeededError } from '../../errors/UserErrors';
@ -7,17 +7,41 @@ import { JwtCreator } from '../../jwtcreator';
import { User } from '../entities/User';
import { Auth } from '../responses/ResponseAuth';
/**
* This class is used to create auth credentials based on user credentials provided in a json body (post request).
* To be a little bit more exact: Is takes in a username/email + password and creates a new access and refresh token for the user.
* It of course checks for user existance, password validity and so on.
*/
export class CreateAuth {
/**
* The username of the user that want's to login.
* Either username or email have to be provided.
*/
@IsOptional()
@IsString()
username?: string;
@IsString()
password: string;
/**
* The email address of the user that want's to login.
* Either username or email have to be provided.
*/
@IsOptional()
@IsEmail()
@IsString()
email?: string;
/**
* The user's password.
* Will be checked against an argon2 hash.
*/
@IsNotEmpty()
@IsString()
password: string;
/**
* Creates a new auth object based on this.
*/
public async toAuth(): Promise<Auth> {
let newAuth: Auth = new Auth();

View File

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

View File

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

View File

@ -11,33 +11,33 @@ import { PermissionAction } from '../enums/PermissionAction';
import { PermissionTarget } from '../enums/PermissionTargets';
/**
* Defines a track of given length.
* This classed is used to create a new Permission entity from a json body (post request).
*/
export class CreatePermission {
/**
* The permissions's principal's id.
* The new permissions's principal's id.
*/
@IsInt()
@IsNotEmpty()
principal: number;
/**
* The permissions's target.
* The new permissions's target.
*/
@IsNotEmpty()
@IsEnum(PermissionTarget)
target: PermissionTarget;
/**
* The permissions's action.
* The new permissions's action.
*/
@IsNotEmpty()
@IsEnum(PermissionAction)
action: PermissionAction;
/**
* Converts a Permission object based on this.
* Creates a new Permission entity from this.
*/
public async toPermission(): Promise<Permission> {
let newPermission: Permission = new Permission();
@ -49,6 +49,9 @@ export class CreatePermission {
return newPermission;
}
/**
* Gets the new permission's principal by it's id.
*/
public async getPrincipal(): Promise<Principal> {
let principal = await getConnectionManager().get().getRepository(Principal).findOne({ id: this.principal })
if (!principal) { throw new PrincipalNotFoundError(); }

View File

@ -7,6 +7,9 @@ import { Runner } from '../entities/Runner';
import { RunnerGroup } from '../entities/RunnerGroup';
import { CreateParticipant } from './CreateParticipant';
/**
* This classed is used to create a new Runner entity from a json body (post request).
*/
export class CreateRunner extends CreateParticipant {
/**
@ -16,7 +19,7 @@ export class CreateRunner extends CreateParticipant {
group: number;
/**
* Creates a Runner entity from this.
* Creates a new Runner entity from this.
*/
public async toRunner(): Promise<Runner> {
let newRunner: Runner = new Runner();
@ -33,10 +36,10 @@ export class CreateRunner extends CreateParticipant {
}
/**
* Manages all the different ways a group can be provided.
* Gets the new runner's group by it's id.
*/
public async getGroup(): Promise<RunnerGroup> {
if (this.group === undefined) {
if (this.group === undefined || this.group === null) {
throw new RunnerTeamNeedsParentError();
}
if (!isNaN(this.group)) {

View File

@ -3,16 +3,19 @@ import { getConnectionManager } from 'typeorm';
import { GroupContactNotFoundError, GroupContactWrongTypeError } from '../../errors/GroupContactErrors';
import { GroupContact } from '../entities/GroupContact';
/**
* This classed is used to create a new RunnerGroup entity from a json body (post request).
*/
export abstract class CreateRunnerGroup {
/**
* The group's name.
* The new group's name.
*/
@IsNotEmpty()
@IsString()
name: string;
/**
* The group's contact.
* The new group's contact.
* Optional
*/
@IsInt()
@ -20,7 +23,7 @@ export abstract class CreateRunnerGroup {
contact?: number;
/**
* Get's this group's contact from this.address.
* Gets the new group's contact by it's id.
*/
public async getContact(): Promise<GroupContact> {
if (this.contact === undefined || this.contact === null) {

View File

@ -5,21 +5,23 @@ import { Address } from '../entities/Address';
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { CreateRunnerGroup } from './CreateRunnerGroup';
/**
* This classed is used to create a new RunnerOrganisation entity from a json body (post request).
*/
export class CreateRunnerOrganisation extends CreateRunnerGroup {
/**
* The new organisation's address.
* Must be of type number (address id).
* Optional.
*/
@IsInt()
@IsOptional()
address?: number;
/**
* Get's this org's address from this.address.
* Gets the org's address by it's id.
*/
public async getAddress(): Promise<Address> {
if (this.address === undefined) {
if (this.address === undefined || this.address === null) {
return null;
}
if (!isNaN(this.address)) {
@ -32,7 +34,7 @@ export class CreateRunnerOrganisation extends CreateRunnerGroup {
}
/**
* Creates a RunnerOrganisation entity from this.
* Creates a new RunnerOrganisation entity from this.
*/
public async toRunnerOrganisation(): Promise<RunnerOrganisation> {
let newRunnerOrganisation: RunnerOrganisation = new RunnerOrganisation();

View File

@ -6,17 +6,23 @@ import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { RunnerTeam } from '../entities/RunnerTeam';
import { CreateRunnerGroup } from './CreateRunnerGroup';
/**
* This classed is used to create a new RunnerTeam entity from a json body (post request).
*/
export class CreateRunnerTeam extends CreateRunnerGroup {
/**
* The team's parent group (organisation).
* The new team's parent group (organisation).
*/
@IsInt()
@IsNotEmpty()
parentGroup: number;
/**
* Gets the new team's parent org based on it's id.
*/
public async getParent(): Promise<RunnerOrganisation> {
if (this.parentGroup === undefined) {
if (this.parentGroup === undefined || this.parentGroup === null) {
throw new RunnerTeamNeedsParentError();
}
if (!isNaN(this.parentGroup)) {
@ -29,7 +35,7 @@ export class CreateRunnerTeam extends CreateRunnerGroup {
}
/**
* Creates a RunnerTeam entity from this.
* Creates a new RunnerTeam entity from this.
*/
public async toRunnerTeam(): Promise<RunnerTeam> {
let newRunnerTeam: RunnerTeam = new RunnerTeam();

View File

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

View File

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

View File

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

View File

@ -6,11 +6,23 @@ import { IllegalJWTError, JwtNotProvidedError, RefreshTokenCountInvalidError, Us
import { User } from '../entities/User';
import { Logout } from '../responses/ResponseLogout';
/**
* This class handels a user logging out of the system.
* Of course it check's the user's provided credential (token) before logging him out.
*/
export class HandleLogout {
/**
* A stringyfied jwt access token.
* Will get checked for validity.
*/
@IsString()
@IsOptional()
token?: string;
/**
* Logs the user out.
* This gets achived by increasing the user's refresh token count, thereby invalidateing all currently existing jwts for that user.
*/
public async logout(): Promise<Logout> {
let logout: Logout = new Logout();
if (!this.token || this.token === undefined) {

View File

@ -7,6 +7,10 @@ import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { RunnerTeam } from '../entities/RunnerTeam';
import { CreateRunner } from './CreateRunner';
/**
* Special class used to import runners from csv files - or json arrays created from csv to be exact.
* Why you ask? Because the past has shown us that a non excel/csv based workflow is too much for most schools.
*/
export class ImportRunner {
/**
@ -18,7 +22,6 @@ export class ImportRunner {
/**
* The new runner's middle name.
* Optional.
*/
@IsString()
@IsOptional()
@ -32,18 +35,26 @@ export class ImportRunner {
lastname: string;
/**
* The new runner's class (if not provided otherwise).
* The new runner's team's name (if not provided otherwise).
* The team will automaticly get generated if it doesn't exist in this org yet.
*/
@IsString()
@IsOptional()
team?: string;
/**
* Just an alias for team, because this is usually only used for importing data from schools.
*/
@IsOptional()
@IsString()
public set class(value: string) {
this.team = value;
}
/**
* Creates a CreateRunner object based on this.
* @param groupID Either the id of the new runner's group or the id of the org that the new runner's team is a part of.
*/
public async toCreateRunner(groupID: number): Promise<CreateRunner> {
let newRunner: CreateRunner = new CreateRunner();
@ -55,6 +66,10 @@ export class ImportRunner {
return newRunner;
}
/**
* Get's the new runners group.
* @param groupID Either the id of the new runner's group or the id of the org that the new runner's team is a part of.
*/
public async getGroup(groupID: number): Promise<RunnerGroup> {
if (this.team === undefined && groupID === undefined) {
throw new RunnerGroupNeededError();

View File

@ -7,11 +7,23 @@ import { JwtCreator } from "../../jwtcreator";
import { User } from '../entities/User';
import { Auth } from '../responses/ResponseAuth';
/**
* This class is used to create refreshed auth credentials.
* To be a little bit more exact: Is takes in a refresh token and creates a new access and refresh token for it's user.
* It of course checks for user existance, jwt validity and so on.
*/
export class RefreshAuth {
/**
* A stringyfied jwt refresh token.
* Will get checked for validity.
*/
@IsString()
@IsOptional()
token?: string;
/**
* Creates a new auth object based on this.
*/
public async toAuth(): Promise<Auth> {
let newAuth: Auth = new Auth();
if (!this.token || this.token === undefined) {

View File

@ -7,16 +7,21 @@ import { Principal } from '../entities/Principal';
import { PermissionAction } from '../enums/PermissionAction';
import { PermissionTarget } from '../enums/PermissionTargets';
/**
* This class is used to update a Permission entity (via put request).
*/
export class UpdatePermission {
/**
* The updated runner's id.
* The updated permission's id.
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
*/
@IsInt()
id: number;
/**
* The permissions's principal's id.
* The updated permissions's principal.
* Just has to contain the principal's id -everything else won't be checked or changed.
*/
@IsObject()
@IsNotEmpty()
@ -35,23 +40,21 @@ export class UpdatePermission {
action: PermissionAction;
/**
* Converts a Permission object based on this.
* Updates a provided Permission entity based on this.
*/
public async toPermission(): Promise<Permission> {
let newPermission: Permission = new Permission();
public async updatePermission(permission: Permission): Promise<Permission> {
permission.principal = await this.getPrincipal();
permission.target = this.target;
permission.action = this.action;
newPermission.principal = await this.getPrincipal();
newPermission.target = this.target;
newPermission.action = this.action;
return newPermission;
return permission;
}
/**
* Manages all the different ways a group can be provided.
* Loads the updated permission's principal based on it's id.
*/
public async getPrincipal(): Promise<Principal> {
if (this.principal === undefined) {
if (this.principal === undefined || this.principal === null) {
throw new PermissionNeedsPrincipalError();
}
if (!isNaN(this.principal.id)) {

View File

@ -7,43 +7,45 @@ import { Runner } from '../entities/Runner';
import { RunnerGroup } from '../entities/RunnerGroup';
import { CreateParticipant } from './CreateParticipant';
/**
* This class is used to update a Runner entity (via put request).
*/
export class UpdateRunner extends CreateParticipant {
/**
* The updated runner's id.
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
*/
@IsInt()
id: number;
/**
* The updated runner's new team/org.
* Just has to contain the group's id -everything else won't be checked or changed.
*/
@IsObject()
group: RunnerGroup;
/**
* Creates a Runner entity from this.
* Updates a provided Runner entity based on this.
*/
public async toRunner(): Promise<Runner> {
let newRunner: Runner = new Runner();
public async updateRunner(runner: Runner): Promise<Runner> {
runner.firstname = this.firstname;
runner.middlename = this.middlename;
runner.lastname = this.lastname;
runner.phone = this.phone;
runner.email = this.email;
runner.group = await this.getGroup();
runner.address = await this.getAddress();
newRunner.id = this.id;
newRunner.firstname = this.firstname;
newRunner.middlename = this.middlename;
newRunner.lastname = this.lastname;
newRunner.phone = this.phone;
newRunner.email = this.email;
newRunner.group = await this.getGroup();
newRunner.address = await this.getAddress();
return newRunner;
return runner;
}
/**
* Manages all the different ways a group can be provided.
* Loads the updated runner's group based on it's id.
*/
public async getGroup(): Promise<RunnerGroup> {
if (this.group === undefined) {
if (this.group === undefined || this.group === null) {
throw new RunnerTeamNeedsParentError();
}
if (!isNaN(this.group.id)) {

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 { CreateRunnerGroup } from './CreateRunnerGroup';
/**
* This class is used to update a RunnerTeam entity (via put request).
*/
export class UpdateRunnerTeam extends CreateRunnerGroup {
/**
* The updated team's id.
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
*/
@IsInt()
id: number;
/**
* The team's parent group (organisation).
* The updated team's parentGroup.
* Just has to contain the organisation's id - everything else won't be checked or changed.
*/
@IsObject()
@IsNotEmpty()
parentGroup: RunnerOrganisation;
/**
* Loads the updated teams's parentGroup based on it's id.
*/
public async getParent(): Promise<RunnerOrganisation> {
if (this.parentGroup === undefined) {
if (this.parentGroup === undefined || this.parentGroup === null) {
throw new RunnerTeamNeedsParentError();
}
if (!isNaN(this.parentGroup.id)) {
@ -35,16 +43,14 @@ export class UpdateRunnerTeam extends CreateRunnerGroup {
}
/**
* Creates a RunnerTeam entity from this.
* Updates a provided RunnerTeam entity based on this.
*/
public async toRunnerTeam(): Promise<RunnerTeam> {
let newRunnerTeam: RunnerTeam = new RunnerTeam();
public async updateRunnerTeam(team: RunnerTeam): Promise<RunnerTeam> {
newRunnerTeam.id = this.id;
newRunnerTeam.name = this.name;
newRunnerTeam.parentGroup = await this.getParent();
newRunnerTeam.contact = await this.getContact()
team.name = this.name;
team.parentGroup = await this.getParent();
team.contact = await this.getContact()
return newRunnerTeam;
return team;
}
}

View File

@ -3,14 +3,18 @@ import { IsBoolean, IsEmail, IsInt, IsOptional, IsPhoneNumber, IsString } from '
import { getConnectionManager } from 'typeorm';
import { config } from '../../config';
import { UsernameOrEmailNeededError } from '../../errors/AuthError';
import { RunnerGroupNotFoundError } from '../../errors/RunnerGroupErrors';
import { UserGroupNotFoundError } from '../../errors/UserGroupErrors';
import { User } from '../entities/User';
import { UserGroup } from '../entities/UserGroup';
/**
* This class is used to update a User entity (via put request).
*/
export class UpdateUser {
/**
* The updated users's id.
* The updated user's id.
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
*/
@IsInt()
id: number;
@ -23,7 +27,6 @@ export class UpdateUser {
/**
* The updated user's middle name.
* Optinal.
*/
@IsString()
@IsOptional()
@ -54,14 +57,16 @@ export class UpdateUser {
/**
* The updated user's phone number.
* Optional
* This will be validated against the configured country phone numer syntax (default: international).
*/
@IsPhoneNumber(config.phone_validation_countrycode)
@IsOptional()
phone?: string;
/**
* The new updated's password. Only provide if you want it updated.
* The new updated's password.
* Only provide it if you want it updated.
* Changeing the password will invalidate all of the user's jwts.
* This will of course not be saved in plaintext :)
*/
@IsString()
@ -75,20 +80,19 @@ export class UpdateUser {
enabled: boolean = true;
/**
* The new user's groups' id(s).
* You can provide either one groupId or an array of groupIDs.
* Optional.
* The updated user's groups.
* This just has to contain the group's id - everything else won't be changed.
*/
@IsOptional()
groups?: UserGroup[]
/**
* Creates a User entity from this.
* Updates a provided User entity based on this.
*/
public async toUser(user: User): Promise<User> {
public async updateUser(user: User): Promise<User> {
user.email = this.email;
user.username = this.username;
if (user.email === undefined && user.username === undefined) {
if ((user.email === undefined || user.email === null) && (user.username === undefined || user.username === null)) {
throw new UsernameOrEmailNeededError();
}
if (this.password) {
@ -107,6 +111,9 @@ export class UpdateUser {
return user;
}
/**
* Loads the updated user's groups based on their ids.
*/
public async getGroups() {
if (!this.groups) { return null; }
let groups = new Array<UserGroup>();
@ -115,7 +122,7 @@ export class UpdateUser {
}
for (let group of this.groups) {
let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group.id });
if (!found) { throw new RunnerGroupNotFoundError(); }
if (!found) { throw new UserGroupNotFoundError(); }
groups.push(found);
}
return groups;

View File

@ -10,7 +10,8 @@ import { Participant } from "./Participant";
import { RunnerOrganisation } from "./RunnerOrganisation";
/**
* Defines a address (to be used for contact information).
* Defines the Address entity.
* Implemented this way to prevent any formatting differences.
*/
@Entity()
export class Address {
@ -23,6 +24,7 @@ export class Address {
/**
* The address's description.
* Optional and mostly for UX.
*/
@Column({ nullable: true })
@IsString()
@ -49,6 +51,8 @@ export class Address {
/**
* The address's postal code.
* This will get checked against the postal code syntax for the configured country.
* TODO: Implement the config option.
*/
@Column()
@IsString()

View File

@ -4,19 +4,21 @@ import { Donation } from "./Donation";
import { Runner } from "./Runner";
/**
* Defines a distance based donation.
* Here people donate a certain amout per kilometer
* Defines the DistanceDonation entity.
* For distanceDonations a donor pledges to donate a certain amount for each kilometer ran by a runner.
*/
@ChildEntity()
export class DistanceDonation extends Donation {
/**
* The runner associated.
* The donation's associated runner.
* Used as the source of the donation's distance.
*/
@IsNotEmpty()
@ManyToOne(() => Runner, runner => runner.distanceDonations)
runner: Runner;
/**
* The donation's amount donated per distance.
* The amount the donor set to be donated per kilometer that the runner ran.
*/
@Column()
@ -26,12 +28,12 @@ export class DistanceDonation extends Donation {
/**
* The donation's amount in cents (or whatever your currency's smallest unit is.).
* The exact implementation may differ for each type of donation.
* Get's calculated from the runner's distance ran and the amount donated per kilometer.
*/
public get amount(): number {
let calculatedAmount = -1;
try {
calculatedAmount = this.amountPerDistance * this.runner.distance;
calculatedAmount = this.amountPerDistance * (this.runner.distance / 1000);
} catch (error) {
throw error;
}

View File

@ -6,7 +6,9 @@ import { Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typ
import { Participant } from "./Participant";
/**
* Defines the donation interface.
* Defines the Donation entity.
* A donation just associates a donor with a donation amount.
* The specifics of the amoun's determination has to be implemented in child classes.
*/
@Entity()
@TableInheritance({ column: { name: "type", type: "varchar" } })

View File

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

View File

@ -3,7 +3,8 @@ import { ChildEntity, Column } from "typeorm";
import { Donation } from "./Donation";
/**
* Defines a fixed donation.
* Defines the FixedDonation entity.
* In the past there was no easy way to track fixed donations (eg. for creating donation receipts).
*/
@ChildEntity()
export class FixedDonation extends Donation {

View File

@ -13,7 +13,8 @@ import { Address } from "./Address";
import { RunnerGroup } from "./RunnerGroup";
/**
* Defines a group's contact.
* Defines the GroupContact entity.
* Mainly it's own class to reduce duplicate code and enable contact's to be associated with multiple groups.
*/
@Entity()
export class GroupContact {
@ -34,7 +35,6 @@ export class GroupContact {
/**
* The contact's middle name.
* Optional
*/
@Column({ nullable: true })
@IsOptional()
@ -51,7 +51,7 @@ export class GroupContact {
/**
* The contact's address.
* Optional
* This is a address object to prevent any formatting differences.
*/
@IsOptional()
@ManyToOne(() => Address, address => address.participants, { nullable: true })
@ -59,7 +59,7 @@ export class GroupContact {
/**
* The contact's phone number.
* Optional
* This will be validated against the configured country phone numer syntax (default: international).
*/
@Column({ nullable: true })
@IsOptional()
@ -68,7 +68,7 @@ export class GroupContact {
/**
* The contact's email address.
* Optional
* Could later be used to automaticly send mails concerning the contact's associated groups.
*/
@Column({ nullable: true })
@IsOptional()

View File

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

View File

@ -8,7 +8,9 @@ import { PermissionAction } from '../enums/PermissionAction';
import { PermissionTarget } from '../enums/PermissionTargets';
import { Principal } from './Principal';
/**
* Defines the Permission interface.
* Defines the Permission entity.
* Permissions can be granted to principals.
* The permissions possible targets and actions are defined in enums.
*/
@Entity()
export class Permission {
@ -20,13 +22,14 @@ export class Permission {
id: number;
/**
* The permissions principal
* The permission's principal.
*/
@ManyToOne(() => Principal, principal => principal.permissions)
principal: Principal;
/**
* The target
* The permission's target.
* This get's stored as the enum value's string representation for compatability reasons.
*/
@Column({ type: 'varchar' })
@IsNotEmpty()
@ -34,14 +37,16 @@ export class Permission {
target: PermissionTarget;
/**
* The action type
* The permission's action.
* This get's stored as the enum value's string representation for compatability reasons.
*/
@Column({ type: 'varchar' })
@IsEnum(PermissionAction)
action: PermissionAction;
/**
* Turn this into a string for exporting (and jwts).
* Turn this into a string for exporting and jwts.
* Mainly used to shrink the size of jwts (otherwise the would contain entire objects).
*/
public toString(): string {
return this.target + ":" + this.action;

View File

@ -4,23 +4,27 @@ import { ResponsePrincipal } from '../responses/ResponsePrincipal';
import { Permission } from './Permission';
/**
* Defines a admin user.
* Defines the principal entity.
* A principal basicly is any entity that can receive permissions for the api (users and their groups).
*/
@Entity()
@TableInheritance({ column: { name: "type", type: "varchar" } })
export abstract class Principal {
/**
* autogenerated unique id (primary key).
* Autogenerated unique id (primary key).
*/
@PrimaryGeneratedColumn()
@IsInt()
id: number;
/**
* permissions
* The participant's permissions.
*/
@OneToMany(() => Permission, permission => permission.principal, { nullable: true })
permissions: Permission[];
/**
* Turns this entity into it's response class.
*/
public abstract toResponse(): ResponsePrincipal;
}

View File

@ -7,44 +7,52 @@ import { RunnerGroup } from "./RunnerGroup";
import { Scan } from "./Scan";
/**
* Defines a runner.
* Defines the runner entity.
* Runners differ from participants in being able to actually accumulate a ran distance through scans.
* Runner's get organized in groups.
*/
@ChildEntity()
export class Runner extends Participant {
/**
* The runner's associated group.
* Can be a runner team or organisation.
*/
@IsNotEmpty()
@ManyToOne(() => RunnerGroup, group => group.runners, { nullable: false })
group: RunnerGroup;
/**
* Used to link runners to donations.
* The runner's associated distanceDonations.
* Used to link runners to distanceDonations in order to calculate the donation's amount based on the distance the runner ran.
*/
@OneToMany(() => DistanceDonation, distanceDonation => distanceDonation.runner, { nullable: true })
distanceDonations: DistanceDonation[];
/**
* Used to link runners to cards.
* The runner's associated cards.
* Used to link runners to cards - yes a runner be associated with multiple cards this came in handy in the past.
*/
@OneToMany(() => RunnerCard, card => card.runner, { nullable: true })
cards: RunnerCard[];
/**
* Used to link runners to a scans
* The runner's associated scans.
* Used to link runners to scans (valid and fraudulant).
*/
@OneToMany(() => Scan, scan => scan.runner, { nullable: true })
scans: Scan[];
/**
* Returns all valid scans associated with this runner.
* This is implemented here to avoid duplicate code in other files.
*/
public get validScans(): Scan[] {
return this.scans.filter(scan => { scan.valid === true });
}
/**
* Returns the total distance ran by this runner.
* Returns the total distance ran by this runner based on all his valid scans.
* This is implemented here to avoid duplicate code in other files.
*/
@IsInt()
public get distance(): number {

View File

@ -11,7 +11,9 @@ import { Runner } from "./Runner";
import { TrackScan } from "./TrackScan";
/**
* Defines a card that can be scanned via a scanner station.
* Defines the RunnerCard entity.
* A runnerCard is a physical representation for a runner.
* It can be associated with a runner to create scans via the scan station's.
*/
@Entity()
export class RunnerCard {
@ -23,7 +25,8 @@ export class RunnerCard {
id: number;
/**
* The runner that is currently associated with this card.
* The card's currently associated runner.
* To increase reusability a card can be reassigned.
*/
@IsOptional()
@ManyToOne(() => Runner, runner => runner.cards, { nullable: true })
@ -32,7 +35,7 @@ export class RunnerCard {
/**
* The card's code.
* This has to be able to being converted to something barcode compatible.
* could theoretically be autogenerated
* Will get automaticlly generated (not implemented yet).
*/
@Column()
@IsEAN()
@ -49,7 +52,8 @@ export class RunnerCard {
enabled: boolean = true;
/**
* Used to link cards to a track scans.
* The card's associated scans.
* Used to link cards to track scans.
*/
@OneToMany(() => TrackScan, scan => scan.track, { nullable: true })
scans: TrackScan[];

View File

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

View File

@ -5,21 +5,22 @@ import { RunnerGroup } from "./RunnerGroup";
import { RunnerTeam } from "./RunnerTeam";
/**
* Defines a runner organisation (business or school for example).
* Defines the RunnerOrganisation entity.
* This usually is a school, club or company.
*/
@ChildEntity()
export class RunnerOrganisation extends RunnerGroup {
/**
* The organisations's address.
* Optional
*/
@IsOptional()
@ManyToOne(() => Address, address => address.groups, { nullable: true })
address?: Address;
/**
* Used to link teams to runner groups.
* The organisation's teams.
* Used to link teams to a organisation.
*/
@OneToMany(() => RunnerTeam, team => team.parentGroup, { nullable: true })
teams: RunnerTeam[];

View File

@ -4,14 +4,15 @@ import { RunnerGroup } from "./RunnerGroup";
import { RunnerOrganisation } from "./RunnerOrganisation";
/**
* Defines a runner team (class or deparment for example).
* Defines the RunnerTeam entity.
* This usually is a school class or department in a company.
*/
@ChildEntity()
export class RunnerTeam extends RunnerGroup {
/**
* The team's parent group.
* Optional
* Every team has to be part of a runnerOrganisation - this get's checked on creation and update.
*/
@IsNotEmpty()
@ManyToOne(() => RunnerOrganisation, org => org.teams, { nullable: true })

View File

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

View File

@ -10,7 +10,8 @@ import { Track } from "./Track";
import { TrackScan } from "./TrackScan";
/**
* ScannerStations have the ability to create scans for specific tracks.
* Defines the ScanStation entity.
* ScanStations get used to create TrackScans for runners based on a scan of their runnerCard.
*/
@Entity()
export class ScanStation {
@ -23,6 +24,7 @@ export class ScanStation {
/**
* The station's description.
* Mostly for better UX when traceing back stuff.
*/
@Column({ nullable: true })
@IsOptional()
@ -31,6 +33,7 @@ export class ScanStation {
/**
* The track this station is associated with.
* All scans created by this station will also be associated with this track.
*/
@IsNotEmpty()
@ManyToOne(() => Track, track => track.stations, { nullable: false })
@ -38,6 +41,7 @@ export class ScanStation {
/**
* The station's api key.
* This is used to authorize a station against the api (not implemented yet).
*/
@Column()
@IsNotEmpty()
@ -45,7 +49,7 @@ export class ScanStation {
key: string;
/**
* Is the station enabled (for fraud reasons)?
* Is the station enabled (for fraud and setup reasons)?
* Default: true
*/
@Column()

View File

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

View File

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

View File

@ -8,26 +8,29 @@ import { UserAction } from './UserAction';
import { UserGroup } from './UserGroup';
/**
* Defines a admin user.
* Defines the User entity.
* Users are the ones that can use the "admin" webui and do stuff in the backend.
*/
@ChildEntity()
export class User extends Principal {
/**
* uuid
* The user's uuid.
* Mainly gets used as a per-user salt for the password hash.
*/
@Column({ unique: true })
@IsUUID(4)
uuid: string;
/**
* user email
* The user's e-mail address.
* Either username or email has to be set (otherwise the user couldn't log in).
*/
@Column({ nullable: true, unique: true })
@IsEmail()
email?: string;
/**
* user phone
* The user's phone number.
*/
@Column({ nullable: true })
@IsOptional()
@ -35,14 +38,15 @@ export class User extends Principal {
phone?: string;
/**
* username
* The user's username.
* Either username or email has to be set (otherwise the user couldn't log in).
*/
@Column({ nullable: true, unique: true })
@IsString()
username?: string;
/**
* firstname
* The user's first name.
*/
@Column()
@IsString()
@ -50,7 +54,7 @@ export class User extends Principal {
firstname: string;
/**
* middlename
* The user's middle name.
*/
@Column({ nullable: true })
@IsString()
@ -58,7 +62,7 @@ export class User extends Principal {
middlename?: string;
/**
* lastname
* The user's last name.
*/
@Column()
@IsString()
@ -66,7 +70,8 @@ export class User extends Principal {
lastname: string;
/**
* password
* The user's password.
* This is a argon2 hash salted with the user's uuid.
*/
@Column()
@IsString()
@ -74,7 +79,8 @@ export class User extends Principal {
password: string;
/**
* groups
* The groups this user is a part of.
* The user will inherit the groups permissions (without overwriting his own).
*/
@IsOptional()
@ManyToMany(() => UserGroup, { nullable: true })
@ -82,21 +88,23 @@ export class User extends Principal {
groups: UserGroup[];
/**
* is user enabled?
* Is this user enabled?
*/
@Column()
@IsBoolean()
enabled: boolean = true;
/**
* jwt refresh count
* The user's jwt refresh token count.
* Used to invalidate jwts.
*/
@IsInt()
@Column({ default: 1 })
refreshTokenCount?: number;
/**
* profilepic
* The user's profile picture.
* We haven't decided yet if this will be a bas64 encoded image or just a link to the profile picture.
*/
@Column({ nullable: true, unique: true })
@IsString()
@ -104,14 +112,15 @@ export class User extends Principal {
profilePic?: string;
/**
* actions
* The actions performed by this user.
* For documentation purposes only, will be implemented later.
*/
@IsOptional()
@OneToMany(() => UserAction, action => action.user, { nullable: true })
actions: UserAction[]
/**
* Turn this into a response.
* Turns this entity into it's response class.
*/
public toResponse(): ResponsePrincipal {
return new ResponseUser(this);

View File

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

View File

@ -9,7 +9,8 @@ import { ResponseUserGroup } from '../responses/ResponseUserGroup';
import { Principal } from './Principal';
/**
* Defines the UserGroup interface.
* Defines the UserGroup entity.
* This entity describes a group of users with a set of permissions.
*/
@ChildEntity()
export class UserGroup extends Principal {
@ -30,6 +31,9 @@ export class UserGroup extends Principal {
@IsString()
description?: string;
/**
* Turns this entity into it's response class.
*/
public toResponse(): ResponsePrincipal {
return new ResponseUserGroup(this);
}

View File

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

View File

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

View File

@ -1,26 +1,29 @@
import { IsInt, IsString } from 'class-validator';
/**
* Defines a auth object
* Defines the repsonse auth.
*/
export class Auth {
/**
* access_token - JWT shortterm access token
* The access_token - JWT shortterm access token.
*/
@IsString()
access_token: string;
/**
* refresh_token - longterm refresh token (used for requesting new access tokens)
* The refresh_token - longterm refresh token (used for requesting new access tokens).
*/
@IsString()
refresh_token: string;
/**
* access_token_expires_at - unix timestamp of access token expiry
* The unix timestamp for access the token's expiry.
*/
@IsInt()
access_token_expires_at: number;
/**
* refresh_token_expires_at - unix timestamp of access token expiry
* The unix unix timestamp for the access token's expiry.
*/
@IsInt()
refresh_token_expires_at: number;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,6 +9,10 @@ import { UserGroup } from '../models/entities/UserGroup';
import { PermissionAction } from '../models/enums/PermissionAction';
import { PermissionTarget } from '../models/enums/PermissionTargets';
/**
* Seeds a admin group with a demo user into the database for initial setup and auto recovery.
* We know that the nameing isn't perfectly fitting. Feel free to change it.
*/
export default class SeedUsers implements Seeder {
public async run(factory: Factory, connection: Connection): Promise<any> {
let adminGroup: UserGroup = await this.createAdminGroup(connection);
@ -19,7 +23,7 @@ export default class SeedUsers implements Seeder {
public async createAdminGroup(connection: Connection) {
let adminGroup = new CreateUserGroup();
adminGroup.name = "ADMINS";
adminGroup.description = "Has all possible permissions";
adminGroup.description = "Have all possible permissions";
return await connection.getRepository(UserGroup).save(await adminGroup.toUserGroup());
}