Merge pull request 'latest work' (#20) from dev into main
Reviewed-on: #20 Reviewed-by: Nicolai Ort <info@nicolai-ort.com>
This commit was merged in pull request #20.
	This commit is contained in:
		
							
								
								
									
										12
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							@@ -7,6 +7,14 @@
 | 
			
		||||
    },
 | 
			
		||||
    "prettier.enable": false,
 | 
			
		||||
    "[typescript]": {
 | 
			
		||||
        "editor.defaultFormatter": "vscode.typescript-language-features"
 | 
			
		||||
    }
 | 
			
		||||
        "editor.defaultFormatter": "vscode.typescript-language-features",
 | 
			
		||||
        "editor.codeActionsOnSave": {
 | 
			
		||||
            "source.organizeImports": true,
 | 
			
		||||
            // "source.fixAll": true
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "javascript.preferences.quoteStyle": "single",
 | 
			
		||||
    "javascript.preferences.importModuleSpecifierEnding": "minimal",
 | 
			
		||||
    "typescript.preferences.importModuleSpecifierEnding": "minimal",
 | 
			
		||||
    "typescript.preferences.includePackageJsonAutoImports": "on"
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
FROM node:alpine
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
COPY ./package.json ./
 | 
			
		||||
RUN npm i
 | 
			
		||||
RUN yarn
 | 
			
		||||
COPY ./ ./
 | 
			
		||||
ENTRYPOINT [ "yarn","dev" ]
 | 
			
		||||
@@ -53,9 +53,11 @@ docker-compose up --build
 | 
			
		||||
 | 
			
		||||
## File Structure
 | 
			
		||||
 | 
			
		||||
- src/models/\* - database models (typeorm entities)
 | 
			
		||||
- src/models/entities\* - database models (typeorm entities)
 | 
			
		||||
- src/models/actions\* - actions models
 | 
			
		||||
- src/models/responses\* - response models
 | 
			
		||||
- src/controllers/\* - routing-controllers
 | 
			
		||||
- src/loaders/\* - loaders for the different init steps of the api server
 | 
			
		||||
- src/routes/\* - express routes for everything we don't do via routing-controllers (shouldn't be much)
 | 
			
		||||
- src/middlewares/\* - express middlewares (mainly auth r/n)
 | 
			
		||||
