diff --git a/package.json b/package.json
index 952b62a..a6f868c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@odit/lfk-backend",
- "version": "0.0.10",
+ "version": "0.0.11",
"main": "src/app.ts",
"repository": "https://git.odit.services/lfk/backend",
"author": {
@@ -22,11 +22,11 @@
],
"license": "CC-BY-NC-SA-4.0",
"dependencies": {
- "argon2": "^0.27.0",
+ "argon2": "^0.27.1",
"body-parser": "^1.19.0",
"class-transformer": "^0.3.1",
"class-validator": "^0.12.2",
- "class-validator-jsonschema": "^2.0.3",
+ "class-validator-jsonschema": "^2.1.0",
"consola": "^2.15.0",
"cookie": "^0.4.1",
"cookie-parser": "^1.4.5",
@@ -39,33 +39,34 @@
"pg": "^8.5.1",
"reflect-metadata": "^0.1.13",
"routing-controllers": "^0.9.0-alpha.6",
- "routing-controllers-openapi": "^2.1.0",
+ "routing-controllers-openapi": "^2.2.0",
"sqlite3": "5.0.0",
"typeorm": "^0.2.29",
"typeorm-routing-controllers-extensions": "^0.2.0",
"typeorm-seeding": "^1.6.1",
- "uuid": "^8.3.1",
+ "uuid": "^8.3.2",
"validator": "^13.5.2"
},
"devDependencies": {
- "@odit/license-exporter": "^0.0.8",
- "@types/cors": "^2.8.8",
+ "@odit/license-exporter": "^0.0.9",
+ "@types/cors": "^2.8.9",
"@types/csvtojson": "^1.1.5",
"@types/express": "^4.17.9",
"@types/jest": "^26.0.16",
"@types/jsonwebtoken": "^8.5.0",
- "@types/node": "^14.14.9",
+ "@types/node": "^14.14.20",
"@types/uuid": "^8.3.0",
- "axios": "^0.21.0",
+ "axios": "^0.21.1",
"cp-cli": "^2.0.0",
"jest": "^26.6.3",
- "nodemon": "^2.0.6",
- "rimraf": "^2.7.1",
- "start-server-and-test": "^1.11.6",
+ "nodemon": "^2.0.7",
+ "release-it": "^14.2.2",
+ "rimraf": "^3.0.2",
+ "start-server-and-test": "^1.11.7",
"ts-jest": "^26.4.4",
- "ts-node": "^9.0.0",
- "typedoc": "^0.19.2",
- "typescript": "^4.1.2"
+ "ts-node": "^9.1.1",
+ "typedoc": "^0.20.14",
+ "typescript": "^4.1.3"
},
"scripts": {
"dev": "nodemon src/app.ts",
@@ -76,7 +77,21 @@
"test:ci": "start-server-and-test dev http://localhost:4010/api/docs/openapi.json test",
"seed": "ts-node ./node_modules/typeorm/cli.js schema:sync && ts-node ./node_modules/typeorm-seeding/dist/cli.js seed",
"openapi:export": "ts-node scripts/openapi_export.ts",
- "licenses:export": "license-exporter --md"
+ "licenses:export": "license-exporter --md",
+ "release": "release-it"
+ },
+ "release-it": {
+ "git": {
+ "requireCleanWorkingDir": false,
+ "requireBranch": "main",
+ "push": false,
+ "tag": true,
+ "tagName": "v${version}",
+ "tagAnnotation": "v${version}"
+ },
+ "npm": {
+ "publish": false
+ }
},
"nodemonConfig": {
"ignore": [
diff --git a/scripts/openapi_export.ts b/scripts/openapi_export.ts
index b72a369..83fd879 100644
--- a/scripts/openapi_export.ts
+++ b/scripts/openapi_export.ts
@@ -3,7 +3,7 @@ import consola from "consola";
import fs from "fs";
import "reflect-metadata";
import { createExpressServer, getMetadataArgsStorage } from "routing-controllers";
-import { routingControllersToSpec } from 'routing-controllers-openapi';
+import { generateSpec } from '../src/apispec';
import { config } from '../src/config';
import authchecker from "../src/middlewares/authchecker";
import { ErrorHandler } from '../src/middlewares/ErrorHandler';
@@ -24,46 +24,7 @@ const schemas = validationMetadatasToSchemas({
});
//Spec creation based on the previously created schemas
-const spec = routingControllersToSpec(
- storage,
- {
- routePrefix: "/api"
- },
- {
- components: {
- schemas,
- "securitySchemes": {
- "AuthToken": {
- "type": "http",
- "scheme": "bearer",
- "bearerFormat": "JWT",
- description: "A JWT based access token. Use /api/auth/login or /api/auth/refresh to get one."
- },
- "RefreshTokenCookie": {
- "type": "apiKey",
- "in": "cookie",
- "name": "lfk_backend__refresh_token",
- description: "A cookie containing a JWT based refreh token. Attention: Doesn't work in swagger-ui. Use /api/auth/login or /api/auth/refresh to get one."
- },
- "StatsApiToken": {
- "type": "http",
- "scheme": "bearer",
- description: "Api token that can be obtained by creating a new stats client (post to /api/statsclients). Only valid for obtaining stats."
- },
- "StationApiToken": {
- "type": "http",
- "scheme": "bearer",
- description: "Api token that can be obtained by creating a new scan station (post to /api/stations). Only valid for creating scans."
- }
- }
- },
- info: {
- description: "The the backend API for the LfK! runner system.",
- title: "LfK! Backend API",
- version: "0.0.8",
- },
- }
-);
+const spec = generateSpec(storage, schemas);
try {
fs.writeFileSync("./openapi.json", JSON.stringify(spec), { encoding: "utf-8" });
diff --git a/src/apispec.ts b/src/apispec.ts
new file mode 100644
index 0000000..f0a6d85
--- /dev/null
+++ b/src/apispec.ts
@@ -0,0 +1,50 @@
+import { MetadataArgsStorage } from 'routing-controllers';
+import { routingControllersToSpec } from 'routing-controllers-openapi';
+
+/**
+ * This function generates a the openapi spec from route metadata and type schemas.
+ * @param storage MetadataArgsStorage object generated by routing-controllers.
+ * @param schemas MetadataArgsStorage object generated by class-validator-jsonschema.
+ */
+export function generateSpec(storage: MetadataArgsStorage, schemas) {
+ return routingControllersToSpec(
+ storage,
+ {
+ routePrefix: "/api"
+ },
+ {
+ components: {
+ schemas,
+ "securitySchemes": {
+ "AuthToken": {
+ "type": "http",
+ "scheme": "bearer",
+ "bearerFormat": "JWT",
+ description: "A JWT based access token. Use /api/auth/login or /api/auth/refresh to get one."
+ },
+ "RefreshTokenCookie": {
+ "type": "apiKey",
+ "in": "cookie",
+ "name": "lfk_backend__refresh_token",
+ description: "A cookie containing a JWT based refreh token. Attention: Doesn't work in swagger-ui. Use /api/auth/login or /api/auth/refresh to get one."
+ },
+ "StatsApiToken": {
+ "type": "http",
+ "scheme": "bearer",
+ description: "Api token that can be obtained by creating a new stats client (post to /api/statsclients). Only valid for obtaining stats."
+ },
+ "StationApiToken": {
+ "type": "http",
+ "scheme": "bearer",
+ description: "Api token that can be obtained by creating a new scan station (post to /api/stations). Only valid for creating scans."
+ }
+ }
+ },
+ info: {
+ description: "The the backend API for the LfK! runner system.",
+ title: "LfK! Backend API",
+ version: process.env.npm_package_version
+ },
+ }
+ );
+}
\ No newline at end of file
diff --git a/src/controllers/AuthController.ts b/src/controllers/AuthController.ts
index 9672de5..507ac38 100644
--- a/src/controllers/AuthController.ts
+++ b/src/controllers/AuthController.ts
@@ -2,12 +2,12 @@ import { Body, CookieParam, JsonController, Param, Post, Req, Res } from 'routin
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { IllegalJWTError, InvalidCredentialsError, JwtNotProvidedError, PasswordNeededError, RefreshTokenCountInvalidError, UsernameOrEmailNeededError } from '../errors/AuthError';
import { UserNotFoundError } from '../errors/UserErrors';
-import { CreateAuth } from '../models/actions/CreateAuth';
-import { CreateResetToken } from '../models/actions/CreateResetToken';
+import { CreateAuth } from '../models/actions/create/CreateAuth';
+import { CreateResetToken } from '../models/actions/create/CreateResetToken';
import { HandleLogout } from '../models/actions/HandleLogout';
import { RefreshAuth } from '../models/actions/RefreshAuth';
import { ResetPassword } from '../models/actions/ResetPassword';
-import { Auth } from '../models/responses/ResponseAuth';
+import { ResponseAuth } from '../models/responses/ResponseAuth';
import { Logout } from '../models/responses/ResponseLogout';
@JsonController('/auth')
@@ -16,7 +16,7 @@ export class AuthController {
}
@Post("/login")
- @ResponseSchema(Auth)
+ @ResponseSchema(ResponseAuth)
@ResponseSchema(InvalidCredentialsError)
@ResponseSchema(UserNotFoundError)
@ResponseSchema(UsernameOrEmailNeededError)
@@ -60,7 +60,7 @@ export class AuthController {
}
@Post("/refresh")
- @ResponseSchema(Auth)
+ @ResponseSchema(ResponseAuth)
@ResponseSchema(JwtNotProvidedError)
@ResponseSchema(IllegalJWTError)
@ResponseSchema(UserNotFoundError)
@@ -82,7 +82,7 @@ export class AuthController {
}
@Post("/reset")
- @ResponseSchema(Auth)
+ @ResponseSchema(ResponseAuth)
@ResponseSchema(UserNotFoundError)
@ResponseSchema(UsernameOrEmailNeededError)
@OpenAPI({ description: "Request a password reset token.
This will provide you with a reset token that you can use by posting to /api/auth/reset/{token}." })
@@ -92,7 +92,7 @@ export class AuthController {
}
@Post("/reset/:token")
- @ResponseSchema(Auth)
+ @ResponseSchema(ResponseAuth)
@ResponseSchema(UserNotFoundError)
@ResponseSchema(UsernameOrEmailNeededError)
@OpenAPI({ description: "Reset a user's utilising a valid password reset token.
This will set the user's password to the one you provided in the body.
To get a reset token post to /api/auth/reset with your username." })
diff --git a/src/controllers/DonorController.ts b/src/controllers/DonorController.ts
index 54a5ba4..f6b8527 100644
--- a/src/controllers/DonorController.ts
+++ b/src/controllers/DonorController.ts
@@ -2,8 +2,8 @@ import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { DonorIdsNotMatchingError, DonorNotFoundError } from '../errors/DonorErrors';
-import { CreateDonor } from '../models/actions/CreateDonor';
-import { UpdateDonor } from '../models/actions/UpdateDonor';
+import { CreateDonor } from '../models/actions/create/CreateDonor';
+import { UpdateDonor } from '../models/actions/update/UpdateDonor';
import { Donor } from '../models/entities/Donor';
import { ResponseDonor } from '../models/responses/ResponseDonor';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
@@ -52,7 +52,7 @@ export class DonorController {
async post(@Body({ validate: true }) createRunner: CreateDonor) {
let donor;
try {
- donor = await createRunner.toDonor();
+ donor = await createRunner.toEntity();
} catch (error) {
throw error;
}
@@ -78,7 +78,7 @@ export class DonorController {
throw new DonorIdsNotMatchingError();
}
- await this.donorRepository.save(await donor.updateDonor(oldDonor));
+ await this.donorRepository.save(await donor.update(oldDonor));
return new ResponseDonor(await this.donorRepository.findOne({ id: id }));
}
diff --git a/src/controllers/PermissionController.ts b/src/controllers/PermissionController.ts
index 15ad301..e123e81 100644
--- a/src/controllers/PermissionController.ts
+++ b/src/controllers/PermissionController.ts
@@ -3,8 +3,8 @@ import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { PermissionIdsNotMatchingError, PermissionNeedsPrincipalError, PermissionNotFoundError } from '../errors/PermissionErrors';
import { PrincipalNotFoundError } from '../errors/PrincipalErrors';
-import { CreatePermission } from '../models/actions/CreatePermission';
-import { UpdatePermission } from '../models/actions/UpdatePermission';
+import { CreatePermission } from '../models/actions/create/CreatePermission';
+import { UpdatePermission } from '../models/actions/update/UpdatePermission';
import { Permission } from '../models/entities/Permission';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponsePermission } from '../models/responses/ResponsePermission';
@@ -58,7 +58,7 @@ export class PermissionController {
async post(@Body({ validate: true }) createPermission: CreatePermission) {
let permission;
try {
- permission = await createPermission.toPermission();
+ permission = await createPermission.toEntity();
} catch (error) {
throw error;
}
@@ -96,7 +96,7 @@ export class PermissionController {
return new ResponsePermission(existingPermission);
}
- await this.permissionRepository.save(await permission.updatePermission(oldPermission));
+ await this.permissionRepository.save(await permission.update(oldPermission));
return new ResponsePermission(await this.permissionRepository.findOne({ id: permission.id }, { relations: ['principal'] }));
}
diff --git a/src/controllers/RunnerCardController.ts b/src/controllers/RunnerCardController.ts
new file mode 100644
index 0000000..ae74e86
--- /dev/null
+++ b/src/controllers/RunnerCardController.ts
@@ -0,0 +1,106 @@
+import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
+import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
+import { getConnectionManager, Repository } from 'typeorm';
+import { RunnerCardHasScansError, RunnerCardIdsNotMatchingError, RunnerCardNotFoundError } from '../errors/RunnerCardErrors';
+import { RunnerNotFoundError } from '../errors/RunnerErrors';
+import { CreateRunnerCard } from '../models/actions/create/CreateRunnerCard';
+import { UpdateRunnerCard } from '../models/actions/update/UpdateRunnerCard';
+import { RunnerCard } from '../models/entities/RunnerCard';
+import { ResponseEmpty } from '../models/responses/ResponseEmpty';
+import { ResponseRunnerCard } from '../models/responses/ResponseRunnerCard';
+import { ScanController } from './ScanController';
+
+@JsonController('/cards')
+@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
+export class RunnerCardController {
+ private cardRepository: Repository;
+
+ /**
+ * Gets the repository of this controller's model/entity.
+ */
+ constructor() {
+ this.cardRepository = getConnectionManager().get().getRepository(RunnerCard);
+ }
+
+ @Get()
+ @Authorized("CARD:GET")
+ @ResponseSchema(ResponseRunnerCard, { isArray: true })
+ @OpenAPI({ description: 'Lists all card.' })
+ async getAll() {
+ let responseCards: ResponseRunnerCard[] = new Array();
+ const cards = await this.cardRepository.find({ relations: ['runner'] });
+ cards.forEach(card => {
+ responseCards.push(new ResponseRunnerCard(card));
+ });
+ return responseCards;
+ }
+
+ @Get('/:id')
+ @Authorized("CARD:GET")
+ @ResponseSchema(ResponseRunnerCard)
+ @ResponseSchema(RunnerCardNotFoundError, { statusCode: 404 })
+ @OnUndefined(RunnerCardNotFoundError)
+ @OpenAPI({ description: "Lists all information about the card whose id got provided." })
+ async getOne(@Param('id') id: number) {
+ let card = await this.cardRepository.findOne({ id: id }, { relations: ['runner'] });
+ if (!card) { throw new RunnerCardNotFoundError(); }
+ return card.toResponse();
+ }
+
+ @Post()
+ @Authorized("CARD:CREATE")
+ @ResponseSchema(ResponseRunnerCard)
+ @ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
+ @OpenAPI({ description: "Create a new card.
You can provide a associated runner by id but you don't have to." })
+ async post(@Body({ validate: true }) createCard: CreateRunnerCard) {
+ let card = await createCard.toEntity();
+ card = await this.cardRepository.save(card);
+ return (await this.cardRepository.findOne({ id: card.id }, { relations: ['runner'] })).toResponse();
+ }
+
+ @Put('/:id')
+ @Authorized("CARD:UPDATE")
+ @ResponseSchema(ResponseRunnerCard)
+ @ResponseSchema(RunnerCardNotFoundError, { statusCode: 404 })
+ @ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
+ @ResponseSchema(RunnerCardIdsNotMatchingError, { statusCode: 406 })
+ @OpenAPI({ description: "Update the card whose id you provided.
Scans created via this card will still be associated with the old runner.
Please remember that ids can't be changed." })
+ async put(@Param('id') id: number, @Body({ validate: true }) card: UpdateRunnerCard) {
+ let oldCard = await this.cardRepository.findOne({ id: id });
+
+ if (!oldCard) {
+ throw new RunnerCardNotFoundError();
+ }
+
+ if (oldCard.id != card.id) {
+ throw new RunnerCardIdsNotMatchingError();
+ }
+
+ await this.cardRepository.save(await card.update(oldCard));
+ return (await this.cardRepository.findOne({ id: id }, { relations: ['runner'] })).toResponse();
+ }
+
+ @Delete('/:id')
+ @Authorized("CARD:DELETE")
+ @ResponseSchema(ResponseRunnerCard)
+ @ResponseSchema(ResponseEmpty, { statusCode: 204 })
+ @ResponseSchema(RunnerCardHasScansError, { statusCode: 406 })
+ @OnUndefined(204)
+ @OpenAPI({ description: "Delete the card whose id you provided.
If no card with this id exists it will just return 204(no content).
If the card still has scans associated you have to provide the force=true query param (warning: this deletes all scans associated with by this card - please disable it instead or just remove the runner association)." })
+ async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
+ let card = await this.cardRepository.findOne({ id: id });
+ if (!card) { return null; }
+
+ const cardScans = (await this.cardRepository.findOne({ id: id }, { relations: ["scans"] })).scans;
+ if (cardScans.length != 0 && !force) {
+ throw new RunnerCardHasScansError();
+ }
+ const scanController = new ScanController;
+ for (let scan of cardScans) {
+ await scanController.remove(scan.id, force);
+ }
+
+ await this.cardRepository.delete(card);
+ return card.toResponse();
+ }
+}
\ No newline at end of file
diff --git a/src/controllers/RunnerController.ts b/src/controllers/RunnerController.ts
index 9eeb916..2d83a02 100644
--- a/src/controllers/RunnerController.ts
+++ b/src/controllers/RunnerController.ts
@@ -3,11 +3,13 @@ import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { RunnerGroupNeededError, RunnerIdsNotMatchingError, RunnerNotFoundError } from '../errors/RunnerErrors';
import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors';
-import { CreateRunner } from '../models/actions/CreateRunner';
-import { UpdateRunner } from '../models/actions/UpdateRunner';
+import { CreateRunner } from '../models/actions/create/CreateRunner';
+import { UpdateRunner } from '../models/actions/update/UpdateRunner';
import { Runner } from '../models/entities/Runner';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseRunner } from '../models/responses/ResponseRunner';
+import { RunnerCardController } from './RunnerCardController';
+import { ScanController } from './ScanController';
@JsonController('/runners')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
@@ -27,7 +29,7 @@ export class RunnerController {
@OpenAPI({ description: 'Lists all runners from all teams/orgs.
This includes the runner\'s group and distance ran.' })
async getAll() {
let responseRunners: ResponseRunner[] = new Array();
- const runners = await this.runnerRepository.find({ relations: ['scans', 'group'] });
+ const runners = await this.runnerRepository.find({ relations: ['scans', 'group', 'scans.track', 'cards'] });
runners.forEach(runner => {
responseRunners.push(new ResponseRunner(runner));
});
@@ -41,7 +43,7 @@ export class RunnerController {
@OnUndefined(RunnerNotFoundError)
@OpenAPI({ description: 'Lists all information about the runner whose id got provided.' })
async getOne(@Param('id') id: number) {
- let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] })
+ let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'scans.track', 'cards'] })
if (!runner) { throw new RunnerNotFoundError(); }
return new ResponseRunner(runner);
}
@@ -55,13 +57,13 @@ export class RunnerController {
async post(@Body({ validate: true }) createRunner: CreateRunner) {
let runner;
try {
- runner = await createRunner.toRunner();
+ runner = await createRunner.toEntity();
} catch (error) {
throw error;
}
runner = await this.runnerRepository.save(runner)
- return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group'] }));
+ return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'scans.track', 'cards'] }));
}
@Put('/:id')
@@ -81,8 +83,8 @@ export class RunnerController {
throw new RunnerIdsNotMatchingError();
}
- await this.runnerRepository.save(await runner.updateRunner(oldRunner));
- return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] }));
+ await this.runnerRepository.save(await runner.update(oldRunner));
+ return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'scans.track', 'cards'] }));
}
@Delete('/:id')
@@ -90,16 +92,28 @@ export class RunnerController {
@ResponseSchema(ResponseRunner)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@OnUndefined(204)
- @OpenAPI({ description: 'Delete the runner whose id you provided.
If no runner with this id exists it will just return 204(no content).' })
+ @OpenAPI({ description: 'Delete the runner whose id you provided.
This will also delete all scans and cards associated with the runner.
If no runner with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let runner = await this.runnerRepository.findOne({ id: id });
if (!runner) { return null; }
- const responseRunner = await this.runnerRepository.findOne(runner, { relations: ['scans', 'group'] });
+ const responseRunner = await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'scans.track', 'cards'] });
if (!runner) {
throw new RunnerNotFoundError();
}
+ const runnerCards = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["cards"] })).cards;
+ const cardController = new RunnerCardController;
+ for (let scan of runnerCards) {
+ await cardController.remove(scan.id, force);
+ }
+
+ const runnerScans = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["scans"] })).scans;
+ const scanController = new ScanController;
+ for (let scan of runnerScans) {
+ await scanController.remove(scan.id, force);
+ }
+
await this.runnerRepository.delete(runner);
return new ResponseRunner(responseRunner);
}
diff --git a/src/controllers/RunnerOrganisationController.ts b/src/controllers/RunnerOrganisationController.ts
index e10d415..15c12ff 100644
--- a/src/controllers/RunnerOrganisationController.ts
+++ b/src/controllers/RunnerOrganisationController.ts
@@ -2,8 +2,8 @@ import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { RunnerOrganisationHasRunnersError, RunnerOrganisationHasTeamsError, RunnerOrganisationIdsNotMatchingError, RunnerOrganisationNotFoundError } from '../errors/RunnerOrganisationErrors';
-import { CreateRunnerOrganisation } from '../models/actions/CreateRunnerOrganisation';
-import { UpdateRunnerOrganisation } from '../models/actions/UpdateRunnerOrganisation';
+import { CreateRunnerOrganisation } from '../models/actions/create/CreateRunnerOrganisation';
+import { UpdateRunnerOrganisation } from '../models/actions/update/UpdateRunnerOrganisation';
import { RunnerOrganisation } from '../models/entities/RunnerOrganisation';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseRunnerOrganisation } from '../models/responses/ResponseRunnerOrganisation';
@@ -55,7 +55,7 @@ export class RunnerOrganisationController {
async post(@Body({ validate: true }) createRunnerOrganisation: CreateRunnerOrganisation) {
let runnerOrganisation;
try {
- runnerOrganisation = await createRunnerOrganisation.toRunnerOrganisation();
+ runnerOrganisation = await createRunnerOrganisation.toEntity();
} catch (error) {
throw error;
}
@@ -82,7 +82,7 @@ export class RunnerOrganisationController {
throw new RunnerOrganisationIdsNotMatchingError();
}
- await this.runnerOrganisationRepository.save(await updateOrganisation.updateRunnerOrganisation(oldRunnerOrganisation));
+ await this.runnerOrganisationRepository.save(await updateOrganisation.update(oldRunnerOrganisation));
return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(id, { relations: ['address', 'contact', 'teams'] }));
}
diff --git a/src/controllers/RunnerTeamController.ts b/src/controllers/RunnerTeamController.ts
index bc99d79..5b97f2b 100644
--- a/src/controllers/RunnerTeamController.ts
+++ b/src/controllers/RunnerTeamController.ts
@@ -2,8 +2,8 @@ import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { RunnerTeamHasRunnersError, RunnerTeamIdsNotMatchingError, RunnerTeamNotFoundError } from '../errors/RunnerTeamErrors';
-import { CreateRunnerTeam } from '../models/actions/CreateRunnerTeam';
-import { UpdateRunnerTeam } from '../models/actions/UpdateRunnerTeam';
+import { CreateRunnerTeam } from '../models/actions/create/CreateRunnerTeam';
+import { UpdateRunnerTeam } from '../models/actions/update/UpdateRunnerTeam';
import { RunnerTeam } from '../models/entities/RunnerTeam';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseRunnerTeam } from '../models/responses/ResponseRunnerTeam';
@@ -54,7 +54,7 @@ export class RunnerTeamController {
async post(@Body({ validate: true }) createRunnerTeam: CreateRunnerTeam) {
let runnerTeam;
try {
- runnerTeam = await createRunnerTeam.toRunnerTeam();
+ runnerTeam = await createRunnerTeam.toEntity();
} catch (error) {
throw error;
}
@@ -82,7 +82,7 @@ export class RunnerTeamController {
throw new RunnerTeamIdsNotMatchingError();
}
- await this.runnerTeamRepository.save(await runnerTeam.updateRunnerTeam(oldRunnerTeam));
+ await this.runnerTeamRepository.save(await runnerTeam.update(oldRunnerTeam));
return new ResponseRunnerTeam(await this.runnerTeamRepository.findOne({ id: runnerTeam.id }, { relations: ['parentGroup', 'contact'] }));
}
diff --git a/src/controllers/ScanController.ts b/src/controllers/ScanController.ts
index ed7df91..0de844c 100644
--- a/src/controllers/ScanController.ts
+++ b/src/controllers/ScanController.ts
@@ -3,11 +3,14 @@ import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { RunnerNotFoundError } from '../errors/RunnerErrors';
import { ScanIdsNotMatchingError, ScanNotFoundError } from '../errors/ScanErrors';
+import { ScanStationNotFoundError } from '../errors/ScanStationErrors';
import ScanAuth from '../middlewares/ScanAuth';
-import { CreateScan } from '../models/actions/CreateScan';
-import { CreateTrackScan } from '../models/actions/CreateTrackScan';
-import { UpdateScan } from '../models/actions/UpdateScan';
+import { CreateScan } from '../models/actions/create/CreateScan';
+import { CreateTrackScan } from '../models/actions/create/CreateTrackScan';
+import { UpdateScan } from '../models/actions/update/UpdateScan';
+import { UpdateTrackScan } from '../models/actions/update/UpdateTrackScan';
import { Scan } from '../models/entities/Scan';
+import { TrackScan } from '../models/entities/TrackScan';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseScan } from '../models/responses/ResponseScan';
import { ResponseTrackScan } from '../models/responses/ResponseTrackScan';
@@ -16,12 +19,14 @@ import { ResponseTrackScan } from '../models/responses/ResponseTrackScan';
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class ScanController {
private scanRepository: Repository;
+ private trackScanRepository: Repository;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.scanRepository = getConnectionManager().get().getRepository(Scan);
+ this.trackScanRepository = getConnectionManager().get().getRepository(TrackScan);
}
@Get()
@@ -31,7 +36,7 @@ export class ScanController {
@OpenAPI({ description: 'Lists all scans (normal or track) from all runners.
This includes the scan\'s runner\'s distance ran.' })
async getAll() {
let responseScans: ResponseScan[] = new Array();
- const scans = await this.scanRepository.find({ relations: ['runner', 'runner.scans', 'runner.scans.track'] });
+ const scans = await this.scanRepository.find({ relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] });
scans.forEach(scan => {
responseScans.push(scan.toResponse());
});
@@ -46,7 +51,7 @@ export class ScanController {
@OnUndefined(ScanNotFoundError)
@OpenAPI({ description: 'Lists all information about the scan whose id got provided. This includes the scan\'s runner\'s distance ran.' })
async getOne(@Param('id') id: number) {
- let scan = await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'runner.scans', 'runner.scans.track'] })
+ let scan = await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })
if (!scan) { throw new ScanNotFoundError(); }
return scan.toResponse();
}
@@ -55,20 +60,22 @@ export class ScanController {
@UseBefore(ScanAuth)
@ResponseSchema(ResponseScan)
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
- @OpenAPI({ description: 'Create a new scan.
Please remeber to provide the scan\'s runner\'s id and distance for normal scans.', security: [{ "ScanApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
+ @OpenAPI({ description: 'Create a new scan (not track scan - use /scans/trackscans instead).
Please rmemember to provide the scan\'s runner\'s id and distance.', security: [{ "ScanApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async post(@Body({ validate: true }) createScan: CreateScan) {
- let scan = await createScan.toScan();
+ let scan = await createScan.toEntity();
scan = await this.scanRepository.save(scan);
- return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner'] })).toResponse();
+ return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse();
}
@Post("/trackscans")
@UseBefore(ScanAuth)
- @ResponseSchema(ResponseScan)
+ @ResponseSchema(ResponseTrackScan)
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
- @OpenAPI({ description: 'Create a new track scan.
This is just a alias for posting /scans', security: [{ "ScanApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
+ @OpenAPI({ description: 'Create a new track scan (for "normal" scans use /scans instead).
Please remember that to provide the scan\'s card\'s station\'s id.', security: [{ "ScanApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async postTrackScans(@Body({ validate: true }) createScan: CreateTrackScan) {
- return this.post(createScan);
+ let scan = await createScan.toEntity();
+ scan = await this.trackScanRepository.save(scan);
+ return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse();
}
@Put('/:id')
@@ -77,7 +84,7 @@ export class ScanController {
@ResponseSchema(ScanNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@ResponseSchema(ScanIdsNotMatchingError, { statusCode: 406 })
- @OpenAPI({ description: "Update the scan whose id you provided.
Please remember that ids can't be changed and distances must be positive." })
+ @OpenAPI({ description: "Update the scan (not track scan use /scans/trackscans/:id instead) whose id you provided.
Please remember that ids can't be changed and distances must be positive." })
async put(@Param('id') id: number, @Body({ validate: true }) scan: UpdateScan) {
let oldScan = await this.scanRepository.findOne({ id: id });
@@ -89,8 +96,31 @@ export class ScanController {
throw new ScanIdsNotMatchingError();
}
- await this.scanRepository.save(await scan.updateScan(oldScan));
- return (await this.scanRepository.findOne({ id: id }, { relations: ['runner'] })).toResponse();
+ await this.scanRepository.save(await scan.update(oldScan));
+ return (await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse();
+ }
+
+ @Put('/trackscans/:id')
+ @Authorized("SCAN:UPDATE")
+ @ResponseSchema(ResponseTrackScan)
+ @ResponseSchema(ScanNotFoundError, { statusCode: 404 })
+ @ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
+ @ResponseSchema(ScanStationNotFoundError, { statusCode: 404 })
+ @ResponseSchema(ScanIdsNotMatchingError, { statusCode: 406 })
+ @OpenAPI({ description: 'Update the track scan (not "normal" scan use /scans/trackscans/:id instead) whose id you provided.
Please remember that only the validity, runner and track can be changed.' })
+ async putTrackScan(@Param('id') id: number, @Body({ validate: true }) scan: UpdateTrackScan) {
+ let oldScan = await this.trackScanRepository.findOne({ id: id });
+
+ if (!oldScan) {
+ throw new ScanNotFoundError();
+ }
+
+ if (oldScan.id != scan.id) {
+ throw new ScanIdsNotMatchingError();
+ }
+
+ await this.trackScanRepository.save(await scan.update(oldScan));
+ return (await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse();
}
@Delete('/:id')
@@ -102,7 +132,7 @@ export class ScanController {
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let scan = await this.scanRepository.findOne({ id: id });
if (!scan) { return null; }
- const responseScan = await this.scanRepository.findOne({ id: scan.id }, { relations: ["runner"] });
+ const responseScan = await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] });
await this.scanRepository.delete(scan);
return responseScan.toResponse();
diff --git a/src/controllers/ScanStationController.ts b/src/controllers/ScanStationController.ts
index 4e32971..80aa6e3 100644
--- a/src/controllers/ScanStationController.ts
+++ b/src/controllers/ScanStationController.ts
@@ -3,8 +3,8 @@ import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { ScanStationHasScansError, ScanStationIdsNotMatchingError, ScanStationNotFoundError } from '../errors/ScanStationErrors';
import { TrackNotFoundError } from '../errors/TrackErrors';
-import { CreateScanStation } from '../models/actions/CreateScanStation';
-import { UpdateScanStation } from '../models/actions/UpdateScanStation';
+import { CreateScanStation } from '../models/actions/create/CreateScanStation';
+import { UpdateScanStation } from '../models/actions/update/UpdateScanStation';
import { ScanStation } from '../models/entities/ScanStation';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseScanStation } from '../models/responses/ResponseScanStation';
@@ -77,7 +77,7 @@ export class ScanStationController {
throw new ScanStationIdsNotMatchingError();
}
- await this.stationRepository.save(await station.updateStation(oldStation));
+ await this.stationRepository.save(await station.update(oldStation));
return (await this.stationRepository.findOne({ id: id }, { relations: ['track'] })).toResponse();
}
@@ -98,7 +98,7 @@ export class ScanStationController {
}
const scanController = new ScanController;
for (let scan of stationScans) {
- scanController.remove(scan.id, force);
+ await scanController.remove(scan.id, force);
}
const responseStation = await this.stationRepository.findOne({ id: station.id }, { relations: ["track"] });
diff --git a/src/controllers/StatsClientController.ts b/src/controllers/StatsClientController.ts
index 1aa46a2..4716909 100644
--- a/src/controllers/StatsClientController.ts
+++ b/src/controllers/StatsClientController.ts
@@ -1,9 +1,9 @@
-import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post } from 'routing-controllers';
+import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { StatsClientNotFoundError } from '../errors/StatsClientErrors';
import { TrackNotFoundError } from "../errors/TrackErrors";
-import { CreateStatsClient } from '../models/actions/CreateStatsClient';
+import { CreateStatsClient } from '../models/actions/create/CreateStatsClient';
import { StatsClient } from '../models/entities/StatsClient';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseStatsClient } from '../models/responses/ResponseStatsClient';
@@ -53,7 +53,7 @@ export class StatsClientController {
@Body({ validate: true })
client: CreateStatsClient
) {
- let newClient = await this.clientRepository.save(await client.toStatsClient());
+ let newClient = await this.clientRepository.save(await client.toEntity());
let responseClient = new ResponseStatsClient(newClient);
responseClient.key = newClient.cleartextkey;
return responseClient;
@@ -65,7 +65,7 @@ export class StatsClientController {
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@OnUndefined(204)
@OpenAPI({ description: "Delete the stats client whose id you provided.
If no client with this id exists it will just return 204(no content)." })
- async remove(@Param("id") id: number) {
+ async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let client = await this.clientRepository.findOne({ id: id });
if (!client) { return null; }
diff --git a/src/controllers/TrackController.ts b/src/controllers/TrackController.ts
index 094756c..6ea2907 100644
--- a/src/controllers/TrackController.ts
+++ b/src/controllers/TrackController.ts
@@ -2,8 +2,8 @@ import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { TrackHasScanStationsError, TrackIdsNotMatchingError, TrackLapTimeCantBeNegativeError, TrackNotFoundError } from "../errors/TrackErrors";
-import { CreateTrack } from '../models/actions/CreateTrack';
-import { UpdateTrack } from '../models/actions/UpdateTrack';
+import { CreateTrack } from '../models/actions/create/CreateTrack';
+import { UpdateTrack } from '../models/actions/update/UpdateTrack';
import { Track } from '../models/entities/Track';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseTrack } from '../models/responses/ResponseTrack';
@@ -55,7 +55,7 @@ export class TrackController {
@Body({ validate: true })
track: CreateTrack
) {
- return new ResponseTrack(await this.trackRepository.save(track.toTrack()));
+ return new ResponseTrack(await this.trackRepository.save(await track.toEntity()));
}
@Put('/:id')
@@ -75,7 +75,7 @@ export class TrackController {
if (oldTrack.id != updateTrack.id) {
throw new TrackIdsNotMatchingError();
}
- await this.trackRepository.save(await updateTrack.updateTrack(oldTrack));
+ await this.trackRepository.save(await updateTrack.update(oldTrack));
return new ResponseTrack(await this.trackRepository.findOne({ id: id }));
}
@@ -94,9 +94,9 @@ export class TrackController {
if (trackStations.length != 0 && !force) {
throw new TrackHasScanStationsError();
}
- const scanController = new ScanStationController;
+ const stationController = new ScanStationController;
for (let station of trackStations) {
- scanController.remove(station.id, force);
+ await stationController.remove(station.id, force);
}
await this.trackRepository.delete(track);
diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts
index c528572..57083db 100644
--- a/src/controllers/UserController.ts
+++ b/src/controllers/UserController.ts
@@ -3,8 +3,8 @@ import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { UserIdsNotMatchingError, UserNotFoundError } from '../errors/UserErrors';
import { UserGroupNotFoundError } from '../errors/UserGroupErrors';
-import { CreateUser } from '../models/actions/CreateUser';
-import { UpdateUser } from '../models/actions/UpdateUser';
+import { CreateUser } from '../models/actions/create/CreateUser';
+import { UpdateUser } from '../models/actions/update/UpdateUser';
import { User } from '../models/entities/User';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseUser } from '../models/responses/ResponseUser';
@@ -56,7 +56,7 @@ export class UserController {
async post(@Body({ validate: true }) createUser: CreateUser) {
let user;
try {
- user = await createUser.toUser();
+ user = await createUser.toEntity();
} catch (error) {
throw error;
}
@@ -81,7 +81,7 @@ export class UserController {
if (oldUser.id != updateUser.id) {
throw new UserIdsNotMatchingError();
}
- await this.userRepository.save(await updateUser.updateUser(oldUser));
+ await this.userRepository.save(await updateUser.update(oldUser));
return new ResponseUser(await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] }));
}
diff --git a/src/controllers/UserGroupController.ts b/src/controllers/UserGroupController.ts
index 4b484fc..18a901d 100644
--- a/src/controllers/UserGroupController.ts
+++ b/src/controllers/UserGroupController.ts
@@ -3,7 +3,8 @@ import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { EntityFromBody } from 'typeorm-routing-controllers-extensions';
import { UserGroupIdsNotMatchingError, UserGroupNotFoundError } from '../errors/UserGroupErrors';
-import { CreateUserGroup } from '../models/actions/CreateUserGroup';
+import { CreateUserGroup } from '../models/actions/create/CreateUserGroup';
+import { UpdateUserGroup } from '../models/actions/update/UpdateUserGroup';
import { UserGroup } from '../models/entities/UserGroup';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseUserGroup } from '../models/responses/ResponseUserGroup';
@@ -48,7 +49,7 @@ export class UserGroupController {
async post(@Body({ validate: true }) createUserGroup: CreateUserGroup) {
let userGroup;
try {
- userGroup = await createUserGroup.toUserGroup();
+ userGroup = await createUserGroup.toEntity();
} catch (error) {
throw error;
}
@@ -62,19 +63,19 @@ export class UserGroupController {
@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
@ResponseSchema(UserGroupIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update the group whose id you provided.
To change the permissions granted to the group please use /api/permissions instead.
Please remember that ids can't be changed." })
- async put(@Param('id') id: number, @EntityFromBody() userGroup: UserGroup) {
- let oldUserGroup = await this.userGroupsRepository.findOne({ id: id }, { relations: ["permissions"] });
+ async put(@Param('id') id: number, @EntityFromBody() updateGroup: UpdateUserGroup) {
+ let oldGroup = await this.userGroupsRepository.findOne({ id: id });
- if (!oldUserGroup) {
- throw new UserGroupNotFoundError()
+ if (!oldGroup) {
+ throw new UserGroupNotFoundError();
}
- if (oldUserGroup.id != userGroup.id) {
+ if (oldGroup.id != updateGroup.id) {
throw new UserGroupIdsNotMatchingError();
}
+ await this.userGroupsRepository.save(await updateGroup.update(oldGroup));
- await this.userGroupsRepository.save(userGroup);
- return userGroup;
+ return (await this.userGroupsRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] })).toResponse();
}
@Delete('/:id')
@@ -88,9 +89,9 @@ export class UserGroupController {
if (!group) { return null; }
const responseGroup = await this.userGroupsRepository.findOne({ id: id }, { relations: ['permissions'] });
- const permissionControler = new PermissionController();
+ const permissionController = new PermissionController();
for (let permission of responseGroup.permissions) {
- await permissionControler.remove(permission.id, true);
+ await permissionController.remove(permission.id, true);
}
await this.userGroupsRepository.delete(group);
diff --git a/src/errors/AddressErrors.ts b/src/errors/AddressErrors.ts
index 18cf624..27f2e5f 100644
--- a/src/errors/AddressErrors.ts
+++ b/src/errors/AddressErrors.ts
@@ -9,11 +9,11 @@ export class AddressWrongTypeError extends NotAcceptableError {
name = "AddressWrongTypeError"
@IsString()
- message = "The address must be an existing adress's id. \n You provided a object of another type."
+ message = "The address must be an existing address's id. \n You provided a object of another type."
}
/**
- * Error to throw, when a non-existant address get's loaded.
+ * Error to throw, when a non-existent address get's loaded.
*/
export class AddressNotFoundError extends NotFoundError {
@IsString()
diff --git a/src/errors/AuthError.ts b/src/errors/AuthError.ts
index 848ce78..6e3cd9d 100644
--- a/src/errors/AuthError.ts
+++ b/src/errors/AuthError.ts
@@ -118,7 +118,7 @@ export class RefreshTokenCountInvalidError extends NotAcceptableError {
}
/**
- * Error to throw when someone tryes to reset a user's password more than once in 15 minutes.
+ * Error to throw when someone tries to reset a user's password more than once in 15 minutes.
*/
export class ResetAlreadyRequestedError extends NotAcceptableError {
@IsString()
diff --git a/src/errors/GroupContactErrors.ts b/src/errors/GroupContactErrors.ts
index 7dc19e8..92fd4a2 100644
--- a/src/errors/GroupContactErrors.ts
+++ b/src/errors/GroupContactErrors.ts
@@ -13,7 +13,7 @@ export class GroupContactWrongTypeError extends NotAcceptableError {
}
/**
- * Error to throw, when a non-existant groupContact get's loaded.
+ * Error to throw, when a non-existent groupContact get's loaded.
*/
export class GroupContactNotFoundError extends NotFoundError {
@IsString()
diff --git a/src/errors/PrincipalErrors.ts b/src/errors/PrincipalErrors.ts
index d519dc7..bd7677c 100644
--- a/src/errors/PrincipalErrors.ts
+++ b/src/errors/PrincipalErrors.ts
@@ -13,12 +13,12 @@ export class PrincipalNotFoundError extends NotFoundError {
}
/**
- * Error to throw, when a provided runnerOrganisation doesn't belong to the accepted types.
+ * Error to throw, when a provided runner organization doesn't belong to the accepted types.
*/
export class PrincipalWrongTypeError extends NotAcceptableError {
@IsString()
name = "PrincipalWrongTypeError"
@IsString()
- message = "The princial must have an existing principal's id. \n You provided a object of another type."
+ message = "The principal must have an existing principal's id. \n You provided a object of another type."
}
diff --git a/src/errors/RunnerCardErrors.ts b/src/errors/RunnerCardErrors.ts
new file mode 100644
index 0000000..7976dc9
--- /dev/null
+++ b/src/errors/RunnerCardErrors.ts
@@ -0,0 +1,48 @@
+import { IsString } from 'class-validator';
+import { NotAcceptableError, NotFoundError } from 'routing-controllers';
+
+/**
+ * Error to throw when a card couldn't be found.
+ */
+export class RunnerCardNotFoundError extends NotFoundError {
+ @IsString()
+ name = "RunnerCardNotFoundError"
+
+ @IsString()
+ message = "Card not found!"
+}
+
+/**
+ * Error to throw when two cards' ids don't match.
+ * Usually occurs when a user tries to change a card's id.
+ */
+export class RunnerCardIdsNotMatchingError extends NotAcceptableError {
+ @IsString()
+ name = "RunnerCardIdsNotMatchingError"
+
+ @IsString()
+ message = "The ids don't match! \n And if you wanted to change a cards's id: This isn't allowed"
+}
+
+/**
+ * Error to throw when a card still has scans associated.
+ */
+export class RunnerCardHasScansError extends NotAcceptableError {
+ @IsString()
+ name = "RunnerCardHasScansError"
+
+ @IsString()
+ message = "This card still has scans associated with it. \n If you want to delete this card with all it's scans add `?force` to your query. \n Otherwise please consider just disabling it."
+}
+
+/**
+ * Error to throw when a card's id is too big to generate a ean-13 barcode for it.
+ * This error should never reach a end user.
+ */
+export class RunnerCardIdOutOfRangeError extends Error {
+ @IsString()
+ name = "RunnerCardIdOutOfRangeError"
+
+ @IsString()
+ message = "The card's id is too big to fit into a ean-13 barcode. \n This has a very low probability of happening but means that you might want to switch your barcode format for something that can accept numbers over 9999999999."
+}
\ No newline at end of file
diff --git a/src/errors/RunnerErrors.ts b/src/errors/RunnerErrors.ts
index 6ef12c5..b60a70d 100644
--- a/src/errors/RunnerErrors.ts
+++ b/src/errors/RunnerErrors.ts
@@ -32,5 +32,5 @@ export class RunnerGroupNeededError extends NotAcceptableError {
name = "RunnerGroupNeededError"
@IsString()
- message = "Runner's need to be part of one group (team or organisiation)! \n You provided neither."
+ message = "Runner's need to be part of one group (team or organisation)! \n You provided neither."
}
\ No newline at end of file
diff --git a/src/errors/RunnerOrganisationErrors.ts b/src/errors/RunnerOrganisationErrors.ts
index b004acf..081fbf0 100644
--- a/src/errors/RunnerOrganisationErrors.ts
+++ b/src/errors/RunnerOrganisationErrors.ts
@@ -13,7 +13,7 @@ export class RunnerOrganisationNotFoundError extends NotFoundError {
}
/**
- * Error to throw when two runner organisations' ids don't match.
+ * Error to throw when two runner organisation's ids don't match.
* Usually occurs when a user tries to change a runner organisation's id.
*/
export class RunnerOrganisationIdsNotMatchingError extends NotAcceptableError {
diff --git a/src/errors/ScanStationErrors.ts b/src/errors/ScanStationErrors.ts
index c013cf5..3b01f5e 100644
--- a/src/errors/ScanStationErrors.ts
+++ b/src/errors/ScanStationErrors.ts
@@ -2,7 +2,7 @@ import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
- * Error to throw, when a non-existant scan station get's loaded.
+ * Error to throw, when a non-existent scan station get's loaded.
*/
export class ScanStationNotFoundError extends NotFoundError {
@IsString()
diff --git a/src/errors/StatsClientErrors.ts b/src/errors/StatsClientErrors.ts
index ab7f1ef..68a088d 100644
--- a/src/errors/StatsClientErrors.ts
+++ b/src/errors/StatsClientErrors.ts
@@ -2,7 +2,7 @@ import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
- * Error to throw, when a non-existant stats client get's loaded.
+ * Error to throw, when a non-existent stats client get's loaded.
*/
export class StatsClientNotFoundError extends NotFoundError {
@IsString()
diff --git a/src/errors/UserGroupErrors.ts b/src/errors/UserGroupErrors.ts
index 7edd6fd..07f4224 100644
--- a/src/errors/UserGroupErrors.ts
+++ b/src/errors/UserGroupErrors.ts
@@ -2,7 +2,7 @@ import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
- * Error to throw when no groupname is set.
+ * Error to throw when no group name is set.
*/
export class GroupNameNeededError extends NotFoundError {
@IsString()
@@ -13,7 +13,7 @@ export class GroupNameNeededError extends NotFoundError {
}
/**
- * Error to throw when a usergroup couldn't be found.
+ * Error to throw when a user group couldn't be found.
*/
export class UserGroupNotFoundError extends NotFoundError {
@IsString()
@@ -24,13 +24,13 @@ export class UserGroupNotFoundError extends NotFoundError {
}
/**
- * Error to throw when two usergroups' ids don't match.
- * Usually occurs when a user tries to change a usergroups's id.
+ * Error to throw when two user groups' ids don't match.
+ * Usually occurs when a user tries to change a user groups's id.
*/
export class UserGroupIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "UserGroupIdsNotMatchingError"
@IsString()
- message = "The ids don't match!! \n If you wanted to change a usergroup's id: This isn't allowed!"
+ message = "The ids don't match!! \n If you wanted to change a user group's id: This isn't allowed!"
}
\ No newline at end of file
diff --git a/src/loaders/openapi.ts b/src/loaders/openapi.ts
index 7d8acfe..0f3a49d 100644
--- a/src/loaders/openapi.ts
+++ b/src/loaders/openapi.ts
@@ -2,7 +2,7 @@ import { validationMetadatasToSchemas } from "class-validator-jsonschema";
import express, { Application } from "express";
import path from 'path';
import { getMetadataArgsStorage } from "routing-controllers";
-import { routingControllersToSpec } from "routing-controllers-openapi";
+import { generateSpec } from '../apispec';
/**
* Loader for everything openapi related - from creating the schema to serving it via a static route and swaggerUiExpress.
@@ -15,46 +15,7 @@ export default async (app: Application) => {
});
//Spec creation based on the previously created schemas
- const spec = routingControllersToSpec(
- storage,
- {
- routePrefix: "/api"
- },
- {
- components: {
- schemas,
- "securitySchemes": {
- "AuthToken": {
- "type": "http",
- "scheme": "bearer",
- "bearerFormat": "JWT",
- description: "A JWT based access token. Use /api/auth/login or /api/auth/refresh to get one."
- },
- "RefreshTokenCookie": {
- "type": "apiKey",
- "in": "cookie",
- "name": "lfk_backend__refresh_token",
- description: "A cookie containing a JWT based refreh token. Attention: Doesn't work in swagger-ui. Use /api/auth/login or /api/auth/refresh to get one."
- },
- "StatsApiToken": {
- "type": "http",
- "scheme": "bearer",
- description: "Api token that can be obtained by creating a new stats client (post to /api/statsclients). Only valid for obtaining stats."
- },
- "StationApiToken": {
- "type": "http",
- "scheme": "bearer",
- description: "Api token that can be obtained by creating a new scan station (post to /api/stations). Only valid for creating scans."
- }
- }
- },
- info: {
- description: "The the backend API for the LfK! runner system.",
- title: "LfK! Backend API",
- version: "0.0.8",
- },
- }
- );
+ const spec = generateSpec(storage, schemas);
app.get(["/api/docs/openapi.json", "/api/docs/swagger.json"], (req, res) => {
res.json(spec);
});
diff --git a/src/middlewares/RawBody.ts b/src/middlewares/RawBody.ts
index 57efefa..52f6664 100644
--- a/src/middlewares/RawBody.ts
+++ b/src/middlewares/RawBody.ts
@@ -1,8 +1,8 @@
import { Request, Response } from 'express';
/**
- * Custom express middleware that appends the raw body to the request obeject.
- * Mainly used for parsing csvs from boddies.
+ * Custom express middleware that appends the raw body to the request object.
+ * Mainly used for parsing csvs from bodies.
*/
const RawBodyMiddleware = (req: Request, res: Response, next: () => void) => {
diff --git a/src/middlewares/ScanAuth.ts b/src/middlewares/ScanAuth.ts
index 7b16420..2f39bbf 100644
--- a/src/middlewares/ScanAuth.ts
+++ b/src/middlewares/ScanAuth.ts
@@ -5,8 +5,9 @@ import { ScanStation } from '../models/entities/ScanStation';
import authchecker from './authchecker';
/**
- * This middleware handels the authentification of scan station api tokens.
- * The tokens have to be provided via Bearer auth header.
+ * This middleware handles the authentication of scan station api tokens.
+ * The tokens have to be provided via Bearer authorization header.
+ * You have to manually use this middleware via @UseBefore(ScanAuth) instead of using @Authorized().
* @param req Express request object.
* @param res Express response object.
* @param next Next function to call on success.
@@ -31,7 +32,7 @@ const ScanAuth = async (req: Request, res: Response, next: () => void) => {
}
finally {
if (prefix == "" || prefix == undefined || prefix == null) {
- res.status(401).send("Api token non-existant or invalid syntax.");
+ res.status(401).send("Api token non-existent or invalid syntax.");
return;
}
}
@@ -45,7 +46,7 @@ const ScanAuth = async (req: Request, res: Response, next: () => void) => {
}
finally {
if (user_authorized == false) {
- res.status(401).send("Api token non-existant or invalid syntax.");
+ res.status(401).send("Api token non-existent or invalid syntax.");
return;
}
else {
diff --git a/src/middlewares/StatsAuth.ts b/src/middlewares/StatsAuth.ts
index 990206b..a6928be 100644
--- a/src/middlewares/StatsAuth.ts
+++ b/src/middlewares/StatsAuth.ts
@@ -5,8 +5,9 @@ import { StatsClient } from '../models/entities/StatsClient';
import authchecker from './authchecker';
/**
- * This middleware handels the authentification of stats client api tokens.
- * The tokens have to be provided via Bearer auth header.
+ * This middleware handles the authentication of stats client api tokens.
+ * The tokens have to be provided via Bearer authorization header.
+ * You have to manually use this middleware via @UseBefore(StatsAuth) instead of using @Authorized().
* @param req Express request object.
* @param res Express response object.
* @param next Next function to call on success.
diff --git a/src/middlewares/authchecker.ts b/src/middlewares/authchecker.ts
index 61ca231..45294d9 100644
--- a/src/middlewares/authchecker.ts
+++ b/src/middlewares/authchecker.ts
@@ -8,7 +8,7 @@ import { JwtCreator, JwtUser } from '../jwtcreator';
import { User } from '../models/entities/User';
/**
- * Handels authorisation verification via jwt's for all api endpoints using the @Authorized decorator.
+ * Handles authentication via jwt's (Bearer authorization header) for all api endpoints using the @Authorized decorator.
* @param action Routing-Controllers action object that provides request and response objects among other stuff.
* @param permissions The permissions that the endpoint using @Authorized requires.
*/
@@ -43,7 +43,7 @@ const authchecker = async (action: Action, permissions: string[] | string) => {
}
/**
- * Handels soft-refreshing of access-tokens.
+ * Handles soft-refreshing of access-tokens.
* @param action Routing-Controllers action object that provides request and response objects among other stuff.
*/
const refresh = async (action: Action) => {
diff --git a/src/models/actions/ImportRunner.ts b/src/models/actions/ImportRunner.ts
index 3155b0e..b399d90 100644
--- a/src/models/actions/ImportRunner.ts
+++ b/src/models/actions/ImportRunner.ts
@@ -5,7 +5,7 @@ import { RunnerOrganisationNotFoundError } from '../../errors/RunnerOrganisation
import { RunnerGroup } from '../entities/RunnerGroup';
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { RunnerTeam } from '../entities/RunnerTeam';
-import { CreateRunner } from './CreateRunner';
+import { CreateRunner } from './create/CreateRunner';
/**
* Special class used to import runners from csv files - or json arrays created from csv to be exact.
diff --git a/src/models/actions/RefreshAuth.ts b/src/models/actions/RefreshAuth.ts
index bcc4fbb..20e8abf 100644
--- a/src/models/actions/RefreshAuth.ts
+++ b/src/models/actions/RefreshAuth.ts
@@ -5,7 +5,7 @@ import { config } from '../../config';
import { IllegalJWTError, JwtNotProvidedError, RefreshTokenCountInvalidError, UserDisabledError, UserNotFoundError } from '../../errors/AuthError';
import { JwtCreator } from "../../jwtcreator";
import { User } from '../entities/User';
-import { Auth } from '../responses/ResponseAuth';
+import { ResponseAuth } from '../responses/ResponseAuth';
/**
* This class is used to create refreshed auth credentials.
@@ -24,8 +24,8 @@ export class RefreshAuth {
/**
* Creates a new auth object based on this.
*/
- public async toAuth(): Promise {
- let newAuth: Auth = new Auth();
+ public async toAuth(): Promise {
+ let newAuth: ResponseAuth = new ResponseAuth();
if (!this.token || this.token === undefined) {
throw new JwtNotProvidedError()
}
diff --git a/src/models/actions/CreateAddress.ts b/src/models/actions/create/CreateAddress.ts
similarity index 91%
rename from src/models/actions/CreateAddress.ts
rename to src/models/actions/create/CreateAddress.ts
index 66ee9bd..c399bb8 100644
--- a/src/models/actions/CreateAddress.ts
+++ b/src/models/actions/create/CreateAddress.ts
@@ -1,6 +1,6 @@
import { IsNotEmpty, IsOptional, IsPostalCode, IsString } from 'class-validator';
-import { config } from '../../config';
-import { Address } from '../entities/Address';
+import { config } from '../../../config';
+import { Address } from '../../entities/Address';
/**
* This classed is used to create a new Address entity from a json body (post request).
@@ -56,7 +56,7 @@ export class CreateAddress {
/**
* Creates a new Address entity from this.
*/
- public toAddress(): Address {
+ public async toEntity(): Promise {
let newAddress: Address = new Address();
newAddress.address1 = this.address1;
diff --git a/src/models/actions/CreateAuth.ts b/src/models/actions/create/CreateAuth.ts
similarity index 86%
rename from src/models/actions/CreateAuth.ts
rename to src/models/actions/create/CreateAuth.ts
index 6b22d7d..d9b96b7 100644
--- a/src/models/actions/CreateAuth.ts
+++ b/src/models/actions/create/CreateAuth.ts
@@ -1,11 +1,11 @@
import * as argon2 from "argon2";
import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
-import { InvalidCredentialsError, PasswordNeededError, UserDisabledError, UserNotFoundError } from '../../errors/AuthError';
-import { UsernameOrEmailNeededError } from '../../errors/UserErrors';
-import { JwtCreator } from '../../jwtcreator';
-import { User } from '../entities/User';
-import { Auth } from '../responses/ResponseAuth';
+import { InvalidCredentialsError, PasswordNeededError, UserDisabledError, UserNotFoundError } from '../../../errors/AuthError';
+import { UsernameOrEmailNeededError } from '../../../errors/UserErrors';
+import { JwtCreator } from '../../../jwtcreator';
+import { User } from '../../entities/User';
+import { ResponseAuth } from '../../responses/ResponseAuth';
/**
* This class is used to create auth credentials based on user credentials provided in a json body (post request).
@@ -42,8 +42,8 @@ export class CreateAuth {
/**
* Creates a new auth object based on this.
*/
- public async toAuth(): Promise {
- let newAuth: Auth = new Auth();
+ public async toAuth(): Promise {
+ let newAuth: ResponseAuth = new ResponseAuth();
if (this.email === undefined && this.username === undefined) {
throw new UsernameOrEmailNeededError();
diff --git a/src/models/actions/CreateDonor.ts b/src/models/actions/create/CreateDonor.ts
similarity index 85%
rename from src/models/actions/CreateDonor.ts
rename to src/models/actions/create/CreateDonor.ts
index 25a631c..791461a 100644
--- a/src/models/actions/CreateDonor.ts
+++ b/src/models/actions/create/CreateDonor.ts
@@ -1,6 +1,6 @@
import { IsBoolean, IsOptional } from 'class-validator';
-import { DonorReceiptAddressNeededError } from '../../errors/DonorErrors';
-import { Donor } from '../entities/Donor';
+import { DonorReceiptAddressNeededError } from '../../../errors/DonorErrors';
+import { Donor } from '../../entities/Donor';
import { CreateParticipant } from './CreateParticipant';
/**
@@ -18,7 +18,7 @@ export class CreateDonor extends CreateParticipant {
/**
* Creates a new Donor entity from this.
*/
- public async toDonor(): Promise {
+ public async toEntity(): Promise {
let newDonor: Donor = new Donor();
newDonor.firstname = this.firstname;
diff --git a/src/models/actions/CreateGroupContact.ts b/src/models/actions/create/CreateGroupContact.ts
similarity index 87%
rename from src/models/actions/CreateGroupContact.ts
rename to src/models/actions/create/CreateGroupContact.ts
index 915f897..c6e10a8 100644
--- a/src/models/actions/CreateGroupContact.ts
+++ b/src/models/actions/create/CreateGroupContact.ts
@@ -1,85 +1,85 @@
-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';
-
-/**
- * This classed is used to create a new Group entity from a json body (post request).
- */
-export class CreateGroupContact {
- /**
- * The new contact's first name.
- */
- @IsNotEmpty()
- @IsString()
- firstname: string;
-
- /**
- * The new contact's middle name.
- */
- @IsOptional()
- @IsString()
- middlename?: string;
-
- /**
- * The new contact's last name.
- */
- @IsNotEmpty()
- @IsString()
- lastname: string;
-
- /**
- * The new contact's address.
- * Must be the address's id.
- */
- @IsInt()
- @IsOptional()
- address?: number;
-
- /**
- * The contact's phone number.
- * This will be validated against the configured country phone numer syntax (default: international).
- */
- @IsOptional()
- @IsPhoneNumber(config.phone_validation_countrycode)
- phone?: string;
-
- /**
- * The contact's email address.
- */
- @IsOptional()
- @IsEmail()
- email?: string;
-
- /**
- * Gets the new contact's address by it's id.
- */
- public async getAddress(): Promise {
- if (this.address === undefined || this.address === null) {
- 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 new Address entity from this.
- */
- public async toGroupContact(): Promise {
- 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;
- }
+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';
+
+/**
+ * This classed is used to create a new Group entity from a json body (post request).
+ */
+export class CreateGroupContact {
+ /**
+ * The new contact's first name.
+ */
+ @IsNotEmpty()
+ @IsString()
+ firstname: string;
+
+ /**
+ * The new contact's middle name.
+ */
+ @IsOptional()
+ @IsString()
+ middlename?: string;
+
+ /**
+ * The new contact's last name.
+ */
+ @IsNotEmpty()
+ @IsString()
+ lastname: string;
+
+ /**
+ * The new contact's address.
+ * Must be the address's id.
+ */
+ @IsInt()
+ @IsOptional()
+ address?: number;
+
+ /**
+ * The contact's phone number.
+ * This will be validated against the configured country phone numer syntax (default: international).
+ */
+ @IsOptional()
+ @IsPhoneNumber(config.phone_validation_countrycode)
+ phone?: string;
+
+ /**
+ * The contact's email address.
+ */
+ @IsOptional()
+ @IsEmail()
+ email?: string;
+
+ /**
+ * Gets the new contact's address by it's id.
+ */
+ public async getAddress(): Promise {
+ if (this.address === undefined || this.address === null) {
+ 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 new Address entity from this.
+ */
+ public async toEntity(): Promise {
+ 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;
+ }
}
\ No newline at end of file
diff --git a/src/models/actions/CreateParticipant.ts b/src/models/actions/create/CreateParticipant.ts
similarity index 90%
rename from src/models/actions/CreateParticipant.ts
rename to src/models/actions/create/CreateParticipant.ts
index 165bddb..17e7c56 100644
--- a/src/models/actions/CreateParticipant.ts
+++ b/src/models/actions/create/CreateParticipant.ts
@@ -1,72 +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';
-
-/**
- * This classed is used to create a new Participant entity from a json body (post request).
- */
-export abstract class CreateParticipant {
- /**
- * The new participant's first name.
- */
- @IsString()
- @IsNotEmpty()
- firstname: string;
-
- /**
- * The new participant's middle name.
- */
- @IsString()
- @IsOptional()
- middlename?: string;
-
- /**
- * The new participant's last name.
- */
- @IsString()
- @IsNotEmpty()
- lastname: string;
-
- /**
- * The new participant's phone number.
- * This will be validated against the configured country phone numer syntax (default: international).
- */
- @IsString()
- @IsOptional()
- @IsPhoneNumber(config.phone_validation_countrycode)
- phone?: string;
-
- /**
- * The new participant's e-mail address.
- */
- @IsString()
- @IsOptional()
- @IsEmail()
- email?: string;
-
- /**
- * The new participant's address.
- * Must be of type number (address id).
- */
- @IsInt()
- @IsOptional()
- address?: number;
-
- /**
- * Gets the new participant's address by it's address.
- */
- public async getAddress(): Promise {
- if (this.address === undefined || this.address === null) {
- 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;
- }
+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';
+
+/**
+ * This classed is used to create a new Participant entity from a json body (post request).
+ */
+export abstract class CreateParticipant {
+ /**
+ * The new participant's first name.
+ */
+ @IsString()
+ @IsNotEmpty()
+ firstname: string;
+
+ /**
+ * The new participant's middle name.
+ */
+ @IsString()
+ @IsOptional()
+ middlename?: string;
+
+ /**
+ * The new participant's last name.
+ */
+ @IsString()
+ @IsNotEmpty()
+ lastname: string;
+
+ /**
+ * The new participant's phone number.
+ * This will be validated against the configured country phone numer syntax (default: international).
+ */
+ @IsString()
+ @IsOptional()
+ @IsPhoneNumber(config.phone_validation_countrycode)
+ phone?: string;
+
+ /**
+ * The new participant's e-mail address.
+ */
+ @IsString()
+ @IsOptional()
+ @IsEmail()
+ email?: string;
+
+ /**
+ * The new participant's address.
+ * Must be of type number (address id).
+ */
+ @IsInt()
+ @IsOptional()
+ address?: number;
+
+ /**
+ * Gets the new participant's address by it's address.
+ */
+ public async getAddress(): Promise {
+ if (this.address === undefined || this.address === null) {
+ 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;
+ }
}
\ No newline at end of file
diff --git a/src/models/actions/CreatePermission.ts b/src/models/actions/create/CreatePermission.ts
similarity index 77%
rename from src/models/actions/CreatePermission.ts
rename to src/models/actions/create/CreatePermission.ts
index 5ee9582..e59a186 100644
--- a/src/models/actions/CreatePermission.ts
+++ b/src/models/actions/create/CreatePermission.ts
@@ -4,11 +4,11 @@ import {
IsNotEmpty
} from "class-validator";
import { getConnectionManager } from 'typeorm';
-import { PrincipalNotFoundError } from '../../errors/PrincipalErrors';
-import { Permission } from '../entities/Permission';
-import { Principal } from '../entities/Principal';
-import { PermissionAction } from '../enums/PermissionAction';
-import { PermissionTarget } from '../enums/PermissionTargets';
+import { PrincipalNotFoundError } from '../../../errors/PrincipalErrors';
+import { Permission } from '../../entities/Permission';
+import { Principal } from '../../entities/Principal';
+import { PermissionAction } from '../../enums/PermissionAction';
+import { PermissionTarget } from '../../enums/PermissionTargets';
/**
* This classed is used to create a new Permission entity from a json body (post request).
@@ -39,7 +39,7 @@ export class CreatePermission {
/**
* Creates a new Permission entity from this.
*/
- public async toPermission(): Promise {
+ public async toEntity(): Promise {
let newPermission: Permission = new Permission();
newPermission.principal = await this.getPrincipal();
diff --git a/src/models/actions/CreateResetToken.ts b/src/models/actions/create/CreateResetToken.ts
similarity index 88%
rename from src/models/actions/CreateResetToken.ts
rename to src/models/actions/create/CreateResetToken.ts
index dcf22f1..81f430a 100644
--- a/src/models/actions/CreateResetToken.ts
+++ b/src/models/actions/create/CreateResetToken.ts
@@ -1,9 +1,9 @@
import { IsEmail, IsOptional, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
-import { ResetAlreadyRequestedError, UserDisabledError, UserNotFoundError } from '../../errors/AuthError';
-import { UsernameOrEmailNeededError } from '../../errors/UserErrors';
-import { JwtCreator } from '../../jwtcreator';
-import { User } from '../entities/User';
+import { ResetAlreadyRequestedError, UserDisabledError, UserNotFoundError } from '../../../errors/AuthError';
+import { UsernameOrEmailNeededError } from '../../../errors/UserErrors';
+import { JwtCreator } from '../../../jwtcreator';
+import { User } from '../../entities/User';
/**
* This calss is used to create password reset tokens for users.
diff --git a/src/models/actions/CreateRunner.ts b/src/models/actions/create/CreateRunner.ts
similarity index 77%
rename from src/models/actions/CreateRunner.ts
rename to src/models/actions/create/CreateRunner.ts
index ab2cc1f..2286fdf 100644
--- a/src/models/actions/CreateRunner.ts
+++ b/src/models/actions/create/CreateRunner.ts
@@ -1,10 +1,10 @@
import { IsInt } from 'class-validator';
import { getConnectionManager } from 'typeorm';
-import { RunnerGroupNotFoundError } from '../../errors/RunnerGroupErrors';
-import { RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
-import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
-import { Runner } from '../entities/Runner';
-import { RunnerGroup } from '../entities/RunnerGroup';
+import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
+import { RunnerOrganisationWrongTypeError } from '../../../errors/RunnerOrganisationErrors';
+import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors';
+import { Runner } from '../../entities/Runner';
+import { RunnerGroup } from '../../entities/RunnerGroup';
import { CreateParticipant } from './CreateParticipant';
/**
@@ -21,7 +21,7 @@ export class CreateRunner extends CreateParticipant {
/**
* Creates a new Runner entity from this.
*/
- public async toRunner(): Promise {
+ public async toEntity(): Promise {
let newRunner: Runner = new Runner();
newRunner.firstname = this.firstname;
diff --git a/src/models/actions/create/CreateRunnerCard.ts b/src/models/actions/create/CreateRunnerCard.ts
new file mode 100644
index 0000000..b661e6f
--- /dev/null
+++ b/src/models/actions/create/CreateRunnerCard.ts
@@ -0,0 +1,45 @@
+import { IsBoolean, IsInt, IsOptional } from 'class-validator';
+import { getConnection } from 'typeorm';
+import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
+import { Runner } from '../../entities/Runner';
+import { RunnerCard } from '../../entities/RunnerCard';
+
+/**
+ * This classed is used to create a new RunnerCard entity from a json body (post request).
+ */
+export class CreateRunnerCard {
+ /**
+ * The card's associated runner.
+ */
+ @IsInt()
+ @IsOptional()
+ runner?: number;
+
+ /**
+ * Is the new card enabled (for fraud reasons)?
+ * Default: true
+ */
+ @IsBoolean()
+ enabled: boolean = true;
+
+ /**
+ * Creates a new RunnerCard entity from this.
+ */
+ public async toEntity(): Promise {
+ let newCard: RunnerCard = new RunnerCard();
+
+ newCard.enabled = this.enabled;
+ newCard.runner = await this.getRunner();
+
+ return newCard;
+ }
+
+ public async getRunner(): Promise {
+ if (!this.runner) { return null; }
+ const runner = await getConnection().getRepository(Runner).findOne({ id: this.runner });
+ if (!runner) {
+ throw new RunnerNotFoundError();
+ }
+ return runner;
+ }
+}
\ No newline at end of file
diff --git a/src/models/actions/CreateRunnerGroup.ts b/src/models/actions/create/CreateRunnerGroup.ts
similarity index 91%
rename from src/models/actions/CreateRunnerGroup.ts
rename to src/models/actions/create/CreateRunnerGroup.ts
index 9f4c803..0b62024 100644
--- a/src/models/actions/CreateRunnerGroup.ts
+++ b/src/models/actions/create/CreateRunnerGroup.ts
@@ -1,7 +1,7 @@
import { IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
-import { GroupContactNotFoundError, GroupContactWrongTypeError } from '../../errors/GroupContactErrors';
-import { GroupContact } from '../entities/GroupContact';
+import { GroupContactNotFoundError, GroupContactWrongTypeError } from '../../../errors/GroupContactErrors';
+import { GroupContact } from '../../entities/GroupContact';
/**
* This classed is used to create a new RunnerGroup entity from a json body (post request).
diff --git a/src/models/actions/CreateRunnerOrganisation.ts b/src/models/actions/create/CreateRunnerOrganisation.ts
similarity index 87%
rename from src/models/actions/CreateRunnerOrganisation.ts
rename to src/models/actions/create/CreateRunnerOrganisation.ts
index a0733de..04edf93 100644
--- a/src/models/actions/CreateRunnerOrganisation.ts
+++ b/src/models/actions/create/CreateRunnerOrganisation.ts
@@ -1,8 +1,8 @@
import { IsInt, IsOptional } from 'class-validator';
import { getConnectionManager } from 'typeorm';
-import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors';
-import { Address } from '../entities/Address';
-import { RunnerOrganisation } from '../entities/RunnerOrganisation';
+import { AddressNotFoundError, AddressWrongTypeError } from '../../../errors/AddressErrors';
+import { Address } from '../../entities/Address';
+import { RunnerOrganisation } from '../../entities/RunnerOrganisation';
import { CreateRunnerGroup } from './CreateRunnerGroup';
/**
@@ -36,7 +36,7 @@ export class CreateRunnerOrganisation extends CreateRunnerGroup {
/**
* Creates a new RunnerOrganisation entity from this.
*/
- public async toRunnerOrganisation(): Promise {
+ public async toEntity(): Promise {
let newRunnerOrganisation: RunnerOrganisation = new RunnerOrganisation();
newRunnerOrganisation.name = this.name;
diff --git a/src/models/actions/CreateRunnerTeam.ts b/src/models/actions/create/CreateRunnerTeam.ts
similarity index 81%
rename from src/models/actions/CreateRunnerTeam.ts
rename to src/models/actions/create/CreateRunnerTeam.ts
index 30a27b3..fc5c310 100644
--- a/src/models/actions/CreateRunnerTeam.ts
+++ b/src/models/actions/create/CreateRunnerTeam.ts
@@ -1,9 +1,9 @@
import { IsInt, IsNotEmpty } from 'class-validator';
import { getConnectionManager } from 'typeorm';
-import { RunnerOrganisationNotFoundError, RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
-import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
-import { RunnerOrganisation } from '../entities/RunnerOrganisation';
-import { RunnerTeam } from '../entities/RunnerTeam';
+import { RunnerOrganisationNotFoundError, RunnerOrganisationWrongTypeError } from '../../../errors/RunnerOrganisationErrors';
+import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors';
+import { RunnerOrganisation } from '../../entities/RunnerOrganisation';
+import { RunnerTeam } from '../../entities/RunnerTeam';
import { CreateRunnerGroup } from './CreateRunnerGroup';
/**
@@ -37,7 +37,7 @@ export class CreateRunnerTeam extends CreateRunnerGroup {
/**
* Creates a new RunnerTeam entity from this.
*/
- public async toRunnerTeam(): Promise {
+ public async toEntity(): Promise {
let newRunnerTeam: RunnerTeam = new RunnerTeam();
newRunnerTeam.name = this.name;
diff --git a/src/models/actions/CreateScan.ts b/src/models/actions/create/CreateScan.ts
similarity index 87%
rename from src/models/actions/CreateScan.ts
rename to src/models/actions/create/CreateScan.ts
index e0d0efc..496b8a0 100644
--- a/src/models/actions/CreateScan.ts
+++ b/src/models/actions/create/CreateScan.ts
@@ -1,8 +1,8 @@
import { IsBoolean, IsInt, IsOptional, IsPositive } from 'class-validator';
import { getConnection } from 'typeorm';
-import { RunnerNotFoundError } from '../../errors/RunnerErrors';
-import { Runner } from '../entities/Runner';
-import { Scan } from '../entities/Scan';
+import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
+import { Runner } from '../../entities/Runner';
+import { Scan } from '../../entities/Scan';
/**
* This class is used to create a new Scan entity from a json body (post request).
@@ -36,7 +36,7 @@ export abstract class CreateScan {
/**
* Creates a new Scan entity from this.
*/
- public async toScan(): Promise {
+ public async toEntity(): Promise {
let newScan = new Scan();
newScan.distance = this.distance;
diff --git a/src/models/actions/CreateScanStation.ts b/src/models/actions/create/CreateScanStation.ts
similarity index 91%
rename from src/models/actions/CreateScanStation.ts
rename to src/models/actions/create/CreateScanStation.ts
index 5d93b7c..0804b9f 100644
--- a/src/models/actions/CreateScanStation.ts
+++ b/src/models/actions/create/CreateScanStation.ts
@@ -3,9 +3,9 @@ import { IsBoolean, IsInt, IsOptional, IsPositive, IsString } from 'class-valida
import crypto from 'crypto';
import { getConnection } from 'typeorm';
import * as uuid from 'uuid';
-import { TrackNotFoundError } from '../../errors/TrackErrors';
-import { ScanStation } from '../entities/ScanStation';
-import { Track } from '../entities/Track';
+import { TrackNotFoundError } from '../../../errors/TrackErrors';
+import { ScanStation } from '../../entities/ScanStation';
+import { Track } from '../../entities/Track';
/**
* This class is used to create a new StatsClient entity from a json body (post request).
diff --git a/src/models/actions/CreateStatsClient.ts b/src/models/actions/create/CreateStatsClient.ts
similarity index 89%
rename from src/models/actions/CreateStatsClient.ts
rename to src/models/actions/create/CreateStatsClient.ts
index 40172e5..0f666fc 100644
--- a/src/models/actions/CreateStatsClient.ts
+++ b/src/models/actions/create/CreateStatsClient.ts
@@ -2,7 +2,7 @@ import * as argon2 from "argon2";
import { IsOptional, IsString } from 'class-validator';
import crypto from 'crypto';
import * as uuid from 'uuid';
-import { StatsClient } from '../entities/StatsClient';
+import { StatsClient } from '../../entities/StatsClient';
/**
* This classed is used to create a new StatsClient entity from a json body (post request).
@@ -18,7 +18,7 @@ export class CreateStatsClient {
/**
* Converts this to a StatsClient entity.
*/
- public async toStatsClient(): Promise {
+ public async toEntity(): Promise {
let newClient: StatsClient = new StatsClient();
newClient.description = this.description;
diff --git a/src/models/actions/CreateTrack.ts b/src/models/actions/create/CreateTrack.ts
similarity index 86%
rename from src/models/actions/CreateTrack.ts
rename to src/models/actions/create/CreateTrack.ts
index fdeae71..8c0db34 100644
--- a/src/models/actions/CreateTrack.ts
+++ b/src/models/actions/create/CreateTrack.ts
@@ -1,6 +1,6 @@
import { IsInt, IsNotEmpty, IsOptional, IsPositive, IsString } from 'class-validator';
-import { TrackLapTimeCantBeNegativeError } from '../../errors/TrackErrors';
-import { Track } from '../entities/Track';
+import { TrackLapTimeCantBeNegativeError } from '../../../errors/TrackErrors';
+import { Track } from '../../entities/Track';
/**
* This classed is used to create a new Track entity from a json body (post request).
@@ -31,7 +31,7 @@ export class CreateTrack {
/**
* Creates a new Track entity from this.
*/
- public toTrack(): Track {
+ public toEntity(): Track {
let newTrack: Track = new Track();
newTrack.name = this.name;
diff --git a/src/models/actions/CreateTrackScan.ts b/src/models/actions/create/CreateTrackScan.ts
similarity index 56%
rename from src/models/actions/CreateTrackScan.ts
rename to src/models/actions/create/CreateTrackScan.ts
index 2303352..db63cf2 100644
--- a/src/models/actions/CreateTrackScan.ts
+++ b/src/models/actions/create/CreateTrackScan.ts
@@ -1,41 +1,36 @@
-import { IsNotEmpty } from 'class-validator';
+import { IsInt, IsPositive } from 'class-validator';
import { getConnection } from 'typeorm';
-import { RunnerNotFoundError } from '../../errors/RunnerErrors';
-import { RunnerCard } from '../entities/RunnerCard';
-import { ScanStation } from '../entities/ScanStation';
-import { TrackScan } from '../entities/TrackScan';
-import { CreateScan } from './CreateScan';
+import { RunnerCardNotFoundError } from '../../../errors/RunnerCardErrors';
+import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
+import { ScanStationNotFoundError } from '../../../errors/ScanStationErrors';
+import { RunnerCard } from '../../entities/RunnerCard';
+import { ScanStation } from '../../entities/ScanStation';
+import { TrackScan } from '../../entities/TrackScan';
/**
* This classed is used to create a new Scan entity from a json body (post request).
*/
-export class CreateTrackScan extends CreateScan {
-
- /**
- * The scan's associated track.
- * This is used to determine the scan's distance.
- */
- @IsNotEmpty()
- track: number;
-
+export class CreateTrackScan {
/**
* The runnerCard associated with the scan.
* This get's saved for documentation and management purposes.
*/
- @IsNotEmpty()
+ @IsInt()
+ @IsPositive()
card: number;
/**
* The scanning station that created the scan.
* Mainly used for logging and traceing back scans (or errors)
*/
- @IsNotEmpty()
+ @IsInt()
+ @IsPositive()
station: number;
/**
* Creates a new Track entity from this.
*/
- public async toScan(): Promise {
+ public async toEntity(): Promise {
let newScan: TrackScan = new TrackScan();
newScan.station = await this.getStation();
@@ -48,7 +43,7 @@ export class CreateTrackScan extends CreateScan {
throw new RunnerNotFoundError();
}
- newScan.timestamp = new Date(Date.now()).toString();
+ newScan.timestamp = Math.round(new Date().getTime() / 1000);
newScan.valid = await this.validateScan(newScan);
return newScan;
@@ -57,25 +52,25 @@ export class CreateTrackScan extends CreateScan {
public async getCard(): Promise {
const track = await getConnection().getRepository(RunnerCard).findOne({ id: this.card }, { relations: ["runner"] });
if (!track) {
- throw new Error();
+ throw new RunnerCardNotFoundError();
}
return track;
}
public async getStation(): Promise {
- const track = await getConnection().getRepository(ScanStation).findOne({ id: this.card }, { relations: ["track"] });
- if (!track) {
- throw new Error();
+ const station = await getConnection().getRepository(ScanStation).findOne({ id: this.station }, { relations: ["track"] });
+ if (!station) {
+ throw new ScanStationNotFoundError();
}
- return track;
+ return station;
}
public async validateScan(scan: TrackScan): Promise {
- const scans = await getConnection().getRepository(TrackScan).find({ where: { runner: scan.runner }, relations: ["track"] });
+ const scans = await getConnection().getRepository(TrackScan).find({ where: { runner: scan.runner, valid: true }, relations: ["track"] });
if (scans.length == 0) { return true; }
- const newestScan = scans[0];
- if ((new Date(scan.timestamp).getTime() - new Date(newestScan.timestamp).getTime()) > scan.track.minimumLapTime) {
+ const newestScan = scans[scans.length - 1];
+ if ((scan.timestamp - newestScan.timestamp) > scan.track.minimumLapTime) {
return true;
}
diff --git a/src/models/actions/CreateUser.ts b/src/models/actions/create/CreateUser.ts
similarity index 87%
rename from src/models/actions/CreateUser.ts
rename to src/models/actions/create/CreateUser.ts
index ad0f905..a5f20c2 100644
--- a/src/models/actions/CreateUser.ts
+++ b/src/models/actions/create/CreateUser.ts
@@ -1,132 +1,132 @@
-import * as argon2 from "argon2";
-import { IsBoolean, IsEmail, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator';
-import { getConnectionManager } from 'typeorm';
-import * as uuid from 'uuid';
-import { config } from '../../config';
-import { UsernameOrEmailNeededError } from '../../errors/UserErrors';
-import { UserGroupNotFoundError } from '../../errors/UserGroupErrors';
-import { User } from '../entities/User';
-import { UserGroup } from '../entities/UserGroup';
-
-/**
- * This classed is used to create a new User entity from a json body (post request).
- */
-export class CreateUser {
- /**
- * The new user's first name.
- */
- @IsString()
- firstname: string;
-
- /**
- * The new user's middle name.
- */
- @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.
- * This will be validated against the configured country phone numer syntax (default: international).
- */
- @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;
-
- /**
- * Will the new user be enabled from the start?
- * Default: true
- */
- @IsBoolean()
- @IsOptional()
- enabled?: boolean = true;
-
- /**
- * The new user's groups' id(s).
- * You can provide either one groupId or an array of groupIDs.
- */
- @IsOptional()
- groups?: number[] | number
-
- /**
- * The user's profile pic (or rather a url pointing to it).
- */
- @IsString()
- @IsUrl()
- @IsOptional()
- profilePic?: string;
-
- /**
- * Converts this to a User entity.
- */
- public async toUser(): Promise {
- let newUser: User = new User();
-
- if (this.email === undefined && this.username === undefined) {
- throw new UsernameOrEmailNeededError();
- }
-
- 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);
- newUser.groups = await this.getGroups();
- newUser.enabled = this.enabled;
-
- if (!this.profilePic) { newUser.profilePic = `https://dev.lauf-fuer-kaya.de/lfk-logo.png`; }
- else { newUser.profilePic = this.profilePic; }
-
- return newUser;
- }
-
- /**
- * Get's all groups for this user by their id's;
- */
- public async getGroups() {
- if (!this.groups) { return null; }
- let groups = new Array();
- if (!Array.isArray(this.groups)) {
- this.groups = [this.groups]
- }
- for (let group of this.groups) {
- let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group });
- if (!found) { throw new UserGroupNotFoundError(); }
- groups.push(found);
- }
- return groups;
- }
+import * as argon2 from "argon2";
+import { IsBoolean, IsEmail, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator';
+import { getConnectionManager } from 'typeorm';
+import * as uuid from 'uuid';
+import { config } from '../../../config';
+import { UsernameOrEmailNeededError } from '../../../errors/UserErrors';
+import { UserGroupNotFoundError } from '../../../errors/UserGroupErrors';
+import { User } from '../../entities/User';
+import { UserGroup } from '../../entities/UserGroup';
+
+/**
+ * This classed is used to create a new User entity from a json body (post request).
+ */
+export class CreateUser {
+ /**
+ * The new user's first name.
+ */
+ @IsString()
+ firstname: string;
+
+ /**
+ * The new user's middle name.
+ */
+ @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.
+ * This will be validated against the configured country phone numer syntax (default: international).
+ */
+ @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;
+
+ /**
+ * Will the new user be enabled from the start?
+ * Default: true
+ */
+ @IsBoolean()
+ @IsOptional()
+ enabled?: boolean = true;
+
+ /**
+ * The new user's groups' id(s).
+ * You can provide either one groupId or an array of groupIDs.
+ */
+ @IsOptional()
+ groups?: number[] | number
+
+ /**
+ * The user's profile pic (or rather a url pointing to it).
+ */
+ @IsString()
+ @IsUrl()
+ @IsOptional()
+ profilePic?: string;
+
+ /**
+ * Converts this to a User entity.
+ */
+ public async toEntity(): Promise {
+ let newUser: User = new User();
+
+ if (this.email === undefined && this.username === undefined) {
+ throw new UsernameOrEmailNeededError();
+ }
+
+ 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);
+ newUser.groups = await this.getGroups();
+ newUser.enabled = this.enabled;
+
+ if (!this.profilePic) { newUser.profilePic = `https://dev.lauf-fuer-kaya.de/lfk-logo.png`; }
+ else { newUser.profilePic = this.profilePic; }
+
+ return newUser;
+ }
+
+ /**
+ * Get's all groups for this user by their id's;
+ */
+ public async getGroups() {
+ if (!this.groups) { return null; }
+ let groups = new Array();
+ if (!Array.isArray(this.groups)) {
+ this.groups = [this.groups]
+ }
+ for (let group of this.groups) {
+ let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group });
+ if (!found) { throw new UserGroupNotFoundError(); }
+ groups.push(found);
+ }
+ return groups;
+ }
}
\ No newline at end of file
diff --git a/src/models/actions/CreateUserGroup.ts b/src/models/actions/create/CreateUserGroup.ts
similarity index 86%
rename from src/models/actions/CreateUserGroup.ts
rename to src/models/actions/create/CreateUserGroup.ts
index 50ad15d..aa5a862 100644
--- a/src/models/actions/CreateUserGroup.ts
+++ b/src/models/actions/create/CreateUserGroup.ts
@@ -1,5 +1,5 @@
import { IsOptional, IsString } from 'class-validator';
-import { UserGroup } from '../entities/UserGroup';
+import { UserGroup } from '../../entities/UserGroup';
/**
* This classed is used to create a new UserGroup entity from a json body (post request).
@@ -22,7 +22,7 @@ export class CreateUserGroup {
/**
* Creates a new UserGroup entity from this.
*/
- public async toUserGroup(): Promise {
+ public async toEntity(): Promise {
let newUserGroup: UserGroup = new UserGroup();
newUserGroup.name = this.name;
diff --git a/src/models/actions/UpdateDonor.ts b/src/models/actions/update/UpdateDonor.ts
similarity index 81%
rename from src/models/actions/UpdateDonor.ts
rename to src/models/actions/update/UpdateDonor.ts
index b7139c8..8c77ff1 100644
--- a/src/models/actions/UpdateDonor.ts
+++ b/src/models/actions/update/UpdateDonor.ts
@@ -1,7 +1,7 @@
import { IsBoolean, IsInt, IsOptional } from 'class-validator';
-import { DonorReceiptAddressNeededError } from '../../errors/DonorErrors';
-import { Donor } from '../entities/Donor';
-import { CreateParticipant } from './CreateParticipant';
+import { DonorReceiptAddressNeededError } from '../../../errors/DonorErrors';
+import { Donor } from '../../entities/Donor';
+import { CreateParticipant } from '../create/CreateParticipant';
/**
* This class is used to update a Donor entity (via put request).
@@ -26,7 +26,7 @@ export class UpdateDonor extends CreateParticipant {
/**
* Updates a provided Donor entity based on this.
*/
- public async updateDonor(donor: Donor): Promise {
+ public async update(donor: Donor): Promise {
donor.firstname = this.firstname;
donor.middlename = this.middlename;
donor.lastname = this.lastname;
diff --git a/src/models/actions/UpdatePermission.ts b/src/models/actions/update/UpdatePermission.ts
similarity index 80%
rename from src/models/actions/UpdatePermission.ts
rename to src/models/actions/update/UpdatePermission.ts
index 5241a1b..e591e20 100644
--- a/src/models/actions/UpdatePermission.ts
+++ b/src/models/actions/update/UpdatePermission.ts
@@ -1,11 +1,11 @@
import { IsInt, IsNotEmpty, IsObject } from 'class-validator';
import { getConnectionManager } from 'typeorm';
-import { PermissionNeedsPrincipalError } from '../../errors/PermissionErrors';
-import { PrincipalNotFoundError, PrincipalWrongTypeError } from '../../errors/PrincipalErrors';
-import { Permission } from '../entities/Permission';
-import { Principal } from '../entities/Principal';
-import { PermissionAction } from '../enums/PermissionAction';
-import { PermissionTarget } from '../enums/PermissionTargets';
+import { PermissionNeedsPrincipalError } from '../../../errors/PermissionErrors';
+import { PrincipalNotFoundError, PrincipalWrongTypeError } from '../../../errors/PrincipalErrors';
+import { Permission } from '../../entities/Permission';
+import { Principal } from '../../entities/Principal';
+import { PermissionAction } from '../../enums/PermissionAction';
+import { PermissionTarget } from '../../enums/PermissionTargets';
/**
* This class is used to update a Permission entity (via put request).
@@ -42,7 +42,7 @@ export class UpdatePermission {
/**
* Updates a provided Permission entity based on this.
*/
- public async updatePermission(permission: Permission): Promise {
+ public async update(permission: Permission): Promise {
permission.principal = await this.getPrincipal();
permission.target = this.target;
permission.action = this.action;
diff --git a/src/models/actions/UpdateRunner.ts b/src/models/actions/update/UpdateRunner.ts
similarity index 76%
rename from src/models/actions/UpdateRunner.ts
rename to src/models/actions/update/UpdateRunner.ts
index abc71e0..29a2423 100644
--- a/src/models/actions/UpdateRunner.ts
+++ b/src/models/actions/update/UpdateRunner.ts
@@ -1,11 +1,11 @@
import { IsInt, IsObject } from 'class-validator';
import { getConnectionManager } from 'typeorm';
-import { RunnerGroupNotFoundError } from '../../errors/RunnerGroupErrors';
-import { RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
-import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
-import { Runner } from '../entities/Runner';
-import { RunnerGroup } from '../entities/RunnerGroup';
-import { CreateParticipant } from './CreateParticipant';
+import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
+import { RunnerOrganisationWrongTypeError } from '../../../errors/RunnerOrganisationErrors';
+import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors';
+import { Runner } from '../../entities/Runner';
+import { RunnerGroup } from '../../entities/RunnerGroup';
+import { CreateParticipant } from '../create/CreateParticipant';
/**
* This class is used to update a Runner entity (via put request).
@@ -29,7 +29,7 @@ export class UpdateRunner extends CreateParticipant {
/**
* Updates a provided Runner entity based on this.
*/
- public async updateRunner(runner: Runner): Promise {
+ public async update(runner: Runner): Promise {
runner.firstname = this.firstname;
runner.middlename = this.middlename;
runner.lastname = this.lastname;
diff --git a/src/models/actions/update/UpdateRunnerCard.ts b/src/models/actions/update/UpdateRunnerCard.ts
new file mode 100644
index 0000000..51f70bf
--- /dev/null
+++ b/src/models/actions/update/UpdateRunnerCard.ts
@@ -0,0 +1,51 @@
+import { IsBoolean, IsInt, IsOptional, IsPositive } from 'class-validator';
+import { getConnection } from 'typeorm';
+import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
+import { Runner } from '../../entities/Runner';
+import { RunnerCard } from '../../entities/RunnerCard';
+
+/**
+ * This class is used to update a RunnerCard entity (via put request).
+ */
+export class UpdateRunnerCard {
+ /**
+ * The updated card's id.
+ * This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
+ */
+ @IsInt()
+ @IsPositive()
+ id?: number;
+
+ /**
+ * The updated card's associated runner.
+ */
+ @IsInt()
+ @IsOptional()
+ runner?: number;
+
+ /**
+ * Is the updated card enabled (for fraud reasons)?
+ * Default: true
+ */
+ @IsBoolean()
+ enabled: boolean = true;
+
+ /**
+ * Creates a new RunnerCard entity from this.
+ */
+ public async update(card: RunnerCard): Promise {
+ card.enabled = this.enabled;
+ card.runner = await this.getRunner();
+
+ return card;
+ }
+
+ public async getRunner(): Promise {
+ if (!this.runner) { return null; }
+ const runner = await getConnection().getRepository(Runner).findOne({ id: this.runner });
+ if (!runner) {
+ throw new RunnerNotFoundError();
+ }
+ return runner;
+ }
+}
\ No newline at end of file
diff --git a/src/models/actions/UpdateRunnerOrganisation.ts b/src/models/actions/update/UpdateRunnerOrganisation.ts
similarity index 79%
rename from src/models/actions/UpdateRunnerOrganisation.ts
rename to src/models/actions/update/UpdateRunnerOrganisation.ts
index 9a2cafa..7270923 100644
--- a/src/models/actions/UpdateRunnerOrganisation.ts
+++ b/src/models/actions/update/UpdateRunnerOrganisation.ts
@@ -1,9 +1,9 @@
import { IsInt, IsOptional } from 'class-validator';
import { getConnectionManager } from 'typeorm';
-import { AddressNotFoundError } from '../../errors/AddressErrors';
-import { Address } from '../entities/Address';
-import { RunnerOrganisation } from '../entities/RunnerOrganisation';
-import { CreateRunnerGroup } from './CreateRunnerGroup';
+import { AddressNotFoundError } from '../../../errors/AddressErrors';
+import { Address } from '../../entities/Address';
+import { RunnerOrganisation } from '../../entities/RunnerOrganisation';
+import { CreateRunnerGroup } from '../create/CreateRunnerGroup';
/**
* This class is used to update a RunnerOrganisation entity (via put request).
@@ -41,7 +41,7 @@ export class UpdateRunnerOrganisation extends CreateRunnerGroup {
/**
* Updates a provided RunnerOrganisation entity based on this.
*/
- public async updateRunnerOrganisation(organisation: RunnerOrganisation): Promise {
+ public async update(organisation: RunnerOrganisation): Promise {
organisation.name = this.name;
organisation.contact = await this.getContact();
diff --git a/src/models/actions/UpdateRunnerTeam.ts b/src/models/actions/update/UpdateRunnerTeam.ts
similarity index 80%
rename from src/models/actions/UpdateRunnerTeam.ts
rename to src/models/actions/update/UpdateRunnerTeam.ts
index 756d7df..d7e435d 100644
--- a/src/models/actions/UpdateRunnerTeam.ts
+++ b/src/models/actions/update/UpdateRunnerTeam.ts
@@ -1,10 +1,10 @@
import { IsInt, IsNotEmpty, IsObject } from 'class-validator';
import { getConnectionManager } from 'typeorm';
-import { RunnerOrganisationNotFoundError, RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
-import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
-import { RunnerOrganisation } from '../entities/RunnerOrganisation';
-import { RunnerTeam } from '../entities/RunnerTeam';
-import { CreateRunnerGroup } from './CreateRunnerGroup';
+import { RunnerOrganisationNotFoundError, RunnerOrganisationWrongTypeError } from '../../../errors/RunnerOrganisationErrors';
+import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors';
+import { RunnerOrganisation } from '../../entities/RunnerOrganisation';
+import { RunnerTeam } from '../../entities/RunnerTeam';
+import { CreateRunnerGroup } from '../create/CreateRunnerGroup';
/**
* This class is used to update a RunnerTeam entity (via put request).
@@ -45,7 +45,7 @@ export class UpdateRunnerTeam extends CreateRunnerGroup {
/**
* Updates a provided RunnerTeam entity based on this.
*/
- public async updateRunnerTeam(team: RunnerTeam): Promise {
+ public async update(team: RunnerTeam): Promise {
team.name = this.name;
team.parentGroup = await this.getParent();
diff --git a/src/models/actions/UpdateScan.ts b/src/models/actions/update/UpdateScan.ts
similarity index 87%
rename from src/models/actions/UpdateScan.ts
rename to src/models/actions/update/UpdateScan.ts
index 00b375e..a0d6633 100644
--- a/src/models/actions/UpdateScan.ts
+++ b/src/models/actions/update/UpdateScan.ts
@@ -1,8 +1,8 @@
import { IsBoolean, IsInt, IsOptional, IsPositive } from 'class-validator';
import { getConnection } from 'typeorm';
-import { RunnerNotFoundError } from '../../errors/RunnerErrors';
-import { Runner } from '../entities/Runner';
-import { Scan } from '../entities/Scan';
+import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
+import { Runner } from '../../entities/Runner';
+import { Scan } from '../../entities/Scan';
/**
* This class is used to update a Scan entity (via put request)
@@ -41,7 +41,7 @@ export abstract class UpdateScan {
* Update a Scan entity based on this.
* @param scan The scan that shall be updated.
*/
- public async updateScan(scan: Scan): Promise {
+ public async update(scan: Scan): Promise {
scan.distance = this.distance;
scan.valid = this.valid;
scan.runner = await this.getRunner();
diff --git a/src/models/actions/UpdateScanStation.ts b/src/models/actions/update/UpdateScanStation.ts
similarity index 86%
rename from src/models/actions/UpdateScanStation.ts
rename to src/models/actions/update/UpdateScanStation.ts
index a8ebc6a..b7557e3 100644
--- a/src/models/actions/UpdateScanStation.ts
+++ b/src/models/actions/update/UpdateScanStation.ts
@@ -1,5 +1,5 @@
import { IsBoolean, IsInt, IsOptional, IsString } from 'class-validator';
-import { ScanStation } from '../entities/ScanStation';
+import { ScanStation } from '../../entities/ScanStation';
/**
* This class is used to update a ScanStation entity (via put request)
@@ -30,7 +30,7 @@ export class UpdateScanStation {
* Update a ScanStation entity based on this.
* @param station The station that shall be updated.
*/
- public async updateStation(station: ScanStation): Promise {
+ public async update(station: ScanStation): Promise {
station.description = this.description;
station.enabled = this.enabled;
diff --git a/src/models/actions/UpdateTrack.ts b/src/models/actions/update/UpdateTrack.ts
similarity index 86%
rename from src/models/actions/UpdateTrack.ts
rename to src/models/actions/update/UpdateTrack.ts
index bc64d54..409a356 100644
--- a/src/models/actions/UpdateTrack.ts
+++ b/src/models/actions/update/UpdateTrack.ts
@@ -1,6 +1,6 @@
import { IsInt, IsNotEmpty, IsOptional, IsPositive, IsString } from 'class-validator';
-import { TrackLapTimeCantBeNegativeError } from '../../errors/TrackErrors';
-import { Track } from '../entities/Track';
+import { TrackLapTimeCantBeNegativeError } from '../../../errors/TrackErrors';
+import { Track } from '../../entities/Track';
/**
* This class is used to update a Track entity (via put request).
@@ -37,7 +37,7 @@ export class UpdateTrack {
* Update a Track entity based on this.
* @param track The track that shall be updated.
*/
- public updateTrack(track: Track): Track {
+ public async update(track: Track): Promise