- src/errors/* - our custom (http) errors
 | 
			
		||||
- src/routes/\* - express routes for everything we don't do via routing-controllers (depreciated)
 | 
			
		||||
@@ -12,9 +12,12 @@ services:
 | 
			
		||||
      DB_USER: lfk
 | 
			
		||||
      DB_PASSWORD: changeme
 | 
			
		||||
      DB_NAME: lfk
 | 
			
		||||
      NODE_ENV: production
 | 
			
		||||
  backend_db:
 | 
			
		||||
    image: postgres:11-alpine
 | 
			
		||||
    environment:
 | 
			
		||||
      POSTGRES_DB: lfk
 | 
			
		||||
      POSTGRES_PASSWORD: changeme
 | 
			
		||||
      POSTGRES_USER: lfk
 | 
			
		||||
      POSTGRES_USER: lfk
 | 
			
		||||
    ports:
 | 
			
		||||
      - 5432:5432
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { config } from 'dotenv-safe';
 | 
			
		||||
import { config } from 'dotenv';
 | 
			
		||||
config();
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
@@ -8,5 +8,5 @@ export default {
 | 
			
		||||
	username: process.env.DB_USER,
 | 
			
		||||
	password: process.env.DB_PASSWORD,
 | 
			
		||||
	database: process.env.DB_NAME,
 | 
			
		||||
	entities: ["src/models/*.ts"]
 | 
			
		||||
	entities: ["src/models/entities/*.ts"]
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								package.json
									
									
									
									
									
								
							@@ -22,12 +22,14 @@
 | 
			
		||||
  ],
 | 
			
		||||
  "license": "CC-BY-NC-SA-4.0",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "argon2": "^0.27.0",
 | 
			
		||||
    "body-parser": "^1.19.0",
 | 
			
		||||
    "class-transformer": "^0.3.1",
 | 
			
		||||
    "class-validator": "^0.12.2",
 | 
			
		||||
    "class-validator-jsonschema": "^2.0.3",
 | 
			
		||||
    "consola": "^2.15.0",
 | 
			
		||||
    "cors": "^2.8.5",
 | 
			
		||||
    "dotenv": "^8.2.0",
 | 
			
		||||
    "express": "^4.17.1",
 | 
			
		||||
    "helmet": "^4.2.0",
 | 
			
		||||
    "jsonwebtoken": "^8.5.1",
 | 
			
		||||
@@ -39,17 +41,18 @@
 | 
			
		||||
    "routing-controllers-openapi": "^2.1.0",
 | 
			
		||||
    "swagger-ui-express": "^4.1.5",
 | 
			
		||||
    "typeorm": "^0.2.29",
 | 
			
		||||
    "typeorm-routing-controllers-extensions": "^0.2.0"
 | 
			
		||||
    "typeorm-routing-controllers-extensions": "^0.2.0",
 | 
			
		||||
    "uuid": "^8.3.1"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/cors": "^2.8.8",
 | 
			
		||||
    "@types/dotenv-safe": "^8.1.1",
 | 
			
		||||
    "@types/dotenv": "^8.2.0",
 | 
			
		||||
    "@types/express": "^4.17.9",
 | 
			
		||||
    "@types/jsonwebtoken": "^8.5.0",
 | 
			
		||||
    "@types/multer": "^1.4.4",
 | 
			
		||||
    "@types/node": "^14.14.9",
 | 
			
		||||
    "@types/swagger-ui-express": "^4.1.2",
 | 
			
		||||
    "dotenv-safe": "^8.2.0",
 | 
			
		||||
    "@types/uuid": "^8.3.0",
 | 
			
		||||
    "nodemon": "^2.0.6",
 | 
			
		||||
    "sqlite3": "^5.0.0",
 | 
			
		||||
    "ts-node": "^9.0.0",
 | 
			
		||||
@@ -61,4 +64,4 @@
 | 
			
		||||
    "build": "tsc",
 | 
			
		||||
    "docs": "typedoc --out docs src"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								src/app.ts
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								src/app.ts
									
									
									
									
									
								
							@@ -1,18 +1,15 @@
 | 
			
		||||
import "reflect-metadata";
 | 
			
		||||
import * as dotenvSafe from "dotenv-safe";
 | 
			
		||||
import { createExpressServer } from "routing-controllers";
 | 
			
		||||
import consola from "consola";
 | 
			
		||||
import loaders from "./loaders/index";
 | 
			
		||||
import "reflect-metadata";
 | 
			
		||||
import { createExpressServer } from "routing-controllers";
 | 
			
		||||
import authchecker from "./authchecker";
 | 
			
		||||
import { config, e as errors } from './config';
 | 
			
		||||
import loaders from "./loaders/index";
 | 
			
		||||
import { ErrorHandler } from './middlewares/ErrorHandler';
 | 
			
		||||
 | 
			
		||||
dotenvSafe.config();
 | 
			
		||||
const PORT = process.env.APP_PORT || 4010;
 | 
			
		||||
 | 
			
		||||
const app = createExpressServer({
 | 
			
		||||
  authorizationChecker: authchecker,
 | 
			
		||||
  middlewares: [ErrorHandler],
 | 
			
		||||
  development: process.env.NODE_ENV === "production",
 | 
			
		||||
  development: config.development,
 | 
			
		||||
  cors: true,
 | 
			
		||||
  routePrefix: "/api",
 | 
			
		||||
  controllers: [__dirname + "/controllers/*.ts"],
 | 
			
		||||
@@ -20,10 +17,15 @@ const app = createExpressServer({
 | 
			
		||||
 | 
			
		||||
async function main() {
 | 
			
		||||
  await loaders(app);
 | 
			
		||||
  app.listen(PORT, () => {
 | 
			
		||||
  app.listen(config.internal_port, () => {
 | 
			
		||||
    consola.success(
 | 
			
		||||
      `⚡️[server]: Server is running at http://localhost:${PORT}`
 | 
			
		||||
      `⚡️[server]: Server is running at http://localhost:${config.internal_port}`
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
main();
 | 
			
		||||
if (errors === 0) {
 | 
			
		||||
  main();
 | 
			
		||||
} else {
 | 
			
		||||
  console.log("error");
 | 
			
		||||
  // something's wrong
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,9 @@
 | 
			
		||||
import * as jwt from "jsonwebtoken";
 | 
			
		||||
import { Action, HttpError } from "routing-controllers";
 | 
			
		||||
// -----------
 | 
			
		||||
const sampletoken = jwt.sign({
 | 
			
		||||
    "permissions": {
 | 
			
		||||
        "TRACKS": ["read", "update", "delete", "add"]
 | 
			
		||||
        // "TRACKS": []
 | 
			
		||||
    }
 | 
			
		||||
}, process.env.JWT_SECRET || "secretjwtsecret")
 | 
			
		||||
console.log(`sampletoken: ${sampletoken}`);
 | 
			
		||||
import { Action } from "routing-controllers";
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { config } from './config';
 | 
			
		||||
import { IllegalJWTError, NoPermissionError, UserNonexistantOrRefreshtokenInvalidError } from './errors/AuthError';
 | 
			
		||||
import { User } from './models/entities/User';
 | 
			
		||||
// -----------
 | 
			
		||||
const authchecker = async (action: Action, permissions: string | string[]) => {
 | 
			
		||||
    let required_permissions = undefined
 | 
			
		||||
@@ -20,9 +16,14 @@ const authchecker = async (action: Action, permissions: string | string[]) => {
 | 
			
		||||
    const provided_token = action.request.query["auth"];
 | 
			
		||||
    let jwtPayload = undefined
 | 
			
		||||
    try {
 | 
			
		||||
        jwtPayload = <any>jwt.verify(provided_token, process.env.JWT_SECRET || "secretjwtsecret");
 | 
			
		||||
        jwtPayload = <any>jwt.verify(provided_token, config.jwt_secret);
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        throw new HttpError(401, "jwt_illegal")
 | 
			
		||||
        console.log(error);
 | 
			
		||||
        throw new IllegalJWTError()
 | 
			
		||||
    }
 | 
			
		||||
    const count = await getConnectionManager().get().getRepository(User).count({ id: jwtPayload["userdetails"]["id"], refreshTokenCount: jwtPayload["userdetails"]["refreshTokenCount"] })
 | 
			
		||||
    if (count !== 1) {
 | 
			
		||||
        throw new UserNonexistantOrRefreshtokenInvalidError()
 | 
			
		||||
    }
 | 
			
		||||
    if (jwtPayload.permissions) {
 | 
			
		||||
        action.response.local = {}
 | 
			
		||||
@@ -34,15 +35,15 @@ const authchecker = async (action: Action, permissions: string | string[]) => {
 | 
			
		||||
            if (actual_accesslevel_for_permission.includes(permission_access_level)) {
 | 
			
		||||
                return true;
 | 
			
		||||
            } else {
 | 
			
		||||
                throw new HttpError(403, "no")
 | 
			
		||||
                throw new NoPermissionError()
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    } else {
 | 
			
		||||
        throw new HttpError(403, "no")
 | 
			
		||||
        throw new NoPermissionError()
 | 
			
		||||
    }
 | 
			
		||||
    // 
 | 
			
		||||
    try {
 | 
			
		||||
        jwt.verify(provided_token, process.env.JWT_SECRET || "secretjwtsecret");
 | 
			
		||||
        jwt.verify(provided_token, config.jwt_secret);
 | 
			
		||||
        return true
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        return false
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								src/config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/config.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
import { config as configDotenv } from 'dotenv';
 | 
			
		||||
configDotenv();
 | 
			
		||||
export const config = {
 | 
			
		||||
    internal_port: parseInt(process.env.APP_PORT) || 4010,
 | 
			
		||||
    development: process.env.NODE_ENV === "production",
 | 
			
		||||
    jwt_secret: process.env.JWT_SECRET || "secretjwtsecret",
 | 
			
		||||
    phone_validation_countrycode: process.env.PHONE_COUNTRYCODE || "ZZ"
 | 
			
		||||
}
 | 
			
		||||
let errors = 0
 | 
			
		||||
if (typeof config.internal_port !== "number") {
 | 
			
		||||
    errors++
 | 
			
		||||
}
 | 
			
		||||
if (typeof config.phone_validation_countrycode !== "string") {
 | 
			
		||||
    errors++
 | 
			
		||||
}
 | 
			
		||||
if (config.phone_validation_countrycode.length !== 2) {
 | 
			
		||||
    errors++
 | 
			
		||||
}
 | 
			
		||||
if (typeof config.development !== "boolean") {
 | 
			
		||||
    errors++
 | 
			
		||||
}
 | 
			
		||||
export let e = errors
 | 
			
		||||
							
								
								
									
										68
									
								
								src/controllers/AuthController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/controllers/AuthController.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
import { Body, JsonController, Post } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { IllegalJWTError, InvalidCredentialsError, JwtNotProvidedError, PasswordNeededError, RefreshTokenCountInvalidError, UsernameOrEmailNeededError } from '../errors/AuthError';
 | 
			
		||||
import { UserNotFoundError } from '../errors/UserErrors';
 | 
			
		||||
import { CreateAuth } from '../models/actions/CreateAuth';
 | 
			
		||||
import { HandleLogout } from '../models/actions/HandleLogout';
 | 
			
		||||
import { RefreshAuth } from '../models/actions/RefreshAuth';
 | 
			
		||||
import { Auth } from '../models/responses/ResponseAuth';
 | 
			
		||||
import { Logout } from '../models/responses/ResponseLogout';
 | 
			
		||||
 | 
			
		||||
@JsonController('/auth')
 | 
			
		||||
export class AuthController {
 | 
			
		||||
	constructor() {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post("/login")
 | 
			
		||||
	@ResponseSchema(Auth)
 | 
			
		||||
	@ResponseSchema(InvalidCredentialsError)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError)
 | 
			
		||||
	@ResponseSchema(UsernameOrEmailNeededError)
 | 
			
		||||
	@ResponseSchema(PasswordNeededError)
 | 
			
		||||
	@ResponseSchema(InvalidCredentialsError)
 | 
			
		||||
	@OpenAPI({ description: 'Create a new access token object' })
 | 
			
		||||
	async login(@Body({ validate: true }) createAuth: CreateAuth) {
 | 
			
		||||
		let auth;
 | 
			
		||||
		try {
 | 
			
		||||
			auth = await createAuth.toAuth();
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
		return auth
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post("/logout")
 | 
			
		||||
	@ResponseSchema(Logout)
 | 
			
		||||
	@ResponseSchema(InvalidCredentialsError)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError)
 | 
			
		||||
	@ResponseSchema(UsernameOrEmailNeededError)
 | 
			
		||||
	@ResponseSchema(PasswordNeededError)
 | 
			
		||||
	@ResponseSchema(InvalidCredentialsError)
 | 
			
		||||
	@OpenAPI({ description: 'Create a new access token object' })
 | 
			
		||||
	async logout(@Body({ validate: true }) handleLogout: HandleLogout) {
 | 
			
		||||
		let logout;
 | 
			
		||||
		try {
 | 
			
		||||
			logout = await handleLogout.logout()
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			return error;
 | 
			
		||||
		}
 | 
			
		||||
		return logout
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post("/refresh")
 | 
			
		||||
	@ResponseSchema(Auth)
 | 
			
		||||
	@ResponseSchema(JwtNotProvidedError)
 | 
			
		||||
	@ResponseSchema(IllegalJWTError)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError)
 | 
			
		||||
	@ResponseSchema(RefreshTokenCountInvalidError)
 | 
			
		||||
	@OpenAPI({ description: 'refresh a access token' })
 | 
			
		||||
	async refresh(@Body({ validate: true }) refreshAuth: RefreshAuth) {
 | 
			
		||||
		let auth;
 | 
			
		||||
		try {
 | 
			
		||||
			auth = await refreshAuth.toAuth();
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			return error;
 | 
			
		||||
		}
 | 
			
		||||
		return auth
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										102
									
								
								src/controllers/RunnerController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								src/controllers/RunnerController.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
			
		||||
import { 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 { RunnerGroupNeededError, RunnerIdsNotMatchingError, RunnerNotFoundError } from '../errors/RunnerErrors';
 | 
			
		||||
import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors';
 | 
			
		||||
import { CreateRunner } from '../models/actions/CreateRunner';
 | 
			
		||||
import { Runner } from '../models/entities/Runner';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
import { ResponseRunner } from '../models/responses/ResponseRunner';
 | 
			
		||||
 | 
			
		||||
@JsonController('/runners')
 | 
			
		||||
//@Authorized('RUNNERS:read')
 | 
			
		||||
export class RunnerController {
 | 
			
		||||
	private runnerRepository: Repository<Runner>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets the repository of this controller's model/entity.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.runnerRepository = getConnectionManager().get().getRepository(Runner);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get()
 | 
			
		||||
	@ResponseSchema(ResponseRunner, { isArray: true })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all runners.' })
 | 
			
		||||
	async getAll() {
 | 
			
		||||
		let responseRunners: ResponseRunner[] = new Array<ResponseRunner>();
 | 
			
		||||
		const runners = await this.runnerRepository.find({ relations: ['scans', 'group'] });
 | 
			
		||||
		console.log(runners);
 | 
			
		||||
		runners.forEach(runner => {
 | 
			
		||||
			responseRunners.push(new ResponseRunner(runner));
 | 
			
		||||
		});
 | 
			
		||||
		return responseRunners;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id')
 | 
			
		||||
	@ResponseSchema(ResponseRunner)
 | 
			
		||||
	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(RunnerNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Returns a runner of a specified id (if it exists)' })
 | 
			
		||||
	async getOne(@Param('id') id: number) {
 | 
			
		||||
		let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] })
 | 
			
		||||
		if (!runner) { throw new RunnerNotFoundError(); }
 | 
			
		||||
		return new ResponseRunner(runner);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post()
 | 
			
		||||
	@ResponseSchema(ResponseRunner)
 | 
			
		||||
	@ResponseSchema(RunnerGroupNeededError)
 | 
			
		||||
	@ResponseSchema(RunnerGroupNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Create a new runner object (id will be generated automagicly).' })
 | 
			
		||||
	async post(@Body({ validate: true }) createRunner: CreateRunner) {
 | 
			
		||||
		let runner;
 | 
			
		||||
		try {
 | 
			
		||||
			runner = await createRunner.toRunner();
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		runner = await this.runnerRepository.save(runner)
 | 
			
		||||
		return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group'] }));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
	@ResponseSchema(ResponseRunner)
 | 
			
		||||
	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(RunnerIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Update a runner object (id can't be changed)." })
 | 
			
		||||
	async put(@Param('id') id: number, @EntityFromBody() runner: Runner) {
 | 
			
		||||
		let oldRunner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] });
 | 
			
		||||
 | 
			
		||||
		if (!oldRunner) {
 | 
			
		||||
			throw new RunnerNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (oldRunner.id != runner.id) {
 | 
			
		||||
			throw new RunnerIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.runnerRepository.update(oldRunner, runner);
 | 
			
		||||
		return new ResponseRunner(runner);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@ResponseSchema(ResponseRunner)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@OpenAPI({ description: 'Delete a specified runner (if it exists).' })
 | 
			
		||||
	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
 | 
			
		||||
		let runner = await this.runnerRepository.findOne({ id: id });
 | 
			
		||||
		if (!runner) { return null; }
 | 
			
		||||
		const responseRunner = await this.runnerRepository.findOne(runner, { relations: ['scans', 'group'] });
 | 
			
		||||
 | 
			
		||||
		if (!runner) {
 | 
			
		||||
			throw new RunnerNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.runnerRepository.delete(runner);
 | 
			
		||||
		return new ResponseRunner(responseRunner);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										124
									
								
								src/controllers/RunnerOrganisationController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								src/controllers/RunnerOrganisationController.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,124 @@
 | 
			
		||||
import { 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 { RunnerOrganisation } from '../models/entities/RunnerOrganisation';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
import { ResponseRunnerOrganisation } from '../models/responses/ResponseRunnerOrganisation';
 | 
			
		||||
import { RunnerController } from './RunnerController';
 | 
			
		||||
import { RunnerTeamController } from './RunnerTeamController';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@JsonController('/organisations')
 | 
			
		||||
//@Authorized('RUNNERS:read')
 | 
			
		||||
export class RunnerOrganisationController {
 | 
			
		||||
	private runnerOrganisationRepository: Repository<RunnerOrganisation>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets the repository of this controller's model/entity.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.runnerOrganisationRepository = getConnectionManager().get().getRepository(RunnerOrganisation);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get()
 | 
			
		||||
	@ResponseSchema(ResponseRunnerOrganisation, { isArray: true })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all runnerOrganisations.' })
 | 
			
		||||
	async getAll() {
 | 
			
		||||
		let responseTeams: ResponseRunnerOrganisation[] = new Array<ResponseRunnerOrganisation>();
 | 
			
		||||
		const runners = await this.runnerOrganisationRepository.find({ relations: ['address', 'contact', 'teams'] });
 | 
			
		||||
		console.log(runners);
 | 
			
		||||
		runners.forEach(runner => {
 | 
			
		||||
			responseTeams.push(new ResponseRunnerOrganisation(runner));
 | 
			
		||||
		});
 | 
			
		||||
		return responseTeams;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id')
 | 
			
		||||
	@ResponseSchema(ResponseRunnerOrganisation)
 | 
			
		||||
	@ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(RunnerOrganisationNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Returns a runnerOrganisation of a specified id (if it exists)' })
 | 
			
		||||
	async getOne(@Param('id') id: number) {
 | 
			
		||||
		let runnerOrg = await this.runnerOrganisationRepository.findOne({ id: id }, { relations: ['address', 'contact', 'teams'] });
 | 
			
		||||
		if (!runnerOrg) { throw new RunnerOrganisationNotFoundError(); }
 | 
			
		||||
		return new ResponseRunnerOrganisation(runnerOrg);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post()
 | 
			
		||||
	@ResponseSchema(ResponseRunnerOrganisation)
 | 
			
		||||
	@OpenAPI({ description: 'Create a new runnerOrganisation object (id will be generated automagicly).' })
 | 
			
		||||
	async post(@Body({ validate: true }) createRunnerOrganisation: CreateRunnerOrganisation) {
 | 
			
		||||
		let runnerOrganisation;
 | 
			
		||||
		try {
 | 
			
		||||
			runnerOrganisation = await createRunnerOrganisation.toRunnerOrganisation();
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		runnerOrganisation = await this.runnerOrganisationRepository.save(runnerOrganisation);
 | 
			
		||||
 | 
			
		||||
		return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(runnerOrganisation, { relations: ['address', 'contact', 'teams'] }));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
	@ResponseSchema(ResponseRunnerOrganisation)
 | 
			
		||||
	@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) {
 | 
			
		||||
		let oldRunnerOrganisation = await this.runnerOrganisationRepository.findOne({ id: id });
 | 
			
		||||
 | 
			
		||||
		if (!oldRunnerOrganisation) {
 | 
			
		||||
			throw new RunnerOrganisationNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (oldRunnerOrganisation.id != runnerOrganisation.id) {
 | 
			
		||||
			throw new RunnerOrganisationIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.runnerOrganisationRepository.update(oldRunnerOrganisation, runnerOrganisation);
 | 
			
		||||
 | 
			
		||||
		runnerOrganisation = await this.runnerOrganisationRepository.findOne(runnerOrganisation, { relations: ['address', 'contact', 'teams'] });
 | 
			
		||||
		return new ResponseRunnerOrganisation(runnerOrganisation);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@ResponseSchema(ResponseRunnerOrganisation)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@ResponseSchema(RunnerOrganisationHasTeamsError, { statusCode: 406 })
 | 
			
		||||
	@ResponseSchema(RunnerOrganisationHasRunnersError, { statusCode: 406 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@OpenAPI({ description: 'Delete a specified runnerOrganisation (if it exists).' })
 | 
			
		||||
	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
 | 
			
		||||
		let organisation = await this.runnerOrganisationRepository.findOne({ id: id });
 | 
			
		||||
		if (!organisation) { return null; }
 | 
			
		||||
		let runnerOrganisation = await this.runnerOrganisationRepository.findOne(organisation, { relations: ['address', 'contact', 'runners', 'teams'] });
 | 
			
		||||
 | 
			
		||||
		if (!force) {
 | 
			
		||||
			if (runnerOrganisation.teams.length != 0) {
 | 
			
		||||
				throw new RunnerOrganisationHasTeamsError();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		const teamController = new RunnerTeamController()
 | 
			
		||||
		for (let team of runnerOrganisation.teams) {
 | 
			
		||||
			await teamController.remove(team.id, true);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (!force) {
 | 
			
		||||
			if (runnerOrganisation.runners.length != 0) {
 | 
			
		||||
				throw new RunnerOrganisationHasRunnersError();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		const runnerController = new RunnerController()
 | 
			
		||||
		for (let runner of runnerOrganisation.runners) {
 | 
			
		||||
			await runnerController.remove(runner.id, true);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const responseOrganisation = new ResponseRunnerOrganisation(runnerOrganisation);
 | 
			
		||||
		await this.runnerOrganisationRepository.delete(organisation);
 | 
			
		||||
		return responseOrganisation;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										113
									
								
								src/controllers/RunnerTeamController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								src/controllers/RunnerTeamController.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
			
		||||
import { 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 { RunnerTeamHasRunnersError, RunnerTeamIdsNotMatchingError, RunnerTeamNotFoundError } from '../errors/RunnerTeamErrors';
 | 
			
		||||
import { CreateRunnerTeam } from '../models/actions/CreateRunnerTeam';
 | 
			
		||||
import { RunnerTeam } from '../models/entities/RunnerTeam';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
import { ResponseRunnerTeam } from '../models/responses/ResponseRunnerTeam';
 | 
			
		||||
import { RunnerController } from './RunnerController';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@JsonController('/teams')
 | 
			
		||||
//@Authorized('RUNNERS:read')
 | 
			
		||||
export class RunnerTeamController {
 | 
			
		||||
	private runnerTeamRepository: Repository<RunnerTeam>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets the repository of this controller's model/entity.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.runnerTeamRepository = getConnectionManager().get().getRepository(RunnerTeam);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get()
 | 
			
		||||
	@ResponseSchema(ResponseRunnerTeam, { isArray: true })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all runnerTeams.' })
 | 
			
		||||
	async getAll() {
 | 
			
		||||
		let responseTeams: ResponseRunnerTeam[] = new Array<ResponseRunnerTeam>();
 | 
			
		||||
		const runners = await this.runnerTeamRepository.find({ relations: ['parentGroup', 'contact'] });
 | 
			
		||||
		console.log(runners);
 | 
			
		||||
		runners.forEach(runner => {
 | 
			
		||||
			responseTeams.push(new ResponseRunnerTeam(runner));
 | 
			
		||||
		});
 | 
			
		||||
		return responseTeams;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id')
 | 
			
		||||
	@ResponseSchema(ResponseRunnerTeam)
 | 
			
		||||
	@ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(RunnerTeamNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Returns a runnerTeam of a specified id (if it exists)' })
 | 
			
		||||
	async getOne(@Param('id') id: number) {
 | 
			
		||||
		let runnerTeam = await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact'] });
 | 
			
		||||
		if (!runnerTeam) { throw new RunnerTeamNotFoundError(); }
 | 
			
		||||
		return new ResponseRunnerTeam(runnerTeam);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post()
 | 
			
		||||
	@ResponseSchema(ResponseRunnerTeam)
 | 
			
		||||
	@OpenAPI({ description: 'Create a new runnerTeam object (id will be generated automagicly).' })
 | 
			
		||||
	async post(@Body({ validate: true }) createRunnerTeam: CreateRunnerTeam) {
 | 
			
		||||
		let runnerTeam;
 | 
			
		||||
		try {
 | 
			
		||||
			runnerTeam = await createRunnerTeam.toRunnerTeam();
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		runnerTeam = await this.runnerTeamRepository.save(runnerTeam);
 | 
			
		||||
		runnerTeam = await this.runnerTeamRepository.findOne(runnerTeam, { relations: ['parentGroup', 'contact'] });
 | 
			
		||||
 | 
			
		||||
		return new ResponseRunnerTeam(runnerTeam);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
	@ResponseSchema(ResponseRunnerTeam)
 | 
			
		||||
	@ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(RunnerTeamIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Update a runnerTeam object (id can't be changed)." })
 | 
			
		||||
	async put(@Param('id') id: number, @EntityFromBody() runnerTeam: RunnerTeam) {
 | 
			
		||||
		let oldRunnerTeam = await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact'] });
 | 
			
		||||
 | 
			
		||||
		if (!oldRunnerTeam) {
 | 
			
		||||
			throw new RunnerTeamNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (oldRunnerTeam.id != runnerTeam.id) {
 | 
			
		||||
			throw new RunnerTeamIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.runnerTeamRepository.update(oldRunnerTeam, runnerTeam);
 | 
			
		||||
 | 
			
		||||
		runnerTeam = await this.runnerTeamRepository.findOne(runnerTeam, { relations: ['parentGroup', 'contact'] });
 | 
			
		||||
		return new ResponseRunnerTeam(runnerTeam);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@ResponseSchema(ResponseRunnerTeam)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@ResponseSchema(RunnerTeamHasRunnersError, { statusCode: 406 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@OpenAPI({ description: 'Delete a specified runnerTeam (if it exists).' })
 | 
			
		||||
	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
 | 
			
		||||
		let team = await this.runnerTeamRepository.findOne({ id: id });
 | 
			
		||||
		if (!team) { return null; }
 | 
			
		||||
		let runnerTeam = await this.runnerTeamRepository.findOne(team, { relations: ['parentGroup', 'contact', 'runners'] });
 | 
			
		||||
 | 
			
		||||
		if (!force) {
 | 
			
		||||
			if (runnerTeam.runners.length != 0) {
 | 
			
		||||
				throw new RunnerTeamHasRunnersError();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		const runnerController = new RunnerController()
 | 
			
		||||
		for (let runner of runnerTeam.runners) {
 | 
			
		||||
			await runnerController.remove(runner.id, true);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const responseTeam = new ResponseRunnerTeam(runnerTeam);
 | 
			
		||||
		await this.runnerTeamRepository.delete(team);
 | 
			
		||||
		return responseTeam;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,23 +1,15 @@
 | 
			
		||||
import { JsonController, Param, Body, Get, Post, Put, Delete, NotFoundError, OnUndefined, NotAcceptableError, Authorized } from 'routing-controllers';
 | 
			
		||||
import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { getConnectionManager, Repository } from 'typeorm';
 | 
			
		||||
import { EntityFromBody } from 'typeorm-routing-controllers-extensions';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { Track } from '../models/Track';
 | 
			
		||||
import { IsInt, IsNotEmpty, IsPositive, IsString } from 'class-validator';
 | 
			
		||||
import {TrackIdsNotMatchingError, TrackNotFoundError} from "../errors/TrackErrors";
 | 
			
		||||
 | 
			
		||||
class CreateTrack {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	@IsNotEmpty()
 | 
			
		||||
	name: string;
 | 
			
		||||
 | 
			
		||||
	@IsInt()
 | 
			
		||||
	@IsPositive()
 | 
			
		||||
	length: number;
 | 
			
		||||
}
 | 
			
		||||
import { TrackIdsNotMatchingError, TrackNotFoundError } from "../errors/TrackErrors";
 | 
			
		||||
import { CreateTrack } from '../models/actions/CreateTrack';
 | 
			
		||||
import { Track } from '../models/entities/Track';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
import { ResponseTrack } from '../models/responses/ResponseTrack';
 | 
			
		||||
 | 
			
		||||
@JsonController('/tracks')
 | 
			
		||||
@Authorized("TRACKS:read")
 | 
			
		||||
//@Authorized("TRACKS:read")
 | 
			
		||||
export class TrackController {
 | 
			
		||||
	private trackRepository: Repository<Track>;
 | 
			
		||||
 | 
			
		||||
@@ -29,36 +21,43 @@ export class TrackController {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get()
 | 
			
		||||
	@ResponseSchema(Track, { isArray: true })
 | 
			
		||||
	@ResponseSchema(ResponseTrack, { isArray: true })
 | 
			
		||||
	@OpenAPI({ description: "Lists all tracks." })
 | 
			
		||||
	getAll() {
 | 
			
		||||
		return this.trackRepository.find();
 | 
			
		||||
	async getAll() {
 | 
			
		||||
		let responseTracks: ResponseTrack[] = new Array<ResponseTrack>();
 | 
			
		||||
		const tracks = await this.trackRepository.find();
 | 
			
		||||
		tracks.forEach(track => {
 | 
			
		||||
			responseTracks.push(new ResponseTrack(track));
 | 
			
		||||
		});
 | 
			
		||||
		return responseTracks;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id')
 | 
			
		||||
	@ResponseSchema(Track)
 | 
			
		||||
	@ResponseSchema(TrackNotFoundError, {statusCode: 404})
 | 
			
		||||
	@ResponseSchema(ResponseTrack)
 | 
			
		||||
	@ResponseSchema(TrackNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(TrackNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: "Returns a track of a specified id (if it exists)" })
 | 
			
		||||
	getOne(@Param('id') id: number) {
 | 
			
		||||
		return this.trackRepository.findOne({ id: id });
 | 
			
		||||
	async getOne(@Param('id') id: number) {
 | 
			
		||||
		let track = await this.trackRepository.findOne({ id: id });
 | 
			
		||||
		if (!track) { throw new TrackNotFoundError(); }
 | 
			
		||||
		return new ResponseTrack(track);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post()
 | 
			
		||||
	@ResponseSchema(Track)
 | 
			
		||||
	@ResponseSchema(ResponseTrack)
 | 
			
		||||
	@OpenAPI({ description: "Create a new track object (id will be generated automagicly)." })
 | 
			
		||||
	post(
 | 
			
		||||
	async post(
 | 
			
		||||
		@Body({ validate: true })
 | 
			
		||||
		track: CreateTrack
 | 
			
		||||
	) {
 | 
			
		||||
		return this.trackRepository.save(track);
 | 
			
		||||
		return new ResponseTrack(await this.trackRepository.save(track.toTrack()));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
	@ResponseSchema(Track)
 | 
			
		||||
	@ResponseSchema(TrackNotFoundError, {statusCode: 404})
 | 
			
		||||
	@ResponseSchema(TrackIdsNotMatchingError, {statusCode: 406})
 | 
			
		||||
	@OpenAPI({description: "Update a track object (id can't be changed)."})
 | 
			
		||||
	@ResponseSchema(ResponseTrack)
 | 
			
		||||
	@ResponseSchema(TrackNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(TrackIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Update a track object (id can't be changed)." })
 | 
			
		||||
	async put(@Param('id') id: number, @EntityFromBody() track: Track) {
 | 
			
		||||
		let oldTrack = await this.trackRepository.findOne({ id: id });
 | 
			
		||||
 | 
			
		||||
@@ -66,26 +65,24 @@ export class TrackController {
 | 
			
		||||
			throw new TrackNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if(oldTrack.id != track.id){
 | 
			
		||||
		if (oldTrack.id != track.id) {
 | 
			
		||||
			throw new TrackIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.trackRepository.update(oldTrack, track);
 | 
			
		||||
		return track;
 | 
			
		||||
		return new ResponseTrack(track);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@ResponseSchema(Track)
 | 
			
		||||
	@ResponseSchema(TrackNotFoundError, {statusCode: 404})
 | 
			
		||||
	@OpenAPI({description: "Delete a specified track (if it exists)."})
 | 
			
		||||
	async remove(@Param('id') id: number) {
 | 
			
		||||
	@ResponseSchema(ResponseTrack)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@OpenAPI({ description: "Delete a specified track (if it exists)." })
 | 
			
		||||
	async remove(@Param("id") id: number) {
 | 
			
		||||
		let track = await this.trackRepository.findOne({ id: id });
 | 
			
		||||
 | 
			
		||||
		if (!track) {
 | 
			
		||||
			throw new TrackNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
		if (!track) { return null; }
 | 
			
		||||
 | 
			
		||||
		await this.trackRepository.delete(track);
 | 
			
		||||
		return track;
 | 
			
		||||
		return new ResponseTrack(track);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										88
									
								
								src/controllers/UserController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								src/controllers/UserController.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
			
		||||
import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { getConnectionManager, Repository } from 'typeorm';
 | 
			
		||||
import { EntityFromBody } from 'typeorm-routing-controllers-extensions';
 | 
			
		||||
import { UserIdsNotMatchingError, UserNotFoundError } from '../errors/UserErrors';
 | 
			
		||||
import { UserGroupNotFoundError } from '../errors/UserGroupErrors';
 | 
			
		||||
import { CreateUser } from '../models/actions/CreateUser';
 | 
			
		||||
import { User } from '../models/entities/User';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@JsonController('/users')
 | 
			
		||||
export class UserController {
 | 
			
		||||
	private userRepository: Repository<User>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets the repository of this controller's model/entity.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.userRepository = getConnectionManager().get().getRepository(User);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get()
 | 
			
		||||
	@ResponseSchema(User, { isArray: true })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all users.' })
 | 
			
		||||
	getAll() {
 | 
			
		||||
		return this.userRepository.find();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id')
 | 
			
		||||
	@ResponseSchema(User)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(UserNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Returns a user of a specified id (if it exists)' })
 | 
			
		||||
	getOne(@Param('id') id: number) {
 | 
			
		||||
		return this.userRepository.findOne({ id: id });
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post()
 | 
			
		||||
	@ResponseSchema(User)
 | 
			
		||||
	@ResponseSchema(UserGroupNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Create a new user object (id will be generated automagicly).' })
 | 
			
		||||
	async post(@Body({ validate: true }) createUser: CreateUser) {
 | 
			
		||||
		let user;
 | 
			
		||||
		try {
 | 
			
		||||
			user = await createUser.toUser();
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return this.userRepository.save(user);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
	@ResponseSchema(User)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Update a user object (id can't be changed)." })
 | 
			
		||||
	async put(@Param('id') id: number, @EntityFromBody() user: User) {
 | 
			
		||||
		let oldUser = await this.userRepository.findOne({ id: id });
 | 
			
		||||
 | 
			
		||||
		if (!oldUser) {
 | 
			
		||||
			throw new UserNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (oldUser.id != user.id) {
 | 
			
		||||
			throw new UserIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.userRepository.update(oldUser, user);
 | 
			
		||||
		return user;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@ResponseSchema(User)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@OpenAPI({ description: 'Delete a specified runner (if it exists).' })
 | 
			
		||||
	async remove(@Param("id") id: number) {
 | 
			
		||||
		let user = await this.userRepository.findOne({ id: id });
 | 
			
		||||
		if (!user) {
 | 
			
		||||
			return null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.userRepository.delete(user);
 | 
			
		||||
		return user;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										87
									
								
								src/controllers/UserGroupController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src/controllers/UserGroupController.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
			
		||||
import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { getConnectionManager, Repository } from 'typeorm';
 | 
			
		||||
import { EntityFromBody } from 'typeorm-routing-controllers-extensions';
 | 
			
		||||
import { UserGroupIdsNotMatchingError, UserGroupNotFoundError } from '../errors/UserGroupErrors';
 | 
			
		||||
import { CreateUserGroup } from '../models/actions/CreateUserGroup';
 | 
			
		||||
import { UserGroup } from '../models/entities/UserGroup';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@JsonController('/usergroups')
 | 
			
		||||
export class UserGroupController {
 | 
			
		||||
	private userGroupsRepository: Repository<UserGroup>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets the repository of this controller's model/entity.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.userGroupsRepository = getConnectionManager().get().getRepository(UserGroup);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get()
 | 
			
		||||
	@ResponseSchema(UserGroup, { isArray: true })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all usergroups.' })
 | 
			
		||||
	getAll() {
 | 
			
		||||
		return this.userGroupsRepository.find();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id')
 | 
			
		||||
	@ResponseSchema(UserGroup)
 | 
			
		||||
	@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(UserGroupNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Returns a usergroup of a specified id (if it exists)' })
 | 
			
		||||
	getOne(@Param('id') id: number) {
 | 
			
		||||
		return this.userGroupsRepository.findOne({ id: id });
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post()
 | 
			
		||||
	@ResponseSchema(UserGroup)
 | 
			
		||||
	@ResponseSchema(UserGroupNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Create a new usergroup object (id will be generated automagicly).' })
 | 
			
		||||
	async post(@Body({ validate: true }) createUserGroup: CreateUserGroup) {
 | 
			
		||||
		let userGroup;
 | 
			
		||||
		try {
 | 
			
		||||
			userGroup = await createUserGroup.toUserGroup();
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return this.userGroupsRepository.save(userGroup);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
	@ResponseSchema(UserGroup)
 | 
			
		||||
	@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(UserGroupIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Update a usergroup object (id can't be changed)." })
 | 
			
		||||
	async put(@Param('id') id: number, @EntityFromBody() userGroup: UserGroup) {
 | 
			
		||||
		let oldUserGroup = await this.userGroupsRepository.findOne({ id: id });
 | 
			
		||||
 | 
			
		||||
		if (!oldUserGroup) {
 | 
			
		||||
			throw new UserGroupNotFoundError()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (oldUserGroup.id != userGroup.id) {
 | 
			
		||||
			throw new UserGroupIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.userGroupsRepository.update(oldUserGroup, userGroup);
 | 
			
		||||
		return userGroup;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@ResponseSchema(UserGroup)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@OpenAPI({ description: 'Delete a specified usergroup (if it exists).' })
 | 
			
		||||
	async remove(@Param("id") id: number) {
 | 
			
		||||
		let group = await this.userGroupsRepository.findOne({ id: id });
 | 
			
		||||
		if (!group) {
 | 
			
		||||
			return null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.userGroupsRepository.delete(group);
 | 
			
		||||
		return group;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								src/errors/AddressErrors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/errors/AddressErrors.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw, when to provided address doesn't belong to the accepted types.
 | 
			
		||||
 */
 | 
			
		||||
export class AddressWrongTypeError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "AddressWrongTypeError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The address must be an existing adress's id. \n You provided a object of another type."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw, when a non-existant address get's loaded.
 | 
			
		||||
 */
 | 
			
		||||
export class AddressNotFoundError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "AddressNotFoundError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The address you provided couldn't be located in the system. \n Please check your request."
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										123
									
								
								src/errors/AuthError.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/errors/AuthError.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
			
		||||
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.
 | 
			
		||||
 */
 | 
			
		||||
export class IllegalJWTError extends UnauthorizedError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "IllegalJWTError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "your provided jwt could not be parsed"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when user is nonexistant or refreshtoken is invalid.
 | 
			
		||||
 */
 | 
			
		||||
export class UserNonexistantOrRefreshtokenInvalidError extends UnauthorizedError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "UserNonexistantOrRefreshtokenInvalidError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "user is nonexistant or refreshtoken is invalid"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when provided credentials are invalid.
 | 
			
		||||
 */
 | 
			
		||||
export class InvalidCredentialsError extends UnauthorizedError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "InvalidCredentialsError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "your provided credentials are invalid"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a jwt does not have permission for this route/action.
 | 
			
		||||
 */
 | 
			
		||||
export class NoPermissionError extends ForbiddenError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "NoPermissionError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "your provided jwt does not have permission for this route/ action"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when no username and no email is set.
 | 
			
		||||
 */
 | 
			
		||||
export class UsernameOrEmailNeededError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "UsernameOrEmailNeededError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "Auth needs to have email or username set! \n You provided neither."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when no password is provided.
 | 
			
		||||
 */
 | 
			
		||||
export class PasswordNeededError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "PasswordNeededError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "no password is provided - you need to provide it"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when no user could be found mating the provided credential.
 | 
			
		||||
 */
 | 
			
		||||
export class UserNotFoundError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "UserNotFoundError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "no user could be found for provided credential"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when no jwt token was provided (but one had to be).
 | 
			
		||||
 */
 | 
			
		||||
export class JwtNotProvidedError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "JwtNotProvidedError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "no jwt token was provided"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when user was not found or 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"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when refresh token count was invalid
 | 
			
		||||
 */
 | 
			
		||||
export class RefreshTokenCountInvalidError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "RefreshTokenCountInvalidError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "refresh token count was invalid"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								src/errors/GroupContactErrors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/errors/GroupContactErrors.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw, when a provided groupContact doesn't belong to the accepted types.
 | 
			
		||||
 */
 | 
			
		||||
export class GroupContactWrongTypeError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "GroupContactWrongTypeError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The groupContact must be an existing groupContact's id. \n You provided a object of another type."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw, when a non-existant groupContact get's loaded.
 | 
			
		||||
 */
 | 
			
		||||
export class GroupContactNotFoundError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "GroupContactNotFoundError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The groupContact you provided couldn't be located in the system. \n Please check your request."
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								src/errors/RunnerErrors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/errors/RunnerErrors.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
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()
 | 
			
		||||
	name = "RunnerNotFoundError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "Runner not found!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a runner is missing his group association.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerGroupNeededError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "RunnerGroupNeededError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "Runner's need to be part of one group (team or organisiation)! \n You provided neither."
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								src/errors/RunnerGroupErrors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/errors/RunnerGroupErrors.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
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()
 | 
			
		||||
	name = "RunnerGroupNotFoundError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "RunnerGroup not found!"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										62
									
								
								src/errors/RunnerOrganisationErrors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/errors/RunnerOrganisationErrors.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
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()
 | 
			
		||||
	name = "RunnerOrganisationNotFoundError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "RunnerOrganisation not found!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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.
 | 
			
		||||
 */
 | 
			
		||||
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"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a organisation still has runners associated.
 | 
			
		||||
 * Implemented this waysto work with the json-schema conversion for openapi.
 | 
			
		||||
 */
 | 
			
		||||
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."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw, when a provided runnerOrganisation doesn't belong to the accepted types.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerOrganisationWrongTypeError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "RunnerOrganisationWrongTypeError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The runner organisation must be an existing organisation's id. \n You provided a object of another type."
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										51
									
								
								src/errors/RunnerTeamErrors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/errors/RunnerTeamErrors.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
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()
 | 
			
		||||
	name = "RunnerTeamNotFoundError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "RunnerTeam not found!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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.
 | 
			
		||||
 */
 | 
			
		||||
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"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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()
 | 
			
		||||
	name = "RunnerTeamNeedsParentError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "You provided no runner organisation as this team's parent group."
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								src/errors/UserErrors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/errors/UserErrors.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when no username or email is set
 | 
			
		||||
 */
 | 
			
		||||
export class UsernameOrEmailNeededError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "UsernameOrEmailNeededError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "no username or email is set!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a user couldn't be found.
 | 
			
		||||
 */
 | 
			
		||||
export class UserNotFoundError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "UserNotFoundError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "User not found!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when two users' ids don't match.
 | 
			
		||||
 * Usually occurs when a user tries to change a user's id.
 | 
			
		||||
 */
 | 
			
		||||
export class UserIdsNotMatchingError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	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"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								src/errors/UserGroupErrors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/errors/UserGroupErrors.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when no groupname is set
 | 
			
		||||
 */
 | 
			
		||||
export class GroupNameNeededError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "GroupNameNeededError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "no groupname is set!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a usergroup couldn't be found.
 | 
			
		||||
 */
 | 
			
		||||
export class UserGroupNotFoundError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "UserGroupNotFoundError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "User Group not found!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when two usergroups' ids don't match.
 | 
			
		||||
 * Usually occurs when a user tries to change a usergroups's id.
 | 
			
		||||
 */
 | 
			
		||||
export class UserGroupIdsNotMatchingError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "UserGroupIdsNotMatchingError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The id's don't match!! \n If you wanted to change a usergroup's id: This isn't allowed"
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,8 @@
 | 
			
		||||
import { createConnection } from "typeorm";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Loader for the database that creates the database connection and initializes the database tabels.
 | 
			
		||||
 */
 | 
			
		||||
export default async () => {
 | 
			
		||||
    const connection = await createConnection();
 | 
			
		||||
    connection.synchronize();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,9 @@
 | 
			
		||||
import { Application } from "express";
 | 
			
		||||
import bodyParser from 'body-parser';
 | 
			
		||||
import cors from 'cors';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Loader for express related configurations.
 | 
			
		||||
 * Currently only enables the proxy trust.
 | 
			
		||||
 */
 | 
			
		||||
export default async (app: Application) => {
 | 
			
		||||
	app.enable('trust proxy');
 | 
			
		||||
	return app;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,11 @@
 | 
			
		||||
import { Application } from "express";
 | 
			
		||||
import databaseLoader from "./database";
 | 
			
		||||
import expressLoader from "./express";
 | 
			
		||||
import openapiLoader from "./openapi";
 | 
			
		||||
import databaseLoader from "./database";
 | 
			
		||||
import { Application } from "express";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Index Loader that executes the other loaders in the right order.
 | 
			
		||||
 */
 | 
			
		||||
export default async (app: Application) => {
 | 
			
		||||
    await databaseLoader();
 | 
			
		||||
    await openapiLoader(app);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,19 @@
 | 
			
		||||
import { validationMetadatasToSchemas } from "class-validator-jsonschema";
 | 
			
		||||
import { Application } from "express";
 | 
			
		||||
import * as swaggerUiExpress from "swagger-ui-express";
 | 
			
		||||
import { getMetadataArgsStorage } from "routing-controllers";
 | 
			
		||||
import { routingControllersToSpec } from "routing-controllers-openapi";
 | 
			
		||||
import { validationMetadatasToSchemas } from "class-validator-jsonschema";
 | 
			
		||||
import * as swaggerUiExpress from "swagger-ui-express";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Loader for everything openapi related - from creating the schema to serving it via a static route.
 | 
			
		||||
 */
 | 
			
		||||
export default async (app: Application) => {
 | 
			
		||||
  const storage = getMetadataArgsStorage();
 | 
			
		||||
  const schemas = validationMetadatasToSchemas({
 | 
			
		||||
    refPointerPrefix: "#/components/schemas/",
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  //Spec creation based on the previously created schemas
 | 
			
		||||
  const spec = routingControllersToSpec(
 | 
			
		||||
    storage,
 | 
			
		||||
    {
 | 
			
		||||
@@ -17,6 +22,13 @@ export default async (app: Application) => {
 | 
			
		||||
    {
 | 
			
		||||
      components: {
 | 
			
		||||
        schemas,
 | 
			
		||||
        "securitySchemes": {
 | 
			
		||||
          "AuthToken": {
 | 
			
		||||
            "type": "http",
 | 
			
		||||
            "scheme": "bearer",
 | 
			
		||||
            "bearerFormat": "JWT"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      info: {
 | 
			
		||||
        description: "The the backend API for the LfK! runner system.",
 | 
			
		||||
@@ -25,6 +37,8 @@ export default async (app: Application) => {
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  //Options for swaggerUiExpress
 | 
			
		||||
  const options = {
 | 
			
		||||
    explorer: true,
 | 
			
		||||
  };
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,14 @@
 | 
			
		||||
import {
 | 
			
		||||
    Middleware,
 | 
			
		||||
    ExpressErrorMiddlewareInterface
 | 
			
		||||
} from "routing-controllers";
 | 
			
		||||
import { ExpressErrorMiddlewareInterface, Middleware } from "routing-controllers";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Our Error handling middlware that returns our custom httperrors to the user
 | 
			
		||||
 */
 | 
			
		||||
@Middleware({ type: "after" })
 | 
			
		||||
export class ErrorHandler implements ExpressErrorMiddlewareInterface {
 | 
			
		||||
    public error(
 | 
			
		||||
        error: any,
 | 
			
		||||
        request: any,
 | 
			
		||||
        response: any,
 | 
			
		||||
        next: (err: any) => any
 | 
			
		||||
    ) {
 | 
			
		||||
    public error(error: any, request: any, response: any, next: (err: any) => any) {
 | 
			
		||||
        if (response.headersSent) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        response.json(error);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +0,0 @@
 | 
			
		||||
import { Request, Response, NextFunction } from "express";
 | 
			
		||||
// import bodyParser from 'body-parser';
 | 
			
		||||
// import cors from 'cors';
 | 
			
		||||
import * as jwt from "jsonwebtoken";
 | 
			
		||||
 | 
			
		||||
export default (req: Request, res: Response, next: NextFunction) => {
 | 
			
		||||
  const token = <string>req.headers["auth"];
 | 
			
		||||
  try {
 | 
			
		||||
    const jwtPayload = <any>jwt.verify(token, "secretjwtsecret");
 | 
			
		||||
    // const jwtPayload = <any>jwt.verify(token, process.env.JWT_SECRET);
 | 
			
		||||
    res.locals.jwtPayload = jwtPayload;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.log(error);
 | 
			
		||||
    return res.status(401).send();
 | 
			
		||||
  }
 | 
			
		||||
  next();
 | 
			
		||||
};
 | 
			
		||||
@@ -1,38 +0,0 @@
 | 
			
		||||
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
 | 
			
		||||
import {
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
  IsOptional,
 | 
			
		||||
  IsPositive,
 | 
			
		||||
  IsString,
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a track of given length.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export class Track {
 | 
			
		||||
  /**
 | 
			
		||||
   * Autogenerated unique id (primary key).
 | 
			
		||||
   */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The track's name.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  name: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The track's length in meters.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  @IsPositive()
 | 
			
		||||
  length: number;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								src/models/actions/CreateAddress.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/models/actions/CreateAddress.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
import { IsNotEmpty, IsOptional, IsPostalCode, IsString } from 'class-validator';
 | 
			
		||||
import { Address } from '../entities/Address';
 | 
			
		||||
 | 
			
		||||
export class CreateAddress {
 | 
			
		||||
    /**
 | 
			
		||||
   * The address's description.
 | 
			
		||||
   */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    description?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The address's first line.
 | 
			
		||||
     * Containing the street and house number.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    address1: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The address's second line.
 | 
			
		||||
     * Containing optional information.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    address2?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The address's postal code.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    @IsPostalCode("DE")
 | 
			
		||||
    postalcode: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The address's city.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    city: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The address's country.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    country: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a Address object based on this.
 | 
			
		||||
     */
 | 
			
		||||
    public toAddress(): Address {
 | 
			
		||||
        let newAddress: Address = new Address();
 | 
			
		||||
 | 
			
		||||
        newAddress.address1 = this.address1;
 | 
			
		||||
        newAddress.address2 = this.address2;
 | 
			
		||||
        newAddress.postalcode = this.postalcode;
 | 
			
		||||
        newAddress.city = this.city;
 | 
			
		||||
        newAddress.country = this.country;
 | 
			
		||||
 | 
			
		||||
        return newAddress;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										59
									
								
								src/models/actions/CreateAuth.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/models/actions/CreateAuth.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
import * as argon2 from "argon2";
 | 
			
		||||
import { IsEmail, IsOptional, IsString } from 'class-validator';
 | 
			
		||||
import * as jsonwebtoken from 'jsonwebtoken';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { InvalidCredentialsError, PasswordNeededError, UserNotFoundError } from '../../errors/AuthError';
 | 
			
		||||
import { UsernameOrEmailNeededError } from '../../errors/UserErrors';
 | 
			
		||||
import { User } from '../entities/User';
 | 
			
		||||
import { Auth } from '../responses/ResponseAuth';
 | 
			
		||||
 | 
			
		||||
export class CreateAuth {
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    username?: string;
 | 
			
		||||
    @IsString()
 | 
			
		||||
    password: string;
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsEmail()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    email?: string;
 | 
			
		||||
 | 
			
		||||
    public async toAuth(): Promise<Auth> {
 | 
			
		||||
        let newAuth: Auth = new Auth();
 | 
			
		||||
 | 
			
		||||
        if (this.email === undefined && this.username === undefined) {
 | 
			
		||||
            throw new UsernameOrEmailNeededError();
 | 
			
		||||
        }
 | 
			
		||||
        if (!this.password) {
 | 
			
		||||
            throw new PasswordNeededError()
 | 
			
		||||
        }
 | 
			
		||||
        const found_users = await getConnectionManager().get().getRepository(User).find({ relations: ['groups', 'permissions'], where: [{ username: this.username }, { email: this.email }] });
 | 
			
		||||
        if (found_users.length === 0) {
 | 
			
		||||
            throw new UserNotFoundError()
 | 
			
		||||
        } else {
 | 
			
		||||
            const found_user = found_users[0]
 | 
			
		||||
            if (await argon2.verify(found_user.password, this.password + found_user.uuid)) {
 | 
			
		||||
                const timestamp_accesstoken_expiry = Math.floor(Date.now() / 1000) + 5 * 60
 | 
			
		||||
                found_user.permissions = found_user.permissions || []
 | 
			
		||||
                delete found_user.password;
 | 
			
		||||
                newAuth.access_token = jsonwebtoken.sign({
 | 
			
		||||
                    userdetails: found_user,
 | 
			
		||||
                    exp: timestamp_accesstoken_expiry
 | 
			
		||||
                }, config.jwt_secret)
 | 
			
		||||
                newAuth.access_token_expires_at = timestamp_accesstoken_expiry
 | 
			
		||||
                // 
 | 
			
		||||
                const timestamp_refresh_expiry = Math.floor(Date.now() / 1000) + 10 * 36000
 | 
			
		||||
                newAuth.refresh_token = jsonwebtoken.sign({
 | 
			
		||||
                    refreshtokencount: found_user.refreshTokenCount,
 | 
			
		||||
                    userid: found_user.id,
 | 
			
		||||
                    exp: timestamp_refresh_expiry
 | 
			
		||||
                }, config.jwt_secret)
 | 
			
		||||
                newAuth.refresh_token_expires_at = timestamp_refresh_expiry
 | 
			
		||||
            } else {
 | 
			
		||||
                throw new InvalidCredentialsError()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return newAuth;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										84
									
								
								src/models/actions/CreateGroupContact.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/models/actions/CreateGroupContact.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
			
		||||
import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors';
 | 
			
		||||
import { Address } from '../entities/Address';
 | 
			
		||||
import { GroupContact } from '../entities/GroupContact';
 | 
			
		||||
 | 
			
		||||
export class CreateGroupContact {
 | 
			
		||||
    /**
 | 
			
		||||
       * The contact's first name.
 | 
			
		||||
       */
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    firstname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The contact's middle name.
 | 
			
		||||
     * Optional
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    middlename?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The contact's last name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    lastname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The contact's address.
 | 
			
		||||
     * Optional
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    address?: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The contact's phone number.
 | 
			
		||||
     * Optional
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsPhoneNumber(config.phone_validation_countrycode)
 | 
			
		||||
    phone?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The contact's email address.
 | 
			
		||||
     * Optional
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsEmail()
 | 
			
		||||
    email?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get's this participant's address from this.address.
 | 
			
		||||
     */
 | 
			
		||||
    public async getAddress(): Promise<Address> {
 | 
			
		||||
        if (this.address === undefined) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.address)) {
 | 
			
		||||
            let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
 | 
			
		||||
            if (!address) { throw new AddressNotFoundError; }
 | 
			
		||||
            return address;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new AddressWrongTypeError;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a Address object based on this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toGroupContact(): Promise<GroupContact> {
 | 
			
		||||
        let contact: GroupContact = new GroupContact();
 | 
			
		||||
        contact.firstname = this.firstname;
 | 
			
		||||
        contact.middlename = this.middlename;
 | 
			
		||||
        contact.lastname = this.lastname;
 | 
			
		||||
        contact.email = this.email;
 | 
			
		||||
        contact.phone = this.phone;
 | 
			
		||||
        contact.address = await this.getAddress();
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										72
									
								
								src/models/actions/CreateParticipant.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/models/actions/CreateParticipant.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors';
 | 
			
		||||
import { Address } from '../entities/Address';
 | 
			
		||||
 | 
			
		||||
export abstract class CreateParticipant {
 | 
			
		||||
    /**
 | 
			
		||||
     * The new participant's first name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    firstname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new participant's middle name.
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    middlename?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new participant's last name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    lastname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new participant's phone number.
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsPhoneNumber(config.phone_validation_countrycode)
 | 
			
		||||
    phone?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new participant's e-mail address.
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsEmail()
 | 
			
		||||
    email?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new participant's address.
 | 
			
		||||
     * Must be of type number (address id), createAddress (new address) or address (existing address)
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    address?: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get's this participant's address from this.address.
 | 
			
		||||
     */
 | 
			
		||||
    public async getAddress(): Promise<Address> {
 | 
			
		||||
        if (this.address === undefined) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.address)) {
 | 
			
		||||
            let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
 | 
			
		||||
            if (!address) { throw new AddressNotFoundError; }
 | 
			
		||||
            return address;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new AddressWrongTypeError;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										51
									
								
								src/models/actions/CreateRunner.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/models/actions/CreateRunner.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
import { IsInt } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { RunnerGroupNotFoundError } from '../../errors/RunnerGroupErrors';
 | 
			
		||||
import { RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
 | 
			
		||||
import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
 | 
			
		||||
import { Runner } from '../entities/Runner';
 | 
			
		||||
import { RunnerGroup } from '../entities/RunnerGroup';
 | 
			
		||||
import { CreateParticipant } from './CreateParticipant';
 | 
			
		||||
 | 
			
		||||
export class CreateRunner extends CreateParticipant {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new runner's team's id.
 | 
			
		||||
     * Either provide this or his organisation's id.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    group: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a Runner entity from this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toRunner(): Promise<Runner> {
 | 
			
		||||
        let newRunner: Runner = new Runner();
 | 
			
		||||
 | 
			
		||||
        newRunner.firstname = this.firstname;
 | 
			
		||||
        newRunner.middlename = this.middlename;
 | 
			
		||||
        newRunner.lastname = this.lastname;
 | 
			
		||||
        newRunner.phone = this.phone;
 | 
			
		||||
        newRunner.email = this.email;
 | 
			
		||||
        newRunner.group = await this.getGroup();
 | 
			
		||||
        newRunner.address = await this.getAddress();
 | 
			
		||||
 | 
			
		||||
        return newRunner;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Manages all the different ways a group can be provided.
 | 
			
		||||
     */
 | 
			
		||||
    public async getGroup(): Promise<RunnerGroup> {
 | 
			
		||||
        if (this.group === undefined) {
 | 
			
		||||
            throw new RunnerTeamNeedsParentError();
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.group)) {
 | 
			
		||||
            let group = await getConnectionManager().get().getRepository(RunnerGroup).findOne({ id: this.group });
 | 
			
		||||
            if (!group) { throw new RunnerGroupNotFoundError; }
 | 
			
		||||
            return group;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new RunnerOrganisationWrongTypeError;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								src/models/actions/CreateRunnerGroup.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/models/actions/CreateRunnerGroup.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
import { IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { GroupContactNotFoundError, GroupContactWrongTypeError } from '../../errors/GroupContactErrors';
 | 
			
		||||
import { GroupContact } from '../entities/GroupContact';
 | 
			
		||||
 | 
			
		||||
export abstract class CreateRunnerGroup {
 | 
			
		||||
    /**
 | 
			
		||||
     * The group's name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    name: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The group's contact.
 | 
			
		||||
     * Optional
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    contact?: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Deals with the contact for groups this.
 | 
			
		||||
     */
 | 
			
		||||
    public async getContact(): Promise<GroupContact> {
 | 
			
		||||
        if (this.contact === undefined) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.contact)) {
 | 
			
		||||
            let address = await getConnectionManager().get().getRepository(GroupContact).findOne({ id: this.contact });
 | 
			
		||||
            if (!address) { throw new GroupContactNotFoundError; }
 | 
			
		||||
            return address;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new GroupContactWrongTypeError;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								src/models/actions/CreateRunnerOrganisation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/models/actions/CreateRunnerOrganisation.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
import { IsInt, IsOptional } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors';
 | 
			
		||||
import { Address } from '../entities/Address';
 | 
			
		||||
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
 | 
			
		||||
import { CreateRunnerGroup } from './CreateRunnerGroup';
 | 
			
		||||
 | 
			
		||||
export class CreateRunnerOrganisation extends CreateRunnerGroup {
 | 
			
		||||
    /**
 | 
			
		||||
     * The new organisation's address.
 | 
			
		||||
     * Must be of type number (address id), createAddress (new address) or address (existing address)
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    address?: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a Participant entity from this.
 | 
			
		||||
     */
 | 
			
		||||
    public async getAddress(): Promise<Address> {
 | 
			
		||||
        if (this.address === undefined) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.address)) {
 | 
			
		||||
            let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
 | 
			
		||||
            if (!address) { throw new AddressNotFoundError; }
 | 
			
		||||
            return address;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new AddressWrongTypeError;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a RunnerOrganisation entity from this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toRunnerOrganisation(): Promise<RunnerOrganisation> {
 | 
			
		||||
        let newRunnerOrganisation: RunnerOrganisation = new RunnerOrganisation();
 | 
			
		||||
 | 
			
		||||
        newRunnerOrganisation.name = this.name;
 | 
			
		||||
        newRunnerOrganisation.contact = await this.getContact();
 | 
			
		||||
        newRunnerOrganisation.address = await this.getAddress();
 | 
			
		||||
 | 
			
		||||
        return newRunnerOrganisation;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								src/models/actions/CreateRunnerTeam.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/models/actions/CreateRunnerTeam.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
import { IsInt, IsNotEmpty } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { RunnerOrganisationNotFoundError, RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
 | 
			
		||||
import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
 | 
			
		||||
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
 | 
			
		||||
import { RunnerTeam } from '../entities/RunnerTeam';
 | 
			
		||||
import { CreateRunnerGroup } from './CreateRunnerGroup';
 | 
			
		||||
 | 
			
		||||
export class CreateRunnerTeam extends CreateRunnerGroup {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The team's parent group (organisation).
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    parentGroup: number;
 | 
			
		||||
 | 
			
		||||
    public async getParent(): Promise<RunnerOrganisation> {
 | 
			
		||||
        if (this.parentGroup === undefined) {
 | 
			
		||||
            throw new RunnerTeamNeedsParentError();
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.parentGroup)) {
 | 
			
		||||
            let parentGroup = await getConnectionManager().get().getRepository(RunnerOrganisation).findOne({ id: this.parentGroup });
 | 
			
		||||
            if (!parentGroup) { throw new RunnerOrganisationNotFoundError();; }
 | 
			
		||||
            return parentGroup;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new RunnerOrganisationWrongTypeError;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a RunnerTeam entity from this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toRunnerTeam(): Promise<RunnerTeam> {
 | 
			
		||||
        let newRunnerTeam: RunnerTeam = new RunnerTeam();
 | 
			
		||||
 | 
			
		||||
        newRunnerTeam.name = this.name;
 | 
			
		||||
        newRunnerTeam.parentGroup = await this.getParent();
 | 
			
		||||
 | 
			
		||||
        newRunnerTeam.contact = await this.getContact()
 | 
			
		||||
 | 
			
		||||
        return newRunnerTeam;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/models/actions/CreateTrack.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/models/actions/CreateTrack.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
import { IsInt, IsNotEmpty, IsPositive, IsString } from 'class-validator';
 | 
			
		||||
import { Track } from '../entities/Track';
 | 
			
		||||
 | 
			
		||||
export class CreateTrack {
 | 
			
		||||
    /**
 | 
			
		||||
     * The track's name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    name: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The track's distance in meters (must be greater 0).
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsPositive()
 | 
			
		||||
    distance: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts a Track object based on this.
 | 
			
		||||
     */
 | 
			
		||||
    public toTrack(): Track {
 | 
			
		||||
        let newTrack: Track = new Track();
 | 
			
		||||
 | 
			
		||||
        newTrack.name = this.name;
 | 
			
		||||
        newTrack.distance = this.distance;
 | 
			
		||||
 | 
			
		||||
        return newTrack;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										120
									
								
								src/models/actions/CreateUser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								src/models/actions/CreateUser.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,120 @@
 | 
			
		||||
import * as argon2 from "argon2";
 | 
			
		||||
import { IsEmail, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { UsernameOrEmailNeededError } from '../../errors/UserErrors';
 | 
			
		||||
import { UserGroupNotFoundError } from '../../errors/UserGroupErrors';
 | 
			
		||||
import { User } from '../entities/User';
 | 
			
		||||
import { UserGroup } from '../entities/UserGroup';
 | 
			
		||||
 | 
			
		||||
export class CreateUser {
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's first name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    firstname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's middle name.
 | 
			
		||||
     * Optinal.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    middlename?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's last name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    lastname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's username.
 | 
			
		||||
     * You have to provide at least one of: {email, username}.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    username?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's email address.
 | 
			
		||||
     * You have to provide at least one of: {email, username}.
 | 
			
		||||
     */
 | 
			
		||||
    @IsEmail()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    email?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's phone number.
 | 
			
		||||
     * Optional
 | 
			
		||||
     */
 | 
			
		||||
    @IsPhoneNumber(config.phone_validation_countrycode)
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    phone?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's password.
 | 
			
		||||
     * This will of course not be saved in plaintext :)
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    password: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's groups' id(s).
 | 
			
		||||
     * You can provide either one groupId or an array of groupIDs.
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    groupId?: number[] | number
 | 
			
		||||
 | 
			
		||||
    //TODO: ProfilePics
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts this to a User Entity.
 | 
			
		||||
     */
 | 
			
		||||
    public async toUser(): Promise<User> {
 | 
			
		||||
        let newUser: User = new User();
 | 
			
		||||
 | 
			
		||||
        if (this.email === undefined && this.username === undefined) {
 | 
			
		||||
            throw new UsernameOrEmailNeededError();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.groupId) {
 | 
			
		||||
            if (!Array.isArray(this.groupId)) {
 | 
			
		||||
                this.groupId = [this.groupId]
 | 
			
		||||
            }
 | 
			
		||||
            const groupIDs: number[] = this.groupId
 | 
			
		||||
            let errors = 0
 | 
			
		||||
            const validateusergroups = async () => {
 | 
			
		||||
                let foundgroups = []
 | 
			
		||||
                for (const g of groupIDs) {
 | 
			
		||||
                    const found = await getConnectionManager().get().getRepository(UserGroup).find({ id: g });
 | 
			
		||||
                    if (found.length === 0) {
 | 
			
		||||
                        errors++
 | 
			
		||||
                    } else {
 | 
			
		||||
                        foundgroups.push(found[0])
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                newUser.groups = foundgroups
 | 
			
		||||
            }
 | 
			
		||||
            await validateusergroups()
 | 
			
		||||
            if (errors !== 0) {
 | 
			
		||||
                throw new UserGroupNotFoundError();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        newUser.email = this.email
 | 
			
		||||
        newUser.username = this.username
 | 
			
		||||
        newUser.firstname = this.firstname
 | 
			
		||||
        newUser.middlename = this.middlename
 | 
			
		||||
        newUser.lastname = this.lastname
 | 
			
		||||
        newUser.uuid = uuid.v4()
 | 
			
		||||
        newUser.phone = this.phone
 | 
			
		||||
        newUser.password = await argon2.hash(this.password + newUser.uuid);
 | 
			
		||||
        //TODO: ProfilePics
 | 
			
		||||
 | 
			
		||||
        return newUser;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/models/actions/CreateUserGroup.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/models/actions/CreateUserGroup.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
import { IsOptional, IsString } from 'class-validator';
 | 
			
		||||
import { UserGroup } from '../entities/UserGroup';
 | 
			
		||||
 | 
			
		||||
export class CreateUserGroup {
 | 
			
		||||
    /**
 | 
			
		||||
     * The new group's name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    name: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new group's description.
 | 
			
		||||
     * Optinal.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    description?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts this to a UserGroup entity.
 | 
			
		||||
     */
 | 
			
		||||
    public async toUserGroup(): Promise<UserGroup> {
 | 
			
		||||
        let newUserGroup: UserGroup = new UserGroup();
 | 
			
		||||
 | 
			
		||||
        newUserGroup.name = this.name;
 | 
			
		||||
        newUserGroup.description = this.description;
 | 
			
		||||
 | 
			
		||||
        return newUserGroup;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								src/models/actions/HandleLogout.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/models/actions/HandleLogout.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
import * as jsonwebtoken from 'jsonwebtoken';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { IllegalJWTError, JwtNotProvidedError, RefreshTokenCountInvalidError, UserNotFoundError } from '../../errors/AuthError';
 | 
			
		||||
import { User } from '../entities/User';
 | 
			
		||||
import { Logout } from '../responses/ResponseLogout';
 | 
			
		||||
 | 
			
		||||
export class HandleLogout {
 | 
			
		||||
    @IsString()
 | 
			
		||||
    token: string;
 | 
			
		||||
 | 
			
		||||
    public async logout(): Promise<Logout> {
 | 
			
		||||
        let logout: Logout = new Logout();
 | 
			
		||||
        if (!this.token || this.token === undefined) {
 | 
			
		||||
            throw new JwtNotProvidedError()
 | 
			
		||||
        }
 | 
			
		||||
        let decoded;
 | 
			
		||||
        try {
 | 
			
		||||
            decoded = jsonwebtoken.verify(this.token, config.jwt_secret)
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            throw new IllegalJWTError()
 | 
			
		||||
        }
 | 
			
		||||
        logout.timestamp = Math.floor(Date.now() / 1000)
 | 
			
		||||
        let found_user: User = await getConnectionManager().get().getRepository(User).findOne({ id: decoded["userid"] });
 | 
			
		||||
        if (!found_user) {
 | 
			
		||||
            throw new UserNotFoundError()
 | 
			
		||||
        }
 | 
			
		||||
        if (found_user.refreshTokenCount !== decoded["refreshtokencount"]) {
 | 
			
		||||
            throw new RefreshTokenCountInvalidError()
 | 
			
		||||
        }
 | 
			
		||||
        found_user.refreshTokenCount++;
 | 
			
		||||
        await getConnectionManager().get().getRepository(User).update({ id: found_user.id }, found_user)
 | 
			
		||||
        return logout;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										51
									
								
								src/models/actions/RefreshAuth.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/models/actions/RefreshAuth.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
import * as jsonwebtoken from 'jsonwebtoken';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { IllegalJWTError, JwtNotProvidedError, RefreshTokenCountInvalidError, UserNotFoundError } from '../../errors/AuthError';
 | 
			
		||||
import { User } from '../entities/User';
 | 
			
		||||
import { Auth } from '../responses/ResponseAuth';
 | 
			
		||||
 | 
			
		||||
export class RefreshAuth {
 | 
			
		||||
    @IsString()
 | 
			
		||||
    token: string;
 | 
			
		||||
 | 
			
		||||
    public async toAuth(): Promise<Auth> {
 | 
			
		||||
        let newAuth: Auth = new Auth();
 | 
			
		||||
        if (!this.token || this.token === undefined) {
 | 
			
		||||
            throw new JwtNotProvidedError()
 | 
			
		||||
        }
 | 
			
		||||
        let decoded
 | 
			
		||||
        try {
 | 
			
		||||
            decoded = jsonwebtoken.verify(this.token, config.jwt_secret)
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            throw new IllegalJWTError()
 | 
			
		||||
        }
 | 
			
		||||
        const found_user = await getConnectionManager().get().getRepository(User).findOne({ id: decoded["userid"] }, { relations: ['groups', 'permissions'] });
 | 
			
		||||
        if (!found_user) {
 | 
			
		||||
            throw new UserNotFoundError()
 | 
			
		||||
        }
 | 
			
		||||
        if (found_user.refreshTokenCount !== decoded["refreshtokencount"]) {
 | 
			
		||||
            throw new RefreshTokenCountInvalidError()
 | 
			
		||||
        }
 | 
			
		||||
        found_user.permissions = found_user.permissions || []
 | 
			
		||||
        delete found_user.password;
 | 
			
		||||
        const timestamp_accesstoken_expiry = Math.floor(Date.now() / 1000) + 5 * 60
 | 
			
		||||
        delete found_user.password;
 | 
			
		||||
        newAuth.access_token = jsonwebtoken.sign({
 | 
			
		||||
            userdetails: found_user,
 | 
			
		||||
            exp: timestamp_accesstoken_expiry
 | 
			
		||||
        }, config.jwt_secret)
 | 
			
		||||
        newAuth.access_token_expires_at = timestamp_accesstoken_expiry
 | 
			
		||||
        // 
 | 
			
		||||
        const timestamp_refresh_expiry = Math.floor(Date.now() / 1000) + 10 * 36000
 | 
			
		||||
        newAuth.refresh_token = jsonwebtoken.sign({
 | 
			
		||||
            refreshtokencount: found_user.refreshTokenCount,
 | 
			
		||||
            userid: found_user.id,
 | 
			
		||||
            exp: timestamp_refresh_expiry
 | 
			
		||||
        }, config.jwt_secret)
 | 
			
		||||
        newAuth.refresh_token_expires_at = timestamp_refresh_expiry
 | 
			
		||||
 | 
			
		||||
        return newAuth;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										86
									
								
								src/models/entities/Address.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/models/entities/Address.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,86 @@
 | 
			
		||||
import {
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
  IsOptional,
 | 
			
		||||
  IsPostalCode,
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
 | 
			
		||||
import { Participant } from "./Participant";
 | 
			
		||||
import { RunnerOrganisation } from "./RunnerOrganisation";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a address (to be used for contact information).
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export class Address {
 | 
			
		||||
  /**
 | 
			
		||||
   * Autogenerated unique id (primary key).
 | 
			
		||||
   */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The address's description.
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  description?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The address's first line.
 | 
			
		||||
   * Containing the street and house number.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  address1: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The address's second line.
 | 
			
		||||
   * Containing optional information.
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  address2?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The address's postal code.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsPostalCode("DE")
 | 
			
		||||
  postalcode: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The address's city.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  city: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The address's country.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  country: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to link the address to participants.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => Participant, participant => participant.address, { nullable: true })
 | 
			
		||||
  participants: Participant[];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to link the address to runner groups.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => RunnerOrganisation, group => group.address, { nullable: true })
 | 
			
		||||
  groups: RunnerOrganisation[];
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								src/models/entities/DistanceDonation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/models/entities/DistanceDonation.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
import { IsInt, IsNotEmpty, IsPositive } from "class-validator";
 | 
			
		||||
import { ChildEntity, Column, ManyToOne } from "typeorm";
 | 
			
		||||
import { Donation } from "./Donation";
 | 
			
		||||
import { Runner } from "./Runner";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a distance based donation.
 | 
			
		||||
 * Here people donate a certain amout per kilometer
 | 
			
		||||
*/
 | 
			
		||||
@ChildEntity()
 | 
			
		||||
export class DistanceDonation extends Donation {
 | 
			
		||||
  /**
 | 
			
		||||
   * The runner associated.
 | 
			
		||||
   */
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @ManyToOne(() => Runner, runner => runner.distanceDonations)
 | 
			
		||||
  runner: Runner;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The amount the donor set to be donated per kilometer that the runner ran.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  @IsPositive()
 | 
			
		||||
  amountPerDistance: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The donation's amount in cents (or whatever your currency's smallest unit is.).
 | 
			
		||||
   * The exact implementation may differ for each type of donation.
 | 
			
		||||
   */
 | 
			
		||||
  public get amount(): number {
 | 
			
		||||
    let calculatedAmount = -1;
 | 
			
		||||
    try {
 | 
			
		||||
      calculatedAmount = this.amountPerDistance * this.runner.distance;
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      throw error;
 | 
			
		||||
    }
 | 
			
		||||
    return calculatedAmount;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								src/models/entities/Donation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/models/entities/Donation.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
import {
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
 | 
			
		||||
import { Participant } from "./Participant";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the donation interface.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
@TableInheritance({ column: { name: "type", type: "varchar" } })
 | 
			
		||||
export abstract class Donation {
 | 
			
		||||
  /**
 | 
			
		||||
   * Autogenerated unique id (primary key).
 | 
			
		||||
   */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The donations's donor.
 | 
			
		||||
   */
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @ManyToOne(() => Participant, donor => donor.donations)
 | 
			
		||||
  donor: Participant;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The donation's amount in cents (or whatever your currency's smallest unit is.).
 | 
			
		||||
   * The exact implementation may differ for each type of donation.
 | 
			
		||||
   */
 | 
			
		||||
  abstract amount: number | Promise<number>;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								src/models/entities/Donor.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/models/entities/Donor.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
import { IsBoolean } from "class-validator";
 | 
			
		||||
import { ChildEntity, Column } from "typeorm";
 | 
			
		||||
import { Participant } from "./Participant";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a donor.
 | 
			
		||||
*/
 | 
			
		||||
@ChildEntity()
 | 
			
		||||
export class Donor extends Participant {
 | 
			
		||||
  /**
 | 
			
		||||
   * Does this donor need a receipt?.
 | 
			
		||||
   * Default: false
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsBoolean()
 | 
			
		||||
  receiptNeeded: boolean = false;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								src/models/entities/FixedDonation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/models/entities/FixedDonation.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
import { IsInt, IsPositive } from "class-validator";
 | 
			
		||||
import { ChildEntity, Column } from "typeorm";
 | 
			
		||||
import { Donation } from "./Donation";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a fixed donation.
 | 
			
		||||
*/
 | 
			
		||||
@ChildEntity()
 | 
			
		||||
export class FixedDonation extends Donation {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The donation's amount in cents (or whatever your currency's smallest unit is.).
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  @IsPositive()
 | 
			
		||||
  amount: number;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										83
									
								
								src/models/entities/GroupContact.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/models/entities/GroupContact.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
			
		||||
import {
 | 
			
		||||
  IsEmail,
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
  IsOptional,
 | 
			
		||||
  IsPhoneNumber,
 | 
			
		||||
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { Address } from "./Address";
 | 
			
		||||
import { RunnerGroup } from "./RunnerGroup";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a group's contact.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export class GroupContact {
 | 
			
		||||
  /**
 | 
			
		||||
 * Autogenerated unique id (primary key).
 | 
			
		||||
 */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The contact's first name.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  firstname: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The contact's middle name.
 | 
			
		||||
   * Optional
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  middlename?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The contact's last name.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  lastname: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The contact's address.
 | 
			
		||||
   * Optional
 | 
			
		||||
   */
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @ManyToOne(() => Address, address => address.participants, { nullable: true })
 | 
			
		||||
  address?: Address;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The contact's phone number.
 | 
			
		||||
   * Optional
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsPhoneNumber(config.phone_validation_countrycode)
 | 
			
		||||
  phone?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The contact's email address.
 | 
			
		||||
   * Optional
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsEmail()
 | 
			
		||||
  email?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
    * Used to link contacts to groups.
 | 
			
		||||
    */
 | 
			
		||||
  @OneToMany(() => RunnerGroup, group => group.contact, { nullable: true })
 | 
			
		||||
  groups: RunnerGroup[];
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										83
									
								
								src/models/entities/Participant.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/models/entities/Participant.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
			
		||||
import {
 | 
			
		||||
  IsEmail,
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
  IsOptional,
 | 
			
		||||
  IsPhoneNumber,
 | 
			
		||||
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { Address } from "./Address";
 | 
			
		||||
import { Donation } from "./Donation";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the participant interface.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
@TableInheritance({ column: { name: "type", type: "varchar" } })
 | 
			
		||||
export abstract class Participant {
 | 
			
		||||
  /**
 | 
			
		||||
   * Autogenerated unique id (primary key).
 | 
			
		||||
   */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The participant's first name.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  firstname: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The participant's middle name.
 | 
			
		||||
   * Optional
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  middlename?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The participant's last name.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  lastname: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The participant's address.
 | 
			
		||||
   * Optional
 | 
			
		||||
   */
 | 
			
		||||
  @ManyToOne(() => Address, address => address.participants, { nullable: true })
 | 
			
		||||
  address?: Address;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The participant's phone number.
 | 
			
		||||
   * Optional
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsPhoneNumber(config.phone_validation_countrycode)
 | 
			
		||||
  phone?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The participant's email address.
 | 
			
		||||
   * Optional
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsEmail()
 | 
			
		||||
  email?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to link the participant as the donor of a donation.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => Donation, donation => donation.donor, { nullable: true })
 | 
			
		||||
  donations: Donation[];
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										49
									
								
								src/models/entities/Permission.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/models/entities/Permission.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
import {
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
 | 
			
		||||
import { User } from './User';
 | 
			
		||||
import { UserGroup } from './UserGroup';
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the Permission interface.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export abstract class Permission {
 | 
			
		||||
  /**
 | 
			
		||||
   * Autogenerated unique id (primary key).
 | 
			
		||||
   */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * users
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => User, user => user.permissions, { nullable: true })
 | 
			
		||||
  users: User[]
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * groups
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => UserGroup, group => group.permissions, { nullable: true })
 | 
			
		||||
  groups: UserGroup[]
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The target
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  target: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The action type
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  action: string;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										53
									
								
								src/models/entities/Runner.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/models/entities/Runner.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
import { IsInt, IsNotEmpty } from "class-validator";
 | 
			
		||||
import { ChildEntity, ManyToOne, OneToMany } from "typeorm";
 | 
			
		||||
import { DistanceDonation } from "./DistanceDonation";
 | 
			
		||||
import { Participant } from "./Participant";
 | 
			
		||||
import { RunnerCard } from "./RunnerCard";
 | 
			
		||||
import { RunnerGroup } from "./RunnerGroup";
 | 
			
		||||
import { Scan } from "./Scan";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a runner.
 | 
			
		||||
*/
 | 
			
		||||
@ChildEntity()
 | 
			
		||||
export class Runner extends Participant {
 | 
			
		||||
  /**
 | 
			
		||||
   * The runner's associated group.
 | 
			
		||||
   */
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @ManyToOne(() => RunnerGroup, group => group.runners, { nullable: false })
 | 
			
		||||
  group: RunnerGroup;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to link runners to donations.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => DistanceDonation, distanceDonation => distanceDonation.runner, { nullable: true })
 | 
			
		||||
  distanceDonations: DistanceDonation[];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to link runners to cards.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => RunnerCard, card => card.runner, { nullable: true })
 | 
			
		||||
  cards: RunnerCard[];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to link runners to a scans
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => Scan, scan => scan.runner, { nullable: true })
 | 
			
		||||
  scans: Scan[];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns all valid scans associated with this runner.
 | 
			
		||||
   */
 | 
			
		||||
  public get validScans(): Scan[] {
 | 
			
		||||
    return this.scans.filter(scan => { scan.valid === true });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the total distance ran by this runner.
 | 
			
		||||
  */
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  public get distance(): number {
 | 
			
		||||
    return this.validScans.reduce((sum, current) => sum + current.distance, 0);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										56
									
								
								src/models/entities/RunnerCard.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/models/entities/RunnerCard.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
import {
 | 
			
		||||
  IsBoolean,
 | 
			
		||||
  IsEAN,
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
  IsOptional,
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
 | 
			
		||||
import { Runner } from "./Runner";
 | 
			
		||||
import { TrackScan } from "./TrackScan";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a card that can be scanned via a scanner station.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export class RunnerCard {
 | 
			
		||||
  /**
 | 
			
		||||
   * Autogenerated unique id (primary key).
 | 
			
		||||
   */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The runner that is currently associated with this card.
 | 
			
		||||
   */
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @ManyToOne(() => Runner, runner => runner.cards, { nullable: true })
 | 
			
		||||
  runner: Runner;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The card's code.
 | 
			
		||||
   * This has to be able to being converted to something barcode compatible.
 | 
			
		||||
   * could theoretically be autogenerated
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsEAN()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  code: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Is the card enabled (for fraud reasons)?
 | 
			
		||||
   * Default: true
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsBoolean()
 | 
			
		||||
  enabled: boolean = true;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to link cards to a track scans.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => TrackScan, scan => scan.track, { nullable: true })
 | 
			
		||||
  scans: TrackScan[];
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										45
									
								
								src/models/entities/RunnerGroup.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/models/entities/RunnerGroup.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
import {
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
  IsOptional,
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
 | 
			
		||||
import { GroupContact } from "./GroupContact";
 | 
			
		||||
import { Runner } from "./Runner";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the runnerGroup interface.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
@TableInheritance({ column: { name: "type", type: "varchar" } })
 | 
			
		||||
export abstract class RunnerGroup {
 | 
			
		||||
  /**
 | 
			
		||||
   * Autogenerated unique id (primary key).
 | 
			
		||||
   */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The group's name.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  name: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The group's contact.
 | 
			
		||||
   * Optional
 | 
			
		||||
   */
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @ManyToOne(() => GroupContact, contact => contact.groups, { nullable: true })
 | 
			
		||||
  contact?: GroupContact;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to link runners to a runner group.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => Runner, runner => runner.group, { nullable: true })
 | 
			
		||||
  runners: Runner[];
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										26
									
								
								src/models/entities/RunnerOrganisation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/models/entities/RunnerOrganisation.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
import { IsOptional } from "class-validator";
 | 
			
		||||
import { ChildEntity, ManyToOne, OneToMany } from "typeorm";
 | 
			
		||||
import { Address } from "./Address";
 | 
			
		||||
import { RunnerGroup } from "./RunnerGroup";
 | 
			
		||||
import { RunnerTeam } from "./RunnerTeam";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a runner organisation (business or school for example).
 | 
			
		||||
*/
 | 
			
		||||
@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.
 | 
			
		||||
 */
 | 
			
		||||
  @OneToMany(() => RunnerTeam, team => team.parentGroup, { nullable: true })
 | 
			
		||||
  teams: RunnerTeam[];
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								src/models/entities/RunnerTeam.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/models/entities/RunnerTeam.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
import { IsNotEmpty } from "class-validator";
 | 
			
		||||
import { ChildEntity, ManyToOne } from "typeorm";
 | 
			
		||||
import { RunnerGroup } from "./RunnerGroup";
 | 
			
		||||
import { RunnerOrganisation } from "./RunnerOrganisation";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a runner team (class or deparment for example).
 | 
			
		||||
*/
 | 
			
		||||
@ChildEntity()
 | 
			
		||||
export class RunnerTeam extends RunnerGroup {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The team's parent group.
 | 
			
		||||
   * Optional
 | 
			
		||||
   */
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @ManyToOne(() => RunnerOrganisation, org => org.teams, { nullable: true })
 | 
			
		||||
  parentGroup?: RunnerOrganisation;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										45
									
								
								src/models/entities/Scan.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/models/entities/Scan.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
import {
 | 
			
		||||
  IsBoolean,
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
 | 
			
		||||
  IsPositive
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
 | 
			
		||||
import { Runner } from "./Runner";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the scan interface.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
@TableInheritance({ column: { name: "type", type: "varchar" } })
 | 
			
		||||
export abstract class Scan {
 | 
			
		||||
  /**
 | 
			
		||||
   * Autogenerated unique id (primary key).
 | 
			
		||||
   */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The associated runner.
 | 
			
		||||
   */
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @ManyToOne(() => Runner, runner => runner.scans, { nullable: false })
 | 
			
		||||
  runner: Runner;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The scan's distance in meters.
 | 
			
		||||
   */
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  @IsPositive()
 | 
			
		||||
  abstract distance: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
 * Is the scan valid (for fraud reasons).
 | 
			
		||||
 * Default: true
 | 
			
		||||
 */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsBoolean()
 | 
			
		||||
  valid: boolean = true;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										60
									
								
								src/models/entities/ScanStation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/models/entities/ScanStation.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
import {
 | 
			
		||||
  IsBoolean,
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
  IsOptional,
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
 | 
			
		||||
import { Track } from "./Track";
 | 
			
		||||
import { TrackScan } from "./TrackScan";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ScannerStations have the ability to create scans for specific tracks.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export class ScanStation {
 | 
			
		||||
  /**
 | 
			
		||||
   * Autogenerated unique id (primary key).
 | 
			
		||||
   */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The station's description.
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  description?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The track this station is associated with.
 | 
			
		||||
   */
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @ManyToOne(() => Track, track => track.stations, { nullable: false })
 | 
			
		||||
  track: Track;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The station's api key.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  key: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Is the station enabled (for fraud reasons)?
 | 
			
		||||
   * Default: true
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsBoolean()
 | 
			
		||||
  enabled: boolean = true;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to link track scans to a scan station.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => TrackScan, scan => scan.track, { nullable: true })
 | 
			
		||||
  scans: TrackScan[];
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										51
									
								
								src/models/entities/Track.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/models/entities/Track.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
import {
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
 | 
			
		||||
  IsPositive,
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
 | 
			
		||||
import { ScanStation } from "./ScanStation";
 | 
			
		||||
import { TrackScan } from "./TrackScan";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a track of given length.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export class Track {
 | 
			
		||||
  /**
 | 
			
		||||
   * Autogenerated unique id (primary key).
 | 
			
		||||
   */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The track's name.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  name: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The track's length/distance in meters.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  @IsPositive()
 | 
			
		||||
  distance: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to link scan stations to track.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => ScanStation, station => station.track, { nullable: true })
 | 
			
		||||
  stations: ScanStation[];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to link track scans to a track.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => TrackScan, scan => scan.track, { nullable: true })
 | 
			
		||||
  scans: TrackScan[];
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										56
									
								
								src/models/entities/TrackScan.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/models/entities/TrackScan.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
import {
 | 
			
		||||
  IsDateString,
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
 | 
			
		||||
  IsPositive
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { ChildEntity, Column, ManyToOne } from "typeorm";
 | 
			
		||||
import { RunnerCard } from "./RunnerCard";
 | 
			
		||||
import { Scan } from "./Scan";
 | 
			
		||||
import { ScanStation } from "./ScanStation";
 | 
			
		||||
import { Track } from "./Track";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the scan interface.
 | 
			
		||||
*/
 | 
			
		||||
@ChildEntity()
 | 
			
		||||
export class TrackScan extends Scan {
 | 
			
		||||
  /**
 | 
			
		||||
   * The associated track.
 | 
			
		||||
   */
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @ManyToOne(() => Track, track => track.scans, { nullable: true })
 | 
			
		||||
  track: Track;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The associated card.
 | 
			
		||||
   */
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @ManyToOne(() => RunnerCard, card => card.scans, { nullable: true })
 | 
			
		||||
  card: RunnerCard;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The scanning station.
 | 
			
		||||
   */
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @ManyToOne(() => ScanStation, station => station.scans, { nullable: true })
 | 
			
		||||
  station: ScanStation;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The scan's distance in meters.
 | 
			
		||||
   */
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  @IsPositive()
 | 
			
		||||
  public get distance(): number {
 | 
			
		||||
    return this.track.distance;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The scan's creation timestamp.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsDateString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  timestamp: string;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										142
									
								
								src/models/entities/User.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								src/models/entities/User.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
			
		||||
import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUUID } from "class-validator";
 | 
			
		||||
import { Column, Entity, JoinTable, ManyToMany, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { Permission } from './Permission';
 | 
			
		||||
import { UserAction } from './UserAction';
 | 
			
		||||
import { UserGroup } from './UserGroup';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a admin user.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export class User {
 | 
			
		||||
  /**
 | 
			
		||||
  * autogenerated unique id (primary key).
 | 
			
		||||
  */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * uuid
 | 
			
		||||
  */
 | 
			
		||||
  @Column({ unique: true })
 | 
			
		||||
  @IsUUID(4)
 | 
			
		||||
  uuid: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * user email
 | 
			
		||||
  */
 | 
			
		||||
  @Column({ nullable: true, unique: true })
 | 
			
		||||
  @IsEmail()
 | 
			
		||||
  email?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * user phone
 | 
			
		||||
  */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsPhoneNumber(config.phone_validation_countrycode)
 | 
			
		||||
  phone?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * username
 | 
			
		||||
  */
 | 
			
		||||
  @Column({ nullable: true, unique: true })
 | 
			
		||||
  @IsString()
 | 
			
		||||
  username?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * firstname
 | 
			
		||||
  */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  firstname: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * middlename
 | 
			
		||||
  */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  middlename?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * lastname
 | 
			
		||||
  */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  lastname: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * password
 | 
			
		||||
  */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  password: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * permissions
 | 
			
		||||
  */
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @ManyToOne(() => Permission, permission => permission.users, { nullable: true })
 | 
			
		||||
  permissions?: Permission[];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * groups
 | 
			
		||||
  */
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @ManyToMany(() => UserGroup, { nullable: true })
 | 
			
		||||
  @JoinTable()
 | 
			
		||||
  groups: UserGroup[];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * is user enabled?
 | 
			
		||||
  */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsBoolean()
 | 
			
		||||
  enabled: boolean = true;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * jwt refresh count
 | 
			
		||||
  */
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  @Column({ default: 1 })
 | 
			
		||||
  refreshTokenCount?: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * profilepic
 | 
			
		||||
  */
 | 
			
		||||
  @Column({ nullable: true, unique: true })
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  profilePic?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * actions
 | 
			
		||||
   */
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @OneToMany(() => UserAction, action => action.user, { nullable: true })
 | 
			
		||||
  actions: UserAction[]
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * calculate all permissions
 | 
			
		||||
   */
 | 
			
		||||
  public get calc_permissions(): Permission[] {
 | 
			
		||||
    let final_permissions = []
 | 
			
		||||
    this.groups.forEach((permission) => {
 | 
			
		||||
      if (!final_permissions.includes(permission)) {
 | 
			
		||||
        final_permissions.push(permission)
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    this.permissions.forEach((permission) => {
 | 
			
		||||
      if (!final_permissions.includes(permission)) {
 | 
			
		||||
        final_permissions.push(permission)
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    return final_permissions
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										51
									
								
								src/models/entities/UserAction.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/models/entities/UserAction.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
import {
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
  IsOptional,
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
 | 
			
		||||
import { User } from './User';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the UserAction interface.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export class UserAction {
 | 
			
		||||
  /**
 | 
			
		||||
   * Autogenerated unique id (primary key).
 | 
			
		||||
   */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * user
 | 
			
		||||
   */
 | 
			
		||||
  @ManyToOne(() => User, user => user.actions)
 | 
			
		||||
  user: User
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The actions's target (e.g. Track#2)
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  target: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The actions's action (e.g. UPDATE)
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  action: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The description of change (before-> after; e.g. distance:15->17)
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  changed: string;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										43
									
								
								src/models/entities/UserGroup.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/models/entities/UserGroup.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
import {
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
  IsOptional,
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
 | 
			
		||||
import { Permission } from "./Permission";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the UserGroup interface.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export class UserGroup {
 | 
			
		||||
  /**
 | 
			
		||||
   * Autogenerated unique id (primary key).
 | 
			
		||||
   */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
     * permissions
 | 
			
		||||
     */
 | 
			
		||||
  @ManyToOne(() => Permission, permission => permission.groups, { nullable: true })
 | 
			
		||||
  permissions: Permission[];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The group's name
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  name: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The group's description
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  description?: string;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								src/models/responses/ResponseAuth.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/models/responses/ResponseAuth.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
import { IsInt, IsString } from 'class-validator';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a auth object
 | 
			
		||||
*/
 | 
			
		||||
export class Auth {
 | 
			
		||||
  /**
 | 
			
		||||
  * access_token - JWT shortterm access token
 | 
			
		||||
  */
 | 
			
		||||
  @IsString()
 | 
			
		||||
  access_token: string;
 | 
			
		||||
  /**
 | 
			
		||||
  * 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
 | 
			
		||||
  */
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  access_token_expires_at: number;
 | 
			
		||||
  /**
 | 
			
		||||
  * refresh_token_expires_at - unix timestamp of access token expiry
 | 
			
		||||
  */
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  refresh_token_expires_at: number;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								src/models/responses/ResponseEmpty.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/models/responses/ResponseEmpty.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a empty response object
 | 
			
		||||
*/
 | 
			
		||||
export class ResponseEmpty {
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								src/models/responses/ResponseLogout.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/models/responses/ResponseLogout.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a Logout object
 | 
			
		||||
*/
 | 
			
		||||
export class Logout {
 | 
			
		||||
  /**
 | 
			
		||||
  * timestamp of logout
 | 
			
		||||
  */
 | 
			
		||||
  @IsString()
 | 
			
		||||
  timestamp: number;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										61
									
								
								src/models/responses/ResponseParticipant.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/models/responses/ResponseParticipant.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
import {
 | 
			
		||||
    IsInt,
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Participant } from '../entities/Participant';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a participant response.
 | 
			
		||||
*/
 | 
			
		||||
export abstract class ResponseParticipant {
 | 
			
		||||
    /**
 | 
			
		||||
     * Autogenerated unique id (primary key).
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    id: number;;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The participant's first name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    firstname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The participant's middle name.
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    middlename?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The participant's last name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    lastname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The participant's phone number.
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    phone?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The participant's e-mail address.
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    email?: string;
 | 
			
		||||
 | 
			
		||||
    public constructor(participant: Participant) {
 | 
			
		||||
        this.id = participant.id;
 | 
			
		||||
        this.firstname = participant.firstname;
 | 
			
		||||
        this.middlename = participant.middlename;
 | 
			
		||||
        this.lastname = participant.lastname;
 | 
			
		||||
        this.phone = participant.phone;
 | 
			
		||||
        this.email = participant.email;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								src/models/responses/ResponseRunner.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/models/responses/ResponseRunner.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
import {
 | 
			
		||||
    IsInt,
 | 
			
		||||
    IsObject
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Runner } from '../entities/Runner';
 | 
			
		||||
import { RunnerGroup } from '../entities/RunnerGroup';
 | 
			
		||||
import { ResponseParticipant } from './ResponseParticipant';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines RunnerTeam's response class.
 | 
			
		||||
*/
 | 
			
		||||
export class ResponseRunner extends ResponseParticipant {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The runner's currently ran distance in meters.
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    distance: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The runner's group.
 | 
			
		||||
     */
 | 
			
		||||
    @IsObject()
 | 
			
		||||
    group: RunnerGroup;
 | 
			
		||||
 | 
			
		||||
    public constructor(runner: Runner) {
 | 
			
		||||
        super(runner);
 | 
			
		||||
        this.distance = runner.scans.filter(scan => { scan.valid === true }).reduce((sum, current) => sum + current.distance, 0);
 | 
			
		||||
        this.group = runner.group;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										55
									
								
								src/models/responses/ResponseRunnerGroup.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/models/responses/ResponseRunnerGroup.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
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.
 | 
			
		||||
*/
 | 
			
		||||
export abstract class ResponseRunnerGroup {
 | 
			
		||||
    /**
 | 
			
		||||
     * Autogenerated unique id (primary key).
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    id: number;;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The groups's name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    name: string;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The group's contact.
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsObject()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    contact?: GroupContact;
 | 
			
		||||
 | 
			
		||||
    public constructor(group: RunnerGroup) {
 | 
			
		||||
        this.id = group.id;
 | 
			
		||||
        this.name = group.name;
 | 
			
		||||
        this.contact = group.contact;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								src/models/responses/ResponseRunnerOrganisation.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/models/responses/ResponseRunnerOrganisation.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
import {
 | 
			
		||||
    IsArray,
 | 
			
		||||
    IsNotEmpty,
 | 
			
		||||
    IsObject
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Address } from '../entities/Address';
 | 
			
		||||
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
 | 
			
		||||
import { RunnerTeam } from '../entities/RunnerTeam';
 | 
			
		||||
import { ResponseRunnerGroup } from './ResponseRunnerGroup';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines RunnerOrgs's response class.
 | 
			
		||||
*/
 | 
			
		||||
export class ResponseRunnerOrganisation extends ResponseRunnerGroup {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The orgs's address.
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsObject()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    address?: Address;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The orgs associated teams.
 | 
			
		||||
     */
 | 
			
		||||
    @IsObject()
 | 
			
		||||
    @IsArray()
 | 
			
		||||
    teams: RunnerTeam[];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public constructor(org: RunnerOrganisation) {
 | 
			
		||||
        super(org);
 | 
			
		||||
        this.address = org.address;
 | 
			
		||||
        this.teams = org.teams;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										26
									
								
								src/models/responses/ResponseRunnerTeam.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/models/responses/ResponseRunnerTeam.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
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.
 | 
			
		||||
*/
 | 
			
		||||
export class ResponseRunnerTeam extends ResponseRunnerGroup {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The team's parent group (organisation).
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsObject()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    parentGroup: RunnerOrganisation;
 | 
			
		||||
 | 
			
		||||
    public constructor(team: RunnerTeam) {
 | 
			
		||||
        super(team);
 | 
			
		||||
        this.parentGroup = team.parentGroup;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								src/models/responses/ResponseTrack.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/models/responses/ResponseTrack.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
import {
 | 
			
		||||
    IsInt,
 | 
			
		||||
 | 
			
		||||
    IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Track } from '../entities/Track';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a track of given length.
 | 
			
		||||
*/
 | 
			
		||||
export class ResponseTrack {
 | 
			
		||||
    /**
 | 
			
		||||
     * Autogenerated unique id (primary key).
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    id: number;;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The track's name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    name: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The track's length/distance in meters.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    distance: number;
 | 
			
		||||
 | 
			
		||||
    public constructor(track: Track) {
 | 
			
		||||
        this.id = track.id;
 | 
			
		||||
        this.name = track.name;
 | 
			
		||||
        this.distance = track.distance;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,10 +0,0 @@
 | 
			
		||||
import { Router } from "express";
 | 
			
		||||
import jwtauth from "../../middlewares/jwtauth";
 | 
			
		||||
 | 
			
		||||
const router = Router();
 | 
			
		||||
 | 
			
		||||
router.use("*", jwtauth, async (req, res, next) => {
 | 
			
		||||
  return res.send("ok");
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default router;
 | 
			
		||||
@@ -1,12 +1,20 @@
 | 
			
		||||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "target": "es2017",
 | 
			
		||||
    "target": "ES2020",
 | 
			
		||||
    "module": "commonjs",
 | 
			
		||||
    "rootDir": "./src",
 | 
			
		||||
    "outDir": "./build",
 | 
			
		||||
    "esModuleInterop": true,
 | 
			
		||||
    "strict": false,
 | 
			
		||||
    "experimentalDecorators": true,
 | 
			
		||||
    "emitDecoratorMetadata": true
 | 
			
		||||
  }
 | 
			
		||||
    "emitDecoratorMetadata": true,
 | 
			
		||||
    "sourceMap": false
 | 
			
		||||
  },
 | 
			
		||||
  "include": [
 | 
			
		||||
    "src/**/*"
 | 
			
		||||
  ],
 | 
			
		||||
  "exclude": [
 | 
			
		||||
    "node_modules",
 | 
			
		||||
    "**/*.spec.ts"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user