Compare commits
135 Commits
931cae3c98
...
v0.0.10
| Author | SHA1 | Date | |
|---|---|---|---|
| 490fbd241d | |||
| f132131156 | |||
| c1e680a063 | |||
| c66b06c2c9 | |||
| 65e605cdc4 | |||
| d2fdb4efd9 | |||
| d0deb9d647 | |||
| 5495c90eaf | |||
| bf3ffae67c | |||
| aa0337ea33 | |||
| 4991d735bf | |||
| 398e61bddb | |||
| e6576f4a54 | |||
| c3b9e135b0 | |||
| 3bd4948c43 | |||
| f3cd1380be | |||
| a2c3dfbf85 | |||
| 3c37aafe1f | |||
| c591c182b3 | |||
| 9cc50078d1 | |||
| 7728759bcd | |||
| ce8fed350e | |||
| a005945e9e | |||
| cf86520fae | |||
| db6fdf6baf | |||
| 975ad50afc | |||
| 0c27df7754 | |||
| 102a860ba3 | |||
| 3a886714a0 | |||
| 09ab638239 | |||
| a4f88c78f4 | |||
| ccf2a3b617 | |||
| c8f941a779 | |||
| 5510cbb8e9 | |||
| a434173b54 | |||
| 7387f700fb | |||
| 4f01baaa23 | |||
| 09b37f0ff2 | |||
| 324d5709e3 | |||
| 3f23e4f1f1 | |||
| 9776a35f9f | |||
| 9b9ee70288 | |||
| 2628f69651 | |||
| b9c0a32862 | |||
| 82644a2ff4 | |||
| 3d2c93b5ac | |||
| c447114297 | |||
| 857de9ffcc | |||
| eea656bd7b | |||
| eec5284306 | |||
| 88a6a768c4 | |||
| edac1a224c | |||
| e67d1c5697 | |||
| 30502ec949 | |||
| a2c3913601 | |||
| f1c7713da2 | |||
| d6a41d5a82 | |||
| 72b5ca4153 | |||
| aeec2e1c32 | |||
| f9889bea3d | |||
| 2cad2ac2e9 | |||
| d948fe2631 | |||
| 2b5525323b | |||
| 58156e0d61 | |||
| a4b0dfe43e | |||
| ee2433a5ae | |||
| 2151b8502d | |||
| b57fde9b0a | |||
| 86706f9422 | |||
| 0687f268fc | |||
| bc426831db | |||
| 276e553e13 | |||
| e7ab302c61 | |||
| a5d70ce4b5 | |||
| d67be313e6 | |||
| 15d2d029dc | |||
| b6ea5e6549 | |||
| f378b0651a | |||
| 1a0573e0d0 | |||
| 9f103d8df1 | |||
| daa899a1ef | |||
| 59cb72a11d | |||
| 28c1b6d31d | |||
| dcb791c9a2 | |||
| 907259bf73 | |||
| 02f7ddbb37 | |||
| 63b1ca9b56 | |||
| 39857cf6e6 | |||
| 3090ae69f3 | |||
| 92186a86cc | |||
| 97e8470b0d | |||
| 6b0e3503a7 | |||
| 1e2de7656e | |||
| 56c6a7efb0 | |||
| 9c4e54fc6e | |||
| 2c47436259 | |||
| 9b5d16ae92 | |||
| deb13674b2 | |||
| 17c82ff409 | |||
| f9e314bf9f | |||
| e4c1930dd1 | |||
| b337ab424d | |||
| 82a0e194cb | |||
| 599296c4e3 | |||
| 2594a607dc | |||
| 335d4e24da | |||
| becc277123 | |||
| 52cdd41ec8 | |||
| 53548ba7a6 | |||
| 1dc438beb2 | |||
| c9ba69792f | |||
| ab67e5f4aa | |||
| 557608e318 | |||
| a83fedc9b8 | |||
| 61a17b198f | |||
| 3df1db4ad8 | |||
| e46cfa0d77 | |||
| 4126d31a5e | |||
| 9d9549cdd4 | |||
| eb40de6eb4 | |||
| 6efd09db73 | |||
| 3f09e3d387 | |||
| 05868e0e00 | |||
| 580a73f9a5 | |||
| ab7110d49f | |||
| 875781335c | |||
| 625340cf8a | |||
| 8d9dbc3957 | |||
| 07d813082b | |||
| a684f60252 | |||
| 5cfd2c9a52 | |||
| 6c7b31d76c | |||
| 2924ac2900 | |||
| a501625dd6 | |||
| cc64ce4498 |
34
.drone.yml
34
.drone.yml
@@ -11,7 +11,7 @@ steps:
|
||||
- git checkout $DRONE_SOURCE_BRANCH
|
||||
- mv .env.ci .env
|
||||
- name: run tests
|
||||
image: node:alpine
|
||||
image: node:14.15.1-alpine3.12
|
||||
commands:
|
||||
- yarn
|
||||
- yarn test:ci
|
||||
@@ -38,12 +38,13 @@ steps:
|
||||
- dev
|
||||
registry: registry.odit.services
|
||||
- name: run full license export
|
||||
image: node:alpine
|
||||
depends_on: [clone]
|
||||
depends_on: ["clone"]
|
||||
image: node:14.15.1-alpine3.12
|
||||
commands:
|
||||
- yarn
|
||||
- yarn licenses:full
|
||||
- yarn licenses:export
|
||||
- name: push new licenses file to repo
|
||||
depends_on: ["run full license export"]
|
||||
image: appleboy/drone-git-push
|
||||
settings:
|
||||
branch: dev
|
||||
@@ -117,27 +118,4 @@ steps:
|
||||
from_secret: BOT_DRONE_KEY
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: export:licenses
|
||||
|
||||
steps:
|
||||
- name: run full license export
|
||||
image: node:alpine
|
||||
commands:
|
||||
- yarn
|
||||
- yarn licenses:full
|
||||
- name: push new licenses file to repo
|
||||
depends_on: ["run full license export"]
|
||||
image: appleboy/drone-git-push
|
||||
settings:
|
||||
branch: feature/59-license_collection
|
||||
commit: true
|
||||
commit_message: new license file version [CI SKIP]
|
||||
author_email: bot@odit.services
|
||||
remote: git@git.odit.services:lfk/backend.git
|
||||
ssh_key:
|
||||
from_secret: GITLAB_SSHKEY
|
||||
- tag
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -133,4 +133,6 @@ build
|
||||
*.sqlite
|
||||
*.sqlite-jurnal
|
||||
/docs
|
||||
lib
|
||||
lib
|
||||
/oss-attribution
|
||||
*.tmp
|
||||
File diff suppressed because one or more lines are too long
1296
licenses.md
Normal file
1296
licenses.md
Normal file
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@odit/lfk-backend",
|
||||
"version": "0.0.6",
|
||||
"version": "0.0.10",
|
||||
"main": "src/app.ts",
|
||||
"repository": "https://git.odit.services/lfk/backend",
|
||||
"author": {
|
||||
@@ -40,7 +40,7 @@
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"routing-controllers": "^0.9.0-alpha.6",
|
||||
"routing-controllers-openapi": "^2.1.0",
|
||||
"sqlite3": "^5.0.0",
|
||||
"sqlite3": "5.0.0",
|
||||
"typeorm": "^0.2.29",
|
||||
"typeorm-routing-controllers-extensions": "^0.2.0",
|
||||
"typeorm-seeding": "^1.6.1",
|
||||
@@ -48,6 +48,7 @@
|
||||
"validator": "^13.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@odit/license-exporter": "^0.0.8",
|
||||
"@types/cors": "^2.8.8",
|
||||
"@types/csvtojson": "^1.1.5",
|
||||
"@types/express": "^4.17.9",
|
||||
@@ -59,7 +60,6 @@
|
||||
"cp-cli": "^2.0.0",
|
||||
"jest": "^26.6.3",
|
||||
"nodemon": "^2.0.6",
|
||||
"license-checker": "^25.0.1",
|
||||
"rimraf": "^2.7.1",
|
||||
"start-server-and-test": "^1.11.6",
|
||||
"ts-jest": "^26.4.4",
|
||||
@@ -75,9 +75,8 @@
|
||||
"test:watch": "jest --watchAll",
|
||||
"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": "node scripts/openapi_export.js",
|
||||
"licenses:export": "node scripts/license_exporter.js",
|
||||
"licenses:full": "node scripts/license_exporter.js --full"
|
||||
"openapi:export": "ts-node scripts/openapi_export.ts",
|
||||
"licenses:export": "license-exporter --md"
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"ignore": [
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
var checker = require('license-checker');
|
||||
var consola = require('consola');
|
||||
var fs = require('fs');
|
||||
|
||||
var args = process.argv.slice(2);
|
||||
|
||||
checker.init({
|
||||
start: './',
|
||||
relativeLicensePath: true,
|
||||
customFormat: {
|
||||
licenseText: "",
|
||||
licenseFile: "",
|
||||
description: "",
|
||||
version: "",
|
||||
}
|
||||
}, function (err, packages) {
|
||||
if (err) {
|
||||
consola.error("Couldn't load the licenses.")
|
||||
} else {
|
||||
let licenses = new Array();
|
||||
if (args.includes("--full")) {
|
||||
Object.keys(packages).forEach(function (key) {
|
||||
licenses.push({
|
||||
"name": packages[key].name,
|
||||
"licenses": packages[key].licenses,
|
||||
"repository": packages[key].repository || null,
|
||||
"publisher": packages[key].publisher || null,
|
||||
"email": packages[key].email || null,
|
||||
"version": packages[key].version || null,
|
||||
"description": packages[key].description || null,
|
||||
"copyright": packages[key].copyright || null,
|
||||
"text": packages[key].licenseText || null,
|
||||
"license_path": packages[key].licenseFile || null,
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
Object.keys(packages).forEach(function (key) {
|
||||
licenses.push({
|
||||
"name": packages[key].name,
|
||||
"licenses": packages[key].licenses,
|
||||
"repository": packages[key].repository || null,
|
||||
"publisher": packages[key].publisher || null,
|
||||
"email": packages[key].email || null,
|
||||
"version": packages[key].version || null,
|
||||
"description": packages[key].description || null,
|
||||
"copyright": packages[key].copyright || null,
|
||||
});
|
||||
});
|
||||
}
|
||||
try {
|
||||
fs.writeFileSync("./licenses.json", JSON.stringify(licenses), { encoding: "utf-8" });
|
||||
consola.success("Exported licenses to ./licenses.json");
|
||||
} catch (error) {
|
||||
consola.error("Couldn't export the licenses");
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -15,7 +15,7 @@ createExpressServer({
|
||||
development: config.development,
|
||||
cors: true,
|
||||
routePrefix: "/api",
|
||||
controllers: [`${__dirname}/controllers/*.${CONTROLLERS_FILE_EXTENSION}`],
|
||||
controllers: [`${__dirname}/../src/controllers/*.${CONTROLLERS_FILE_EXTENSION}`],
|
||||
});
|
||||
|
||||
const storage = getMetadataArgsStorage();
|
||||
@@ -48,14 +48,19 @@ const spec = routingControllersToSpec(
|
||||
"StatsApiToken": {
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
description: "Api token that can be obtained by creating a new stats client (post to /api/statsclients)."
|
||||
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.5",
|
||||
version: "0.0.8",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@@ -70,7 +70,6 @@ export class AuthController {
|
||||
if (refresh_token && refresh_token.length != 0 && refreshAuth.token == undefined) {
|
||||
refreshAuth.token = refresh_token;
|
||||
}
|
||||
console.log(req.headers)
|
||||
let auth;
|
||||
try {
|
||||
auth = await refreshAuth.toAuth();
|
||||
|
||||
105
src/controllers/DonorController.ts
Normal file
105
src/controllers/DonorController.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
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 { DonorIdsNotMatchingError, DonorNotFoundError } from '../errors/DonorErrors';
|
||||
import { CreateDonor } from '../models/actions/CreateDonor';
|
||||
import { UpdateDonor } from '../models/actions/UpdateDonor';
|
||||
import { Donor } from '../models/entities/Donor';
|
||||
import { ResponseDonor } from '../models/responses/ResponseDonor';
|
||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
||||
|
||||
@JsonController('/donors')
|
||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
||||
export class DonorController {
|
||||
private donorRepository: Repository<Donor>;
|
||||
|
||||
/**
|
||||
* Gets the repository of this controller's model/entity.
|
||||
*/
|
||||
constructor() {
|
||||
this.donorRepository = getConnectionManager().get().getRepository(Donor);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@Authorized("DONOR:GET")
|
||||
@ResponseSchema(ResponseDonor, { isArray: true })
|
||||
@OpenAPI({ description: 'Lists all runners from all teams/orgs. <br> This includes the runner\'s group and distance ran.' })
|
||||
async getAll() {
|
||||
let responseDonors: ResponseDonor[] = new Array<ResponseDonor>();
|
||||
const donors = await this.donorRepository.find();
|
||||
donors.forEach(donor => {
|
||||
responseDonors.push(new ResponseDonor(donor));
|
||||
});
|
||||
return responseDonors;
|
||||
}
|
||||
|
||||
@Get('/:id')
|
||||
@Authorized("DONOR:GET")
|
||||
@ResponseSchema(ResponseDonor)
|
||||
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
|
||||
@OnUndefined(DonorNotFoundError)
|
||||
@OpenAPI({ description: 'Lists all information about the runner whose id got provided.' })
|
||||
async getOne(@Param('id') id: number) {
|
||||
let donor = await this.donorRepository.findOne({ id: id })
|
||||
if (!donor) { throw new DonorNotFoundError(); }
|
||||
return new ResponseDonor(donor);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@Authorized("DONOR:CREATE")
|
||||
@ResponseSchema(ResponseDonor)
|
||||
@OpenAPI({ description: 'Create a new runner. <br> Please remeber to provide the runner\'s group\'s id.' })
|
||||
async post(@Body({ validate: true }) createRunner: CreateDonor) {
|
||||
let donor;
|
||||
try {
|
||||
donor = await createRunner.toDonor();
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
donor = await this.donorRepository.save(donor)
|
||||
return new ResponseDonor(await this.donorRepository.findOne(donor));
|
||||
}
|
||||
|
||||
@Put('/:id')
|
||||
@Authorized("DONOR:UPDATE")
|
||||
@ResponseSchema(ResponseDonor)
|
||||
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
|
||||
@ResponseSchema(DonorIdsNotMatchingError, { statusCode: 406 })
|
||||
@OpenAPI({ description: "Update the runner whose id you provided. <br> Please remember that ids can't be changed." })
|
||||
async put(@Param('id') id: number, @Body({ validate: true }) donor: UpdateDonor) {
|
||||
let oldDonor = await this.donorRepository.findOne({ id: id });
|
||||
|
||||
if (!oldDonor) {
|
||||
throw new DonorNotFoundError();
|
||||
}
|
||||
|
||||
if (oldDonor.id != donor.id) {
|
||||
throw new DonorIdsNotMatchingError();
|
||||
}
|
||||
|
||||
await this.donorRepository.save(await donor.updateDonor(oldDonor));
|
||||
return new ResponseDonor(await this.donorRepository.findOne({ id: id }));
|
||||
}
|
||||
|
||||
@Delete('/:id')
|
||||
@Authorized("DONOR:DELETE")
|
||||
@ResponseSchema(ResponseDonor)
|
||||
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
||||
@OnUndefined(204)
|
||||
@OpenAPI({ description: 'Delete the runner whose id you provided. <br> 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 donor = await this.donorRepository.findOne({ id: id });
|
||||
if (!donor) { return null; }
|
||||
const responseDonor = await this.donorRepository.findOne(donor);
|
||||
|
||||
if (!donor) {
|
||||
throw new DonorNotFoundError();
|
||||
}
|
||||
|
||||
//TODO: DELETE DONATIONS AND WARN FOR FORCE (https://git.odit.services/lfk/backend/issues/66)
|
||||
|
||||
await this.donorRepository.delete(donor);
|
||||
return new ResponseDonor(responseDonor);
|
||||
}
|
||||
}
|
||||
110
src/controllers/ScanController.ts
Normal file
110
src/controllers/ScanController.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam, UseBefore } from 'routing-controllers';
|
||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
||||
import { getConnectionManager, Repository } from 'typeorm';
|
||||
import { RunnerNotFoundError } from '../errors/RunnerErrors';
|
||||
import { ScanIdsNotMatchingError, ScanNotFoundError } from '../errors/ScanErrors';
|
||||
import ScanAuth from '../middlewares/ScanAuth';
|
||||
import { CreateScan } from '../models/actions/CreateScan';
|
||||
import { CreateTrackScan } from '../models/actions/CreateTrackScan';
|
||||
import { UpdateScan } from '../models/actions/UpdateScan';
|
||||
import { Scan } from '../models/entities/Scan';
|
||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
||||
import { ResponseScan } from '../models/responses/ResponseScan';
|
||||
import { ResponseTrackScan } from '../models/responses/ResponseTrackScan';
|
||||
|
||||
@JsonController('/scans')
|
||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
||||
export class ScanController {
|
||||
private scanRepository: Repository<Scan>;
|
||||
|
||||
/**
|
||||
* Gets the repository of this controller's model/entity.
|
||||
*/
|
||||
constructor() {
|
||||
this.scanRepository = getConnectionManager().get().getRepository(Scan);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@Authorized("SCAN:GET")
|
||||
@ResponseSchema(ResponseScan, { isArray: true })
|
||||
@ResponseSchema(ResponseTrackScan, { isArray: true })
|
||||
@OpenAPI({ description: 'Lists all scans (normal or track) from all runners. <br> This includes the scan\'s runner\'s distance ran.' })
|
||||
async getAll() {
|
||||
let responseScans: ResponseScan[] = new Array<ResponseScan>();
|
||||
const scans = await this.scanRepository.find({ relations: ['runner', 'runner.scans', 'runner.scans.track'] });
|
||||
scans.forEach(scan => {
|
||||
responseScans.push(scan.toResponse());
|
||||
});
|
||||
return responseScans;
|
||||
}
|
||||
|
||||
@Get('/:id')
|
||||
@Authorized("SCAN:GET")
|
||||
@ResponseSchema(ResponseScan)
|
||||
@ResponseSchema(ResponseTrackScan)
|
||||
@ResponseSchema(ScanNotFoundError, { statusCode: 404 })
|
||||
@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'] })
|
||||
if (!scan) { throw new ScanNotFoundError(); }
|
||||
return scan.toResponse();
|
||||
}
|
||||
|
||||
@Post()
|
||||
@UseBefore(ScanAuth)
|
||||
@ResponseSchema(ResponseScan)
|
||||
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
|
||||
@OpenAPI({ description: 'Create a new scan. <br> Please remeber to provide the scan\'s runner\'s id and distance for normal scans.', security: [{ "ScanApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
||||
async post(@Body({ validate: true }) createScan: CreateScan) {
|
||||
let scan = await createScan.toScan();
|
||||
scan = await this.scanRepository.save(scan);
|
||||
return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner'] })).toResponse();
|
||||
}
|
||||
|
||||
@Post("/trackscans")
|
||||
@UseBefore(ScanAuth)
|
||||
@ResponseSchema(ResponseScan)
|
||||
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
|
||||
@OpenAPI({ description: 'Create a new track scan. <br> This is just a alias for posting /scans', security: [{ "ScanApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
||||
async postTrackScans(@Body({ validate: true }) createScan: CreateTrackScan) {
|
||||
return this.post(createScan);
|
||||
}
|
||||
|
||||
@Put('/:id')
|
||||
@Authorized("SCAN:UPDATE")
|
||||
@ResponseSchema(ResponseScan)
|
||||
@ResponseSchema(ScanNotFoundError, { statusCode: 404 })
|
||||
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
|
||||
@ResponseSchema(ScanIdsNotMatchingError, { statusCode: 406 })
|
||||
@OpenAPI({ description: "Update the scan whose id you provided. <br> 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 });
|
||||
|
||||
if (!oldScan) {
|
||||
throw new ScanNotFoundError();
|
||||
}
|
||||
|
||||
if (oldScan.id != scan.id) {
|
||||
throw new ScanIdsNotMatchingError();
|
||||
}
|
||||
|
||||
await this.scanRepository.save(await scan.updateScan(oldScan));
|
||||
return (await this.scanRepository.findOne({ id: id }, { relations: ['runner'] })).toResponse();
|
||||
}
|
||||
|
||||
@Delete('/:id')
|
||||
@Authorized("SCAN:DELETE")
|
||||
@ResponseSchema(ResponseScan)
|
||||
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
||||
@OnUndefined(204)
|
||||
@OpenAPI({ description: 'Delete the scan whose id you provided. <br> If no scan with this id exists it will just return 204(no content).' })
|
||||
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"] });
|
||||
|
||||
await this.scanRepository.delete(scan);
|
||||
return responseScan.toResponse();
|
||||
}
|
||||
}
|
||||
108
src/controllers/ScanStationController.ts
Normal file
108
src/controllers/ScanStationController.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
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 { ScanStationHasScansError, ScanStationIdsNotMatchingError, ScanStationNotFoundError } from '../errors/ScanStationErrors';
|
||||
import { TrackNotFoundError } from '../errors/TrackErrors';
|
||||
import { CreateScanStation } from '../models/actions/CreateScanStation';
|
||||
import { UpdateScanStation } from '../models/actions/UpdateScanStation';
|
||||
import { ScanStation } from '../models/entities/ScanStation';
|
||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
||||
import { ResponseScanStation } from '../models/responses/ResponseScanStation';
|
||||
import { ScanController } from './ScanController';
|
||||
|
||||
@JsonController('/stations')
|
||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
||||
export class ScanStationController {
|
||||
private stationRepository: Repository<ScanStation>;
|
||||
|
||||
/**
|
||||
* Gets the repository of this controller's model/entity.
|
||||
*/
|
||||
constructor() {
|
||||
this.stationRepository = getConnectionManager().get().getRepository(ScanStation);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@Authorized("STATION:GET")
|
||||
@ResponseSchema(ResponseScanStation, { isArray: true })
|
||||
@OpenAPI({ description: 'Lists all stations. <br> This includes their associated tracks.' })
|
||||
async getAll() {
|
||||
let responseStations: ResponseScanStation[] = new Array<ResponseScanStation>();
|
||||
const stations = await this.stationRepository.find({ relations: ['track'] });
|
||||
stations.forEach(station => {
|
||||
responseStations.push(station.toResponse());
|
||||
});
|
||||
return responseStations;
|
||||
}
|
||||
|
||||
@Get('/:id')
|
||||
@Authorized("STATION:GET")
|
||||
@ResponseSchema(ResponseScanStation)
|
||||
@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 })
|
||||
@OnUndefined(ScanStationNotFoundError)
|
||||
@OpenAPI({ description: 'Lists all information about the station whose id got provided. <br> This includes it\'s associated track.' })
|
||||
async getOne(@Param('id') id: number) {
|
||||
let scan = await this.stationRepository.findOne({ id: id }, { relations: ['track'] })
|
||||
if (!scan) { throw new ScanStationNotFoundError(); }
|
||||
return scan.toResponse();
|
||||
}
|
||||
|
||||
@Post()
|
||||
@Authorized("STATION:CREATE")
|
||||
@ResponseSchema(ResponseScanStation)
|
||||
@ResponseSchema(TrackNotFoundError, { statusCode: 404 })
|
||||
@OpenAPI({ description: 'Create a new station. <br> Please remeber to provide the station\'s track\'s id. <br> Please also remember that the station key is only visibe on creation.' })
|
||||
async post(@Body({ validate: true }) createStation: CreateScanStation) {
|
||||
let newStation = await createStation.toEntity();
|
||||
const station = await this.stationRepository.save(newStation);
|
||||
let responseStation = (await this.stationRepository.findOne({ id: station.id }, { relations: ['track'] })).toResponse();
|
||||
responseStation.key = newStation.cleartextkey;
|
||||
return responseStation;
|
||||
}
|
||||
|
||||
@Put('/:id')
|
||||
@Authorized("STATION:UPDATE")
|
||||
@ResponseSchema(ResponseScanStation)
|
||||
@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 })
|
||||
@ResponseSchema(ScanStationIdsNotMatchingError, { statusCode: 406 })
|
||||
@OpenAPI({ description: "Update the station whose id you provided. <br> Please remember that only the description and enabled state can be changed." })
|
||||
async put(@Param('id') id: number, @Body({ validate: true }) station: UpdateScanStation) {
|
||||
let oldStation = await this.stationRepository.findOne({ id: id });
|
||||
|
||||
if (!oldStation) {
|
||||
throw new ScanStationNotFoundError();
|
||||
}
|
||||
|
||||
if (oldStation.id != station.id) {
|
||||
throw new ScanStationIdsNotMatchingError();
|
||||
}
|
||||
|
||||
await this.stationRepository.save(await station.updateStation(oldStation));
|
||||
return (await this.stationRepository.findOne({ id: id }, { relations: ['track'] })).toResponse();
|
||||
}
|
||||
|
||||
@Delete('/:id')
|
||||
@Authorized("STATION:DELETE")
|
||||
@ResponseSchema(ResponseScanStation)
|
||||
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
||||
@ResponseSchema(ScanStationHasScansError, { statusCode: 406 })
|
||||
@OnUndefined(204)
|
||||
@OpenAPI({ description: 'Delete the station whose id you provided. <br> If no station with this id exists it will just return 204(no content). <br> If the station still has scans associated you have to provide the force=true query param (warning: this deletes all scans associated with/created by this station - please disable it instead).' })
|
||||
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
|
||||
let station = await this.stationRepository.findOne({ id: id });
|
||||
if (!station) { return null; }
|
||||
|
||||
const stationScans = (await this.stationRepository.findOne({ id: station.id }, { relations: ["scans"] })).scans;
|
||||
if (stationScans.length != 0 && !force) {
|
||||
throw new ScanStationHasScansError();
|
||||
}
|
||||
const scanController = new ScanController;
|
||||
for (let scan of stationScans) {
|
||||
scanController.remove(scan.id, force);
|
||||
}
|
||||
|
||||
const responseStation = await this.stationRepository.findOne({ id: station.id }, { relations: ["track"] });
|
||||
await this.stationRepository.delete(station);
|
||||
return responseStation.toResponse();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers';
|
||||
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
|
||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
||||
import { getConnectionManager, Repository } from 'typeorm';
|
||||
import { EntityFromBody } from 'typeorm-routing-controllers-extensions';
|
||||
import { TrackIdsNotMatchingError, TrackNotFoundError } from "../errors/TrackErrors";
|
||||
import { TrackHasScanStationsError, TrackIdsNotMatchingError, TrackLapTimeCantBeNegativeError, TrackNotFoundError } from "../errors/TrackErrors";
|
||||
import { CreateTrack } from '../models/actions/CreateTrack';
|
||||
import { UpdateTrack } from '../models/actions/UpdateTrack';
|
||||
import { Track } from '../models/entities/Track';
|
||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
||||
import { ResponseTrack } from '../models/responses/ResponseTrack';
|
||||
import { ScanStationController } from './ScanStationController';
|
||||
|
||||
@JsonController('/tracks')
|
||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
||||
@@ -48,6 +49,7 @@ export class TrackController {
|
||||
@Post()
|
||||
@Authorized("TRACK:CREATE")
|
||||
@ResponseSchema(ResponseTrack)
|
||||
@ResponseSchema(TrackLapTimeCantBeNegativeError, { statusCode: 406 })
|
||||
@OpenAPI({ description: "Create a new track. <br> Please remember that the track\'s distance must be greater than 0." })
|
||||
async post(
|
||||
@Body({ validate: true })
|
||||
@@ -61,20 +63,21 @@ export class TrackController {
|
||||
@ResponseSchema(ResponseTrack)
|
||||
@ResponseSchema(TrackNotFoundError, { statusCode: 404 })
|
||||
@ResponseSchema(TrackIdsNotMatchingError, { statusCode: 406 })
|
||||
@ResponseSchema(TrackLapTimeCantBeNegativeError, { statusCode: 406 })
|
||||
@OpenAPI({ description: "Update the track whose id you provided. <br> Please remember that ids can't be changed." })
|
||||
async put(@Param('id') id: number, @EntityFromBody() track: Track) {
|
||||
async put(@Param('id') id: number, @Body({ validate: true }) updateTrack: UpdateTrack) {
|
||||
let oldTrack = await this.trackRepository.findOne({ id: id });
|
||||
|
||||
if (!oldTrack) {
|
||||
throw new TrackNotFoundError();
|
||||
}
|
||||
|
||||
if (oldTrack.id != track.id) {
|
||||
if (oldTrack.id != updateTrack.id) {
|
||||
throw new TrackIdsNotMatchingError();
|
||||
}
|
||||
await this.trackRepository.save(await updateTrack.updateTrack(oldTrack));
|
||||
|
||||
await this.trackRepository.save(track);
|
||||
return new ResponseTrack(track);
|
||||
return new ResponseTrack(await this.trackRepository.findOne({ id: id }));
|
||||
}
|
||||
|
||||
@Delete('/:id')
|
||||
@@ -83,10 +86,19 @@ export class TrackController {
|
||||
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
||||
@OnUndefined(204)
|
||||
@OpenAPI({ description: "Delete the track whose id you provided. <br> If no track 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 track = await this.trackRepository.findOne({ id: id });
|
||||
if (!track) { return null; }
|
||||
|
||||
const trackStations = (await this.trackRepository.findOne({ id: id }, { relations: ["stations"] })).stations;
|
||||
if (trackStations.length != 0 && !force) {
|
||||
throw new TrackHasScanStationsError();
|
||||
}
|
||||
const scanController = new ScanStationController;
|
||||
for (let station of trackStations) {
|
||||
scanController.remove(station.id, force);
|
||||
}
|
||||
|
||||
await this.trackRepository.delete(track);
|
||||
return new ResponseTrack(track);
|
||||
}
|
||||
|
||||
@@ -25,11 +25,11 @@ export class UserController {
|
||||
|
||||
@Get()
|
||||
@Authorized("USER:GET")
|
||||
@ResponseSchema(User, { isArray: true })
|
||||
@ResponseSchema(ResponseUser, { isArray: true })
|
||||
@OpenAPI({ description: 'Lists all users. <br> This includes their groups and permissions directly granted to them (if existing/associated).' })
|
||||
async getAll() {
|
||||
let responseUsers: ResponseUser[] = new Array<ResponseUser>();
|
||||
const users = await this.userRepository.find({ relations: ['permissions', 'groups'] });
|
||||
const users = await this.userRepository.find({ relations: ['permissions', 'groups', 'groups.permissions'] });
|
||||
users.forEach(user => {
|
||||
responseUsers.push(new ResponseUser(user));
|
||||
});
|
||||
@@ -38,19 +38,19 @@ export class UserController {
|
||||
|
||||
@Get('/:id')
|
||||
@Authorized("USER:GET")
|
||||
@ResponseSchema(User)
|
||||
@ResponseSchema(ResponseUser)
|
||||
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
|
||||
@OnUndefined(UserNotFoundError)
|
||||
@OpenAPI({ description: 'Lists all information about the user whose id got provided. <br> Please remember that only permissions granted directly to the user will show up here, not permissions inherited from groups.' })
|
||||
async getOne(@Param('id') id: number) {
|
||||
let user = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] })
|
||||
let user = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions'] })
|
||||
if (!user) { throw new UserNotFoundError(); }
|
||||
return new ResponseUser(user);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@Authorized("USER:CREATE")
|
||||
@ResponseSchema(User)
|
||||
@ResponseSchema(ResponseUser)
|
||||
@ResponseSchema(UserGroupNotFoundError)
|
||||
@OpenAPI({ description: 'Create a new user. <br> If you want to grant permissions to the user you have to create them seperately by posting to /api/permissions after creating the user.' })
|
||||
async post(@Body({ validate: true }) createUser: CreateUser) {
|
||||
@@ -67,7 +67,7 @@ export class UserController {
|
||||
|
||||
@Put('/:id')
|
||||
@Authorized("USER:UPDATE")
|
||||
@ResponseSchema(User)
|
||||
@ResponseSchema(ResponseUser)
|
||||
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
|
||||
@ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 })
|
||||
@OpenAPI({ description: "Update the user whose id you provided. <br> To change the permissions directly granted to the user please use /api/permissions instead. <br> Please remember that ids can't be changed." })
|
||||
@@ -88,7 +88,7 @@ export class UserController {
|
||||
|
||||
@Delete('/:id')
|
||||
@Authorized("USER:DELETE")
|
||||
@ResponseSchema(User)
|
||||
@ResponseSchema(ResponseUser)
|
||||
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
||||
@OnUndefined(204)
|
||||
@OpenAPI({ description: 'Delete the user whose id you provided. <br> If there are any permissions directly granted to the user they will get deleted as well. <br> If no user with this id exists it will just return 204(no content).' })
|
||||
|
||||
36
src/errors/DonorErrors.ts
Normal file
36
src/errors/DonorErrors.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { IsString } from 'class-validator';
|
||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
|
||||
|
||||
/**
|
||||
* Error to throw when a donor couldn't be found.
|
||||
*/
|
||||
export class DonorNotFoundError extends NotFoundError {
|
||||
@IsString()
|
||||
name = "DonorNotFoundError"
|
||||
|
||||
@IsString()
|
||||
message = "Donor not found!"
|
||||
}
|
||||
|
||||
/**
|
||||
* Error to throw when two donors' ids don't match.
|
||||
* Usually occurs when a user tries to change a donor's id.
|
||||
*/
|
||||
export class DonorIdsNotMatchingError extends NotAcceptableError {
|
||||
@IsString()
|
||||
name = "DonorIdsNotMatchingError"
|
||||
|
||||
@IsString()
|
||||
message = "The ids don't match! \n And if you wanted to change a donor's id: This isn't allowed!"
|
||||
}
|
||||
|
||||
/**
|
||||
* Error to throw when a donor needs a receipt, but no address is associated with them.
|
||||
*/
|
||||
export class DonorReceiptAddressNeededError extends NotAcceptableError {
|
||||
@IsString()
|
||||
name = "DonorReceiptAddressNeededError"
|
||||
|
||||
@IsString()
|
||||
message = "An address is needed to create a receipt for a donor. \n You didn't provide one."
|
||||
}
|
||||
25
src/errors/ScanErrors.ts
Normal file
25
src/errors/ScanErrors.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { IsString } from 'class-validator';
|
||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
|
||||
|
||||
/**
|
||||
* Error to throw when a Scan couldn't be found.
|
||||
*/
|
||||
export class ScanNotFoundError extends NotFoundError {
|
||||
@IsString()
|
||||
name = "ScanNotFoundError"
|
||||
|
||||
@IsString()
|
||||
message = "Scan not found!"
|
||||
}
|
||||
|
||||
/**
|
||||
* Error to throw when two Scans' ids don't match.
|
||||
* Usually occurs when a user tries to change a Scan's id.
|
||||
*/
|
||||
export class ScanIdsNotMatchingError extends NotAcceptableError {
|
||||
@IsString()
|
||||
name = "ScanIdsNotMatchingError"
|
||||
|
||||
@IsString()
|
||||
message = "The ids don't match! \n And if you wanted to change a Scan's id: This isn't allowed!"
|
||||
}
|
||||
36
src/errors/ScanStationErrors.ts
Normal file
36
src/errors/ScanStationErrors.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { IsString } from 'class-validator';
|
||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
|
||||
|
||||
/**
|
||||
* Error to throw, when a non-existant scan station get's loaded.
|
||||
*/
|
||||
export class ScanStationNotFoundError extends NotFoundError {
|
||||
@IsString()
|
||||
name = "ScanStationNotFoundError"
|
||||
|
||||
@IsString()
|
||||
message = "The scan station you provided couldn't be located in the system. \n Please check your request."
|
||||
}
|
||||
|
||||
/**
|
||||
* Error to throw when two scan stations' ids don't match.
|
||||
* Usually occurs when a user tries to change a scan station's id.
|
||||
*/
|
||||
export class ScanStationIdsNotMatchingError extends NotAcceptableError {
|
||||
@IsString()
|
||||
name = "ScanStationIdsNotMatchingError"
|
||||
|
||||
@IsString()
|
||||
message = "The ids don't match! \n And if you wanted to change a scan station's id: This isn't allowed!"
|
||||
}
|
||||
|
||||
/**
|
||||
* Error to throw when a station still has scans associated.
|
||||
*/
|
||||
export class ScanStationHasScansError extends NotAcceptableError {
|
||||
@IsString()
|
||||
name = "ScanStationHasScansError"
|
||||
|
||||
@IsString()
|
||||
message = "This station still has scans associated with it. \n If you want to delete this station with all it's scans add `?force` to your query."
|
||||
}
|
||||
@@ -22,4 +22,23 @@ export class TrackIdsNotMatchingError extends NotAcceptableError {
|
||||
|
||||
@IsString()
|
||||
message = "The ids don't match! \n And if you wanted to change a track's id: This isn't allowed"
|
||||
}
|
||||
|
||||
/**
|
||||
* Error to throw when a track's lap time is set to a negative value.
|
||||
*/
|
||||
export class TrackLapTimeCantBeNegativeError extends NotAcceptableError {
|
||||
@IsString()
|
||||
name = "TrackLapTimeCantBeNegativeError"
|
||||
|
||||
@IsString()
|
||||
message = "The minimum lap time you provided is negative - That isn't possible. \n If you wanted to disable it: Just set it to 0/null."
|
||||
}
|
||||
|
||||
export class TrackHasScanStationsError extends NotAcceptableError {
|
||||
@IsString()
|
||||
name = "TrackHasScanStationsError"
|
||||
|
||||
@IsString()
|
||||
message = "This track still has stations associated with it. \n If you want to delete this track with all it's stations and scans add `?force` to your query."
|
||||
}
|
||||
@@ -106,23 +106,6 @@ export class JwtUser {
|
||||
this.refreshTokenCount = user.refreshTokenCount;
|
||||
this.uuid = user.uuid;
|
||||
this.profilePic = user.profilePic;
|
||||
this.permissions = this.getPermissions(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handels getting the permissions granted to this user (direct or indirect).
|
||||
* @param user User which's permissions shall be gotten.
|
||||
*/
|
||||
public getPermissions(user: User): string[] {
|
||||
let returnPermissions: string[] = new Array<string>();
|
||||
for (let permission of user.permissions) {
|
||||
returnPermissions.push(permission.toString());
|
||||
}
|
||||
for (let group of user.groups) {
|
||||
for (let permission of group.permissions) {
|
||||
returnPermissions.push(permission.toString());
|
||||
}
|
||||
}
|
||||
return Array.from(new Set(returnPermissions));
|
||||
this.permissions = user.allPermissions;
|
||||
}
|
||||
}
|
||||
@@ -39,14 +39,19 @@ export default async (app: Application) => {
|
||||
"StatsApiToken": {
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
description: "Api token that can be obtained by creating a new stats client (post to /api/statsclients)."
|
||||
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.5",
|
||||
version: "0.0.8",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
68
src/middlewares/ScanAuth.ts
Normal file
68
src/middlewares/ScanAuth.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import * as argon2 from "argon2";
|
||||
import { Request, Response } from 'express';
|
||||
import { getConnectionManager } from 'typeorm';
|
||||
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.
|
||||
* @param req Express request object.
|
||||
* @param res Express response object.
|
||||
* @param next Next function to call on success.
|
||||
*/
|
||||
const ScanAuth = async (req: Request, res: Response, next: () => void) => {
|
||||
let provided_token: string = req.headers["authorization"];
|
||||
if (provided_token == "" || provided_token === undefined || provided_token === null) {
|
||||
res.status(401).send("No api token provided.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
provided_token = provided_token.replace("Bearer ", "");
|
||||
} catch (error) {
|
||||
res.status(401).send("No valid jwt or api token provided.");
|
||||
return;
|
||||
}
|
||||
|
||||
let prefix = "";
|
||||
try {
|
||||
prefix = provided_token.split(".")[0];
|
||||
}
|
||||
finally {
|
||||
if (prefix == "" || prefix == undefined || prefix == null) {
|
||||
res.status(401).send("Api token non-existant or invalid syntax.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const station = await getConnectionManager().get().getRepository(ScanStation).findOne({ prefix: prefix });
|
||||
if (!station) {
|
||||
let user_authorized = false;
|
||||
try {
|
||||
let action = { request: req, response: res, context: null, next: next }
|
||||
user_authorized = await authchecker(action, ["SCAN:CREATE"]);
|
||||
}
|
||||
finally {
|
||||
if (user_authorized == false) {
|
||||
res.status(401).send("Api token non-existant or invalid syntax.");
|
||||
return;
|
||||
}
|
||||
else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (station.enabled == false) {
|
||||
res.status(401).send("Station disabled.");
|
||||
}
|
||||
if (!(await argon2.verify(station.key, provided_token))) {
|
||||
res.status(401).send("Api token invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
}
|
||||
export default ScanAuth;
|
||||
38
src/models/actions/CreateDonor.ts
Normal file
38
src/models/actions/CreateDonor.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { IsBoolean, IsOptional } from 'class-validator';
|
||||
import { DonorReceiptAddressNeededError } from '../../errors/DonorErrors';
|
||||
import { Donor } from '../entities/Donor';
|
||||
import { CreateParticipant } from './CreateParticipant';
|
||||
|
||||
/**
|
||||
* This classed is used to create a new Donor entity from a json body (post request).
|
||||
*/
|
||||
export class CreateDonor extends CreateParticipant {
|
||||
|
||||
/**
|
||||
* Does this donor need a receipt?
|
||||
*/
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
receiptNeeded?: boolean = false;
|
||||
|
||||
/**
|
||||
* Creates a new Donor entity from this.
|
||||
*/
|
||||
public async toDonor(): Promise<Donor> {
|
||||
let newDonor: Donor = new Donor();
|
||||
|
||||
newDonor.firstname = this.firstname;
|
||||
newDonor.middlename = this.middlename;
|
||||
newDonor.lastname = this.lastname;
|
||||
newDonor.phone = this.phone;
|
||||
newDonor.email = this.email;
|
||||
newDonor.address = await this.getAddress();
|
||||
newDonor.receiptNeeded = this.receiptNeeded;
|
||||
|
||||
if (this.receiptNeeded == true && this.address == null) {
|
||||
throw new DonorReceiptAddressNeededError()
|
||||
}
|
||||
|
||||
return newDonor;
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ export class CreateRunnerOrganisation extends CreateRunnerGroup {
|
||||
|
||||
newRunnerOrganisation.name = this.name;
|
||||
newRunnerOrganisation.contact = await this.getContact();
|
||||
newRunnerOrganisation.address = await this.getAddress();
|
||||
// newRunnerOrganisation.address = await this.getAddress();
|
||||
|
||||
return newRunnerOrganisation;
|
||||
}
|
||||
|
||||
59
src/models/actions/CreateScan.ts
Normal file
59
src/models/actions/CreateScan.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
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';
|
||||
|
||||
/**
|
||||
* This class is used to create a new Scan entity from a json body (post request).
|
||||
*/
|
||||
export abstract class CreateScan {
|
||||
/**
|
||||
* The scan's associated runner.
|
||||
* This is important to link ran distances to runners.
|
||||
*/
|
||||
@IsInt()
|
||||
@IsPositive()
|
||||
runner: number;
|
||||
|
||||
/**
|
||||
* Is the scan valid (for fraud reasons).
|
||||
* The determination of validity will work differently for every child class.
|
||||
* Default: true
|
||||
*/
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
valid?: boolean = true;
|
||||
|
||||
/**
|
||||
* The scan's distance in meters.
|
||||
* Can be set manually or derived from another object.
|
||||
*/
|
||||
@IsInt()
|
||||
@IsPositive()
|
||||
public distance: number;
|
||||
|
||||
/**
|
||||
* Creates a new Scan entity from this.
|
||||
*/
|
||||
public async toScan(): Promise<Scan> {
|
||||
let newScan = new Scan();
|
||||
|
||||
newScan.distance = this.distance;
|
||||
newScan.valid = this.valid;
|
||||
newScan.runner = await this.getRunner();
|
||||
|
||||
return newScan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a runner based on the runner id provided via this.runner.
|
||||
*/
|
||||
public async getRunner(): Promise<Runner> {
|
||||
const runner = await getConnection().getRepository(Runner).findOne({ id: this.runner });
|
||||
if (!runner) {
|
||||
throw new RunnerNotFoundError();
|
||||
}
|
||||
return runner;
|
||||
}
|
||||
}
|
||||
64
src/models/actions/CreateScanStation.ts
Normal file
64
src/models/actions/CreateScanStation.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import * as argon2 from "argon2";
|
||||
import { IsBoolean, IsInt, IsOptional, IsPositive, IsString } from 'class-validator';
|
||||
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';
|
||||
|
||||
/**
|
||||
* This class is used to create a new StatsClient entity from a json body (post request).
|
||||
*/
|
||||
export class CreateScanStation {
|
||||
/**
|
||||
* The new station's description.
|
||||
*/
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
|
||||
/**
|
||||
* The station's associated track.
|
||||
*/
|
||||
@IsInt()
|
||||
@IsPositive()
|
||||
track: number;
|
||||
|
||||
/**
|
||||
* Is this station enabled?
|
||||
*/
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
enabled?: boolean = true;
|
||||
|
||||
/**
|
||||
* Converts this to a ScanStation entity.
|
||||
*/
|
||||
public async toEntity(): Promise<ScanStation> {
|
||||
let newStation: ScanStation = new ScanStation();
|
||||
|
||||
newStation.description = this.description;
|
||||
newStation.enabled = this.enabled;
|
||||
newStation.track = await this.getTrack();
|
||||
|
||||
let newUUID = uuid.v4().toUpperCase();
|
||||
newStation.prefix = crypto.createHash("sha3-512").update(newUUID).digest('hex').substring(0, 7).toUpperCase();
|
||||
newStation.key = await argon2.hash(newStation.prefix + "." + newUUID);
|
||||
newStation.cleartextkey = newStation.prefix + "." + newUUID;
|
||||
|
||||
return newStation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get's a track by it's id provided via this.track.
|
||||
* Used to link the new station to a track.
|
||||
*/
|
||||
public async getTrack(): Promise<Track> {
|
||||
const track = await getConnection().getRepository(Track).findOne({ id: this.track });
|
||||
if (!track) {
|
||||
throw new TrackNotFoundError();
|
||||
}
|
||||
return track;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { IsInt, IsNotEmpty, IsPositive, IsString } from 'class-validator';
|
||||
import { IsInt, IsNotEmpty, IsOptional, IsPositive, IsString } from 'class-validator';
|
||||
import { TrackLapTimeCantBeNegativeError } from '../../errors/TrackErrors';
|
||||
import { Track } from '../entities/Track';
|
||||
|
||||
/**
|
||||
@@ -19,6 +20,14 @@ export class CreateTrack {
|
||||
@IsPositive()
|
||||
distance: number;
|
||||
|
||||
/**
|
||||
* The minimum time a runner should take to run a lap on this track (in seconds).
|
||||
* Will be used for fraud detection.
|
||||
*/
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
minimumLapTime: number;
|
||||
|
||||
/**
|
||||
* Creates a new Track entity from this.
|
||||
*/
|
||||
@@ -27,6 +36,10 @@ export class CreateTrack {
|
||||
|
||||
newTrack.name = this.name;
|
||||
newTrack.distance = this.distance;
|
||||
newTrack.minimumLapTime = this.minimumLapTime;
|
||||
if (this.minimumLapTime < 0) {
|
||||
throw new TrackLapTimeCantBeNegativeError();
|
||||
}
|
||||
|
||||
return newTrack;
|
||||
}
|
||||
|
||||
84
src/models/actions/CreateTrackScan.ts
Normal file
84
src/models/actions/CreateTrackScan.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { IsNotEmpty } 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';
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* The runnerCard associated with the scan.
|
||||
* This get's saved for documentation and management purposes.
|
||||
*/
|
||||
@IsNotEmpty()
|
||||
card: number;
|
||||
|
||||
/**
|
||||
* The scanning station that created the scan.
|
||||
* Mainly used for logging and traceing back scans (or errors)
|
||||
*/
|
||||
@IsNotEmpty()
|
||||
station: number;
|
||||
|
||||
/**
|
||||
* Creates a new Track entity from this.
|
||||
*/
|
||||
public async toScan(): Promise<TrackScan> {
|
||||
let newScan: TrackScan = new TrackScan();
|
||||
|
||||
newScan.station = await this.getStation();
|
||||
newScan.card = await this.getCard();
|
||||
|
||||
newScan.track = newScan.station.track;
|
||||
newScan.runner = newScan.card.runner;
|
||||
|
||||
if (!newScan.runner) {
|
||||
throw new RunnerNotFoundError();
|
||||
}
|
||||
|
||||
newScan.timestamp = new Date(Date.now()).toString();
|
||||
newScan.valid = await this.validateScan(newScan);
|
||||
|
||||
return newScan;
|
||||
}
|
||||
|
||||
public async getCard(): Promise<RunnerCard> {
|
||||
const track = await getConnection().getRepository(RunnerCard).findOne({ id: this.card }, { relations: ["runner"] });
|
||||
if (!track) {
|
||||
throw new Error();
|
||||
}
|
||||
return track;
|
||||
}
|
||||
|
||||
public async getStation(): Promise<ScanStation> {
|
||||
const track = await getConnection().getRepository(ScanStation).findOne({ id: this.card }, { relations: ["track"] });
|
||||
if (!track) {
|
||||
throw new Error();
|
||||
}
|
||||
return track;
|
||||
}
|
||||
|
||||
public async validateScan(scan: TrackScan): Promise<boolean> {
|
||||
const scans = await getConnection().getRepository(TrackScan).find({ where: { runner: scan.runner }, 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) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as argon2 from "argon2";
|
||||
import { IsBoolean, IsEmail, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
|
||||
import { IsBoolean, IsEmail, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator';
|
||||
import { getConnectionManager } from 'typeorm';
|
||||
import * as uuid from 'uuid';
|
||||
import { config } from '../../config';
|
||||
@@ -78,7 +78,13 @@ export class CreateUser {
|
||||
@IsOptional()
|
||||
groups?: number[] | number
|
||||
|
||||
//TODO: ProfilePics
|
||||
/**
|
||||
* The user's profile pic (or rather a url pointing to it).
|
||||
*/
|
||||
@IsString()
|
||||
@IsUrl()
|
||||
@IsOptional()
|
||||
profilePic?: string;
|
||||
|
||||
/**
|
||||
* Converts this to a User entity.
|
||||
@@ -100,7 +106,9 @@ export class CreateUser {
|
||||
newUser.password = await argon2.hash(this.password + newUser.uuid);
|
||||
newUser.groups = await this.getGroups();
|
||||
newUser.enabled = this.enabled;
|
||||
//TODO: ProfilePics
|
||||
|
||||
if (!this.profilePic) { newUser.profilePic = `https://dev.lauf-fuer-kaya.de/lfk-logo.png`; }
|
||||
else { newUser.profilePic = this.profilePic; }
|
||||
|
||||
return newUser;
|
||||
}
|
||||
|
||||
44
src/models/actions/UpdateDonor.ts
Normal file
44
src/models/actions/UpdateDonor.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { IsBoolean, IsInt, IsOptional } from 'class-validator';
|
||||
import { DonorReceiptAddressNeededError } from '../../errors/DonorErrors';
|
||||
import { Donor } from '../entities/Donor';
|
||||
import { CreateParticipant } from './CreateParticipant';
|
||||
|
||||
/**
|
||||
* This class is used to update a Donor entity (via put request).
|
||||
*/
|
||||
export class UpdateDonor extends CreateParticipant {
|
||||
|
||||
/**
|
||||
* The updated donor's id.
|
||||
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
|
||||
*/
|
||||
@IsInt()
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* Does the updated donor need a receipt?
|
||||
*/
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
receiptNeeded?: boolean;
|
||||
|
||||
|
||||
/**
|
||||
* Updates a provided Donor entity based on this.
|
||||
*/
|
||||
public async updateDonor(donor: Donor): Promise<Donor> {
|
||||
donor.firstname = this.firstname;
|
||||
donor.middlename = this.middlename;
|
||||
donor.lastname = this.lastname;
|
||||
donor.phone = this.phone;
|
||||
donor.email = this.email;
|
||||
donor.receiptNeeded = this.receiptNeeded;
|
||||
donor.address = await this.getAddress();
|
||||
|
||||
if (this.receiptNeeded == true && this.address == null) {
|
||||
throw new DonorReceiptAddressNeededError()
|
||||
}
|
||||
|
||||
return donor;
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ export class UpdateRunnerOrganisation extends CreateRunnerGroup {
|
||||
|
||||
organisation.name = this.name;
|
||||
organisation.contact = await this.getContact();
|
||||
organisation.address = await this.getAddress();
|
||||
// organisation.address = await this.getAddress();
|
||||
|
||||
return organisation;
|
||||
}
|
||||
|
||||
62
src/models/actions/UpdateScan.ts
Normal file
62
src/models/actions/UpdateScan.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
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';
|
||||
|
||||
/**
|
||||
* This class is used to update a Scan entity (via put request)
|
||||
*/
|
||||
export abstract class UpdateScan {
|
||||
/**
|
||||
* The updated scan's id.
|
||||
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
|
||||
*/
|
||||
@IsInt()
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* The updated scan's associated runner.
|
||||
* This is important to link ran distances to runners.
|
||||
*/
|
||||
@IsInt()
|
||||
@IsPositive()
|
||||
runner: number;
|
||||
|
||||
/**
|
||||
* Is the updated scan valid (for fraud reasons).
|
||||
*/
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
valid?: boolean = true;
|
||||
|
||||
/**
|
||||
* The updated scan's distance in meters.
|
||||
*/
|
||||
@IsInt()
|
||||
@IsPositive()
|
||||
public distance: number;
|
||||
|
||||
/**
|
||||
* Update a Scan entity based on this.
|
||||
* @param scan The scan that shall be updated.
|
||||
*/
|
||||
public async updateScan(scan: Scan): Promise<Scan> {
|
||||
scan.distance = this.distance;
|
||||
scan.valid = this.valid;
|
||||
scan.runner = await this.getRunner();
|
||||
|
||||
return scan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a runner based on the runner id provided via this.runner.
|
||||
*/
|
||||
public async getRunner(): Promise<Runner> {
|
||||
const runner = await getConnection().getRepository(Runner).findOne({ id: this.runner });
|
||||
if (!runner) {
|
||||
throw new RunnerNotFoundError();
|
||||
}
|
||||
return runner;
|
||||
}
|
||||
}
|
||||
39
src/models/actions/UpdateScanStation.ts
Normal file
39
src/models/actions/UpdateScanStation.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { IsBoolean, IsInt, IsOptional, IsString } from 'class-validator';
|
||||
import { ScanStation } from '../entities/ScanStation';
|
||||
|
||||
/**
|
||||
* This class is used to update a ScanStation entity (via put request)
|
||||
*/
|
||||
export class UpdateScanStation {
|
||||
/**
|
||||
* The updated station's id.
|
||||
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
|
||||
*/
|
||||
@IsInt()
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* The updated station's description.
|
||||
*/
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
|
||||
/**
|
||||
* Is this station enabled?
|
||||
*/
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
enabled?: boolean = true;
|
||||
|
||||
/**
|
||||
* Update a ScanStation entity based on this.
|
||||
* @param station The station that shall be updated.
|
||||
*/
|
||||
public async updateStation(station: ScanStation): Promise<ScanStation> {
|
||||
station.description = this.description;
|
||||
station.enabled = this.enabled;
|
||||
|
||||
return station;
|
||||
}
|
||||
}
|
||||
50
src/models/actions/UpdateTrack.ts
Normal file
50
src/models/actions/UpdateTrack.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { IsInt, IsNotEmpty, IsOptional, IsPositive, IsString } from 'class-validator';
|
||||
import { TrackLapTimeCantBeNegativeError } from '../../errors/TrackErrors';
|
||||
import { Track } from '../entities/Track';
|
||||
|
||||
/**
|
||||
* This class is used to update a Track entity (via put request).
|
||||
*/
|
||||
export class UpdateTrack {
|
||||
/**
|
||||
* The updated track's id.
|
||||
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
|
||||
*/
|
||||
@IsInt()
|
||||
id: number;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The updated track's distance in meters (must be greater than 0).
|
||||
*/
|
||||
@IsInt()
|
||||
@IsPositive()
|
||||
distance: number;
|
||||
|
||||
/**
|
||||
* The minimum time a runner should take to run a lap on this track (in seconds).
|
||||
* Will be used for fraud detection.
|
||||
*/
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
minimumLapTime: number;
|
||||
|
||||
|
||||
/**
|
||||
* Update a Track entity based on this.
|
||||
* @param track The track that shall be updated.
|
||||
*/
|
||||
public updateTrack(track: Track): Track {
|
||||
track.name = this.name;
|
||||
track.distance = this.distance;
|
||||
track.minimumLapTime = this.minimumLapTime;
|
||||
if (this.minimumLapTime < 0) {
|
||||
throw new TrackLapTimeCantBeNegativeError();
|
||||
}
|
||||
|
||||
return track;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as argon2 from "argon2";
|
||||
import { IsBoolean, IsEmail, IsInt, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
|
||||
import { IsBoolean, IsEmail, IsInt, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator';
|
||||
import { getConnectionManager } from 'typeorm';
|
||||
import { config } from '../../config';
|
||||
import { UsernameOrEmailNeededError } from '../../errors/AuthError';
|
||||
@@ -87,7 +87,16 @@ export class UpdateUser {
|
||||
groups?: UserGroup[]
|
||||
|
||||
/**
|
||||
* Updates a provided User entity based on this.
|
||||
* The user's profile pic (or rather a url pointing to it).
|
||||
*/
|
||||
@IsString()
|
||||
@IsUrl()
|
||||
@IsOptional()
|
||||
profilePic?: string;
|
||||
|
||||
/**
|
||||
* Updates a user entity based on this.
|
||||
* @param user The user that shall be updated.
|
||||
*/
|
||||
public async updateUser(user: User): Promise<User> {
|
||||
user.email = this.email;
|
||||
@@ -106,7 +115,9 @@ export class UpdateUser {
|
||||
user.lastname = this.lastname
|
||||
user.phone = this.phone;
|
||||
user.groups = await this.getGroups();
|
||||
//TODO: ProfilePics
|
||||
|
||||
if (!this.profilePic) { user.profilePic = `https://dev.lauf-fuer-kaya.de/lfk-logo.png`; }
|
||||
else { user.profilePic = this.profilePic; }
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
@@ -7,8 +7,7 @@ import {
|
||||
} from "class-validator";
|
||||
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { config } from '../../config';
|
||||
import { Participant } from "./Participant";
|
||||
import { RunnerOrganisation } from "./RunnerOrganisation";
|
||||
import { IAddressUser } from './IAddressUser';
|
||||
|
||||
/**
|
||||
* Defines the Address entity.
|
||||
@@ -79,12 +78,13 @@ export class Address {
|
||||
/**
|
||||
* Used to link the address to participants.
|
||||
*/
|
||||
@OneToMany(() => Participant, participant => participant.address, { nullable: true })
|
||||
participants: Participant[];
|
||||
@OneToMany(() => IAddressUser, addressUser => addressUser.address, { nullable: true })
|
||||
addressUsers: IAddressUser[];
|
||||
|
||||
/**
|
||||
* Used to link the address to runner groups.
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
@OneToMany(() => RunnerOrganisation, group => group.address, { nullable: true })
|
||||
groups: RunnerOrganisation[];
|
||||
public toResponse() {
|
||||
return new Error("NotImplemented");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,4 +39,11 @@ export class DistanceDonation extends Donation {
|
||||
}
|
||||
return calculatedAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
public toResponse() {
|
||||
return new Error("NotImplemented");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
IsNotEmpty
|
||||
} from "class-validator";
|
||||
import { Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
|
||||
import { Participant } from "./Participant";
|
||||
import { Donor } from './Donor';
|
||||
|
||||
/**
|
||||
* Defines the Donation entity.
|
||||
@@ -24,12 +24,19 @@ export abstract class Donation {
|
||||
* The donations's donor.
|
||||
*/
|
||||
@IsNotEmpty()
|
||||
@ManyToOne(() => Participant, donor => donor.donations)
|
||||
donor: Participant;
|
||||
@ManyToOne(() => Donor, donor => donor.donations)
|
||||
donor: Donor;
|
||||
|
||||
/**
|
||||
* The donation's amount in cents (or whatever your currency's smallest unit is.).
|
||||
* The exact implementation may differ for each type of donation.
|
||||
*/
|
||||
abstract amount: number;
|
||||
|
||||
/**
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
public toResponse() {
|
||||
return new Error("NotImplemented");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { IsBoolean } from "class-validator";
|
||||
import { ChildEntity, Column } from "typeorm";
|
||||
import { ChildEntity, Column, OneToMany } from "typeorm";
|
||||
import { ResponseDonor } from '../responses/ResponseDonor';
|
||||
import { Donation } from './Donation';
|
||||
import { Participant } from "./Participant";
|
||||
|
||||
/**
|
||||
@@ -14,4 +16,18 @@ export class Donor extends Participant {
|
||||
@Column()
|
||||
@IsBoolean()
|
||||
receiptNeeded: boolean = false;
|
||||
|
||||
/**
|
||||
* Used to link the participant as the donor of a donation.
|
||||
* Attention: Only runner's can be associated as a distanceDonations distance source.
|
||||
*/
|
||||
@OneToMany(() => Donation, donation => donation.donor, { nullable: true })
|
||||
donations: Donation[];
|
||||
|
||||
/**
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
public toResponse(): ResponseDonor {
|
||||
return new ResponseDonor(this);
|
||||
}
|
||||
}
|
||||
@@ -16,4 +16,11 @@ export class FixedDonation extends Donation {
|
||||
@IsInt()
|
||||
@IsPositive()
|
||||
amount: number;
|
||||
|
||||
/**
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
public toResponse() {
|
||||
return new Error("NotImplemented");
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { config } from '../../config';
|
||||
import { Address } from "./Address";
|
||||
import { IAddressUser } from './IAddressUser';
|
||||
import { RunnerGroup } from "./RunnerGroup";
|
||||
|
||||
/**
|
||||
@@ -17,7 +18,7 @@ import { RunnerGroup } from "./RunnerGroup";
|
||||
* Mainly it's own class to reduce duplicate code and enable contact's to be associated with multiple groups.
|
||||
*/
|
||||
@Entity()
|
||||
export class GroupContact {
|
||||
export class GroupContact implements IAddressUser {
|
||||
/**
|
||||
* Autogenerated unique id (primary key).
|
||||
*/
|
||||
@@ -54,7 +55,7 @@ export class GroupContact {
|
||||
* This is a address object to prevent any formatting differences.
|
||||
*/
|
||||
@IsOptional()
|
||||
@ManyToOne(() => Address, address => address.participants, { nullable: true })
|
||||
@ManyToOne(() => Address, address => address.addressUsers, { nullable: true })
|
||||
address?: Address;
|
||||
|
||||
/**
|
||||
@@ -80,4 +81,11 @@ export class GroupContact {
|
||||
*/
|
||||
@OneToMany(() => RunnerGroup, group => group.contact, { nullable: true })
|
||||
groups: RunnerGroup[];
|
||||
|
||||
/**
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
public toResponse() {
|
||||
return new Error("NotImplemented");
|
||||
}
|
||||
}
|
||||
20
src/models/entities/IAddressUser.ts
Normal file
20
src/models/entities/IAddressUser.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Entity, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||
import { Address } from './Address';
|
||||
|
||||
/**
|
||||
* The interface(tm) all entities using addresses have to implement.
|
||||
* This is a abstract class, because apparently typeorm can't really work with interfaces :/
|
||||
*/
|
||||
@Entity()
|
||||
export abstract class IAddressUser {
|
||||
@PrimaryColumn()
|
||||
id: number;
|
||||
|
||||
@ManyToOne(() => Address, address => address.addressUsers, { nullable: true })
|
||||
address?: Address
|
||||
|
||||
/**
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
public abstract toResponse();
|
||||
}
|
||||
@@ -7,10 +7,11 @@ import {
|
||||
|
||||
IsString
|
||||
} from "class-validator";
|
||||
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
|
||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
|
||||
import { config } from '../../config';
|
||||
import { ResponseParticipant } from '../responses/ResponseParticipant';
|
||||
import { Address } from "./Address";
|
||||
import { Donation } from "./Donation";
|
||||
import { IAddressUser } from './IAddressUser';
|
||||
|
||||
/**
|
||||
* Defines the Participant entity.
|
||||
@@ -18,7 +19,7 @@ import { Donation } from "./Donation";
|
||||
*/
|
||||
@Entity()
|
||||
@TableInheritance({ column: { name: "type", type: "varchar" } })
|
||||
export abstract class Participant {
|
||||
export abstract class Participant implements IAddressUser {
|
||||
/**
|
||||
* Autogenerated unique id (primary key).
|
||||
*/
|
||||
@@ -54,7 +55,7 @@ export abstract class Participant {
|
||||
* The participant's address.
|
||||
* This is a address object to prevent any formatting differences.
|
||||
*/
|
||||
@ManyToOne(() => Address, address => address.participants, { nullable: true })
|
||||
@ManyToOne(() => Address, address => address.addressUsers, { nullable: true })
|
||||
address?: Address;
|
||||
|
||||
/**
|
||||
@@ -76,9 +77,7 @@ export abstract class Participant {
|
||||
email?: string;
|
||||
|
||||
/**
|
||||
* Used to link the participant as the donor of a donation.
|
||||
* Attention: Only runner's can be associated as a distanceDonations distance source.
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
@OneToMany(() => Donation, donation => donation.donor, { nullable: true })
|
||||
donations: Donation[];
|
||||
public abstract toResponse(): ResponseParticipant;
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { PermissionAction } from '../enums/PermissionAction';
|
||||
import { PermissionTarget } from '../enums/PermissionTargets';
|
||||
import { ResponsePermission } from '../responses/ResponsePermission';
|
||||
import { Principal } from './Principal';
|
||||
/**
|
||||
* Defines the Permission entity.
|
||||
@@ -51,4 +52,11 @@ export class Permission {
|
||||
public toString(): string {
|
||||
return this.target + ":" + this.action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
public toResponse(): ResponsePermission {
|
||||
return new ResponsePermission(this);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { IsInt, IsNotEmpty } from "class-validator";
|
||||
import { ChildEntity, ManyToOne, OneToMany } from "typeorm";
|
||||
import { ResponseRunner } from '../responses/ResponseRunner';
|
||||
import { DistanceDonation } from "./DistanceDonation";
|
||||
import { Participant } from "./Participant";
|
||||
import { RunnerCard } from "./RunnerCard";
|
||||
@@ -18,7 +19,7 @@ export class Runner extends Participant {
|
||||
* Can be a runner team or organisation.
|
||||
*/
|
||||
@IsNotEmpty()
|
||||
@ManyToOne(() => RunnerGroup, group => group.runners, { nullable: false })
|
||||
@ManyToOne(() => RunnerGroup, group => group.runners)
|
||||
group: RunnerGroup;
|
||||
|
||||
/**
|
||||
@@ -47,7 +48,7 @@ export class Runner extends Participant {
|
||||
* This is implemented here to avoid duplicate code in other files.
|
||||
*/
|
||||
public get validScans(): Scan[] {
|
||||
return this.scans.filter(scan => { scan.valid === true });
|
||||
return this.scans.filter(scan => scan.valid == true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,4 +67,11 @@ export class Runner extends Participant {
|
||||
public get distanceDonationAmount(): number {
|
||||
return this.distanceDonations.reduce((sum, current) => sum + current.amountPerDistance, 0) * this.distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
public toResponse(): ResponseRunner {
|
||||
return new ResponseRunner(this);
|
||||
}
|
||||
}
|
||||
@@ -57,4 +57,11 @@ export class RunnerCard {
|
||||
*/
|
||||
@OneToMany(() => TrackScan, scan => scan.track, { nullable: true })
|
||||
scans: TrackScan[];
|
||||
|
||||
/**
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
public toResponse() {
|
||||
return new Error("NotImplemented");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
IsString
|
||||
} from "class-validator";
|
||||
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
|
||||
import { ResponseRunnerGroup } from '../responses/ResponseRunnerGroup';
|
||||
import { GroupContact } from "./GroupContact";
|
||||
import { Runner } from "./Runner";
|
||||
|
||||
@@ -60,4 +61,9 @@ export abstract class RunnerGroup {
|
||||
public get distanceDonationAmount(): number {
|
||||
return this.runners.reduce((sum, current) => sum + current.distanceDonationAmount, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
public abstract toResponse(): ResponseRunnerGroup;
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { IsInt, IsOptional } from "class-validator";
|
||||
import { ChildEntity, ManyToOne, OneToMany } from "typeorm";
|
||||
import { Address } from "./Address";
|
||||
import { ResponseRunnerOrganisation } from '../responses/ResponseRunnerOrganisation';
|
||||
import { Address } from './Address';
|
||||
import { IAddressUser } from './IAddressUser';
|
||||
import { Runner } from './Runner';
|
||||
import { RunnerGroup } from "./RunnerGroup";
|
||||
import { RunnerTeam } from "./RunnerTeam";
|
||||
@@ -10,13 +12,13 @@ import { RunnerTeam } from "./RunnerTeam";
|
||||
* This usually is a school, club or company.
|
||||
*/
|
||||
@ChildEntity()
|
||||
export class RunnerOrganisation extends RunnerGroup {
|
||||
export class RunnerOrganisation extends RunnerGroup implements IAddressUser {
|
||||
|
||||
/**
|
||||
* The organisations's address.
|
||||
*/
|
||||
@IsOptional()
|
||||
@ManyToOne(() => Address, address => address.groups, { nullable: true })
|
||||
@ManyToOne(() => Address, address => address.addressUsers, { nullable: true })
|
||||
address?: Address;
|
||||
|
||||
/**
|
||||
@@ -53,4 +55,11 @@ export class RunnerOrganisation extends RunnerGroup {
|
||||
public get distanceDonationAmount(): number {
|
||||
return this.allRunners.reduce((sum, current) => sum + current.distanceDonationAmount, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
public toResponse(): ResponseRunnerOrganisation {
|
||||
return new ResponseRunnerOrganisation(this);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { IsNotEmpty } from "class-validator";
|
||||
import { ChildEntity, ManyToOne } from "typeorm";
|
||||
import { ResponseRunnerTeam } from '../responses/ResponseRunnerTeam';
|
||||
import { RunnerGroup } from "./RunnerGroup";
|
||||
import { RunnerOrganisation } from "./RunnerOrganisation";
|
||||
|
||||
@@ -17,4 +18,11 @@ export class RunnerTeam extends RunnerGroup {
|
||||
@IsNotEmpty()
|
||||
@ManyToOne(() => RunnerOrganisation, org => org.teams, { nullable: true })
|
||||
parentGroup?: RunnerOrganisation;
|
||||
|
||||
/**
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
public toResponse(): ResponseRunnerTeam {
|
||||
return new ResponseRunnerTeam(this);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
IsPositive
|
||||
} from "class-validator";
|
||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
|
||||
import { ResponseScan } from '../responses/ResponseScan';
|
||||
import { Runner } from "./Runner";
|
||||
|
||||
/**
|
||||
@@ -14,7 +15,7 @@ import { Runner } from "./Runner";
|
||||
*/
|
||||
@Entity()
|
||||
@TableInheritance({ column: { name: "type", type: "varchar" } })
|
||||
export abstract class Scan {
|
||||
export class Scan {
|
||||
/**
|
||||
* Autogenerated unique id (primary key).
|
||||
*/
|
||||
@@ -30,14 +31,6 @@ export abstract class Scan {
|
||||
@ManyToOne(() => Runner, runner => runner.scans, { nullable: false })
|
||||
runner: Runner;
|
||||
|
||||
/**
|
||||
* The scan's distance in meters.
|
||||
* Can be set manually or derived from another object.
|
||||
*/
|
||||
@IsInt()
|
||||
@IsPositive()
|
||||
abstract distance: number;
|
||||
|
||||
/**
|
||||
* Is the scan valid (for fraud reasons).
|
||||
* The determination of validity will work differently for every child class.
|
||||
@@ -46,4 +39,37 @@ export abstract class Scan {
|
||||
@Column()
|
||||
@IsBoolean()
|
||||
valid: boolean = true;
|
||||
|
||||
/**
|
||||
* The scan's distance in meters.
|
||||
* This is the "real" value used by "normal" scans..
|
||||
*/
|
||||
@Column({ nullable: true })
|
||||
@IsInt()
|
||||
private _distance?: number;
|
||||
|
||||
/**
|
||||
* The scan's distance in meters.
|
||||
* Can be set manually or derived from another object.
|
||||
*/
|
||||
@IsInt()
|
||||
@IsPositive()
|
||||
public get distance(): number {
|
||||
return this._distance;
|
||||
}
|
||||
|
||||
/**
|
||||
* The scan's distance in meters.
|
||||
* Can be set manually or derived from another object.
|
||||
*/
|
||||
public set distance(value: number) {
|
||||
this._distance = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
public toResponse(): ResponseScan {
|
||||
return new ResponseScan(this);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
IsString
|
||||
} from "class-validator";
|
||||
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { ResponseScanStation } from '../responses/ResponseScanStation';
|
||||
import { Track } from "./Track";
|
||||
import { TrackScan } from "./TrackScan";
|
||||
|
||||
@@ -39,6 +40,14 @@ export class ScanStation {
|
||||
@ManyToOne(() => Track, track => track.stations, { nullable: false })
|
||||
track: Track;
|
||||
|
||||
/**
|
||||
* The client's api key prefix.
|
||||
* This is used identitfy a client by it's api key.
|
||||
*/
|
||||
@Column({ unique: true })
|
||||
@IsString()
|
||||
prefix: string;
|
||||
|
||||
/**
|
||||
* The station's api key.
|
||||
* This is used to authorize a station against the api (not implemented yet).
|
||||
@@ -49,16 +58,30 @@ export class ScanStation {
|
||||
key: string;
|
||||
|
||||
/**
|
||||
* Is the station enabled (for fraud and setup reasons)?
|
||||
* Default: true
|
||||
* The client's api key in plain text.
|
||||
* This will only be used to display the full key on creation and updates.
|
||||
*/
|
||||
@Column()
|
||||
@IsBoolean()
|
||||
enabled: boolean = true;
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
cleartextkey?: string;
|
||||
|
||||
/**
|
||||
* Used to link track scans to a scan station.
|
||||
*/
|
||||
@OneToMany(() => TrackScan, scan => scan.track, { nullable: true })
|
||||
scans: TrackScan[];
|
||||
|
||||
/**
|
||||
* Is this station enabled?
|
||||
*/
|
||||
@Column({ nullable: true })
|
||||
@IsBoolean()
|
||||
enabled?: boolean = true;
|
||||
|
||||
/**
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
public toResponse(): ResponseScanStation {
|
||||
return new ResponseScanStation(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { IsInt, IsOptional, IsString } from "class-validator";
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { ResponseStatsClient } from '../responses/ResponseStatsClient';
|
||||
/**
|
||||
* Defines the StatsClient entity.
|
||||
* StatsClients can be used to access the protected parts of the stats api (top runners, donators and so on).
|
||||
@@ -45,4 +46,11 @@ export class StatsClient {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
cleartextkey?: string;
|
||||
|
||||
/**
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
public toResponse(): ResponseStatsClient {
|
||||
return new ResponseStatsClient(this);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import {
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsPositive,
|
||||
IsString
|
||||
} from "class-validator";
|
||||
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { ResponseTrack } from '../responses/ResponseTrack';
|
||||
import { ScanStation } from "./ScanStation";
|
||||
import { TrackScan } from "./TrackScan";
|
||||
|
||||
@@ -18,7 +20,7 @@ export class Track {
|
||||
*/
|
||||
@PrimaryGeneratedColumn()
|
||||
@IsInt()
|
||||
id: number;;
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* The track's name.
|
||||
@@ -38,6 +40,15 @@ export class Track {
|
||||
@IsPositive()
|
||||
distance: number;
|
||||
|
||||
/**
|
||||
* The minimum time a runner should take to run a lap on this track (in seconds).
|
||||
* Will be used for fraud detection.
|
||||
*/
|
||||
@Column({ nullable: true })
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
minimumLapTime?: number;
|
||||
|
||||
/**
|
||||
* Used to link scan stations to a certain track.
|
||||
* This makes the configuration of the scan stations easier.
|
||||
@@ -51,4 +62,11 @@ export class Track {
|
||||
*/
|
||||
@OneToMany(() => TrackScan, scan => scan.track, { nullable: true })
|
||||
scans: TrackScan[];
|
||||
|
||||
/**
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
public toResponse(): ResponseTrack {
|
||||
return new ResponseTrack(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
IsPositive
|
||||
} from "class-validator";
|
||||
import { ChildEntity, Column, ManyToOne } from "typeorm";
|
||||
import { ResponseTrackScan } from '../responses/ResponseTrackScan';
|
||||
import { RunnerCard } from "./RunnerCard";
|
||||
import { Scan } from "./Scan";
|
||||
import { ScanStation } from "./ScanStation";
|
||||
@@ -59,4 +60,11 @@ export class TrackScan extends Scan {
|
||||
@IsDateString()
|
||||
@IsNotEmpty()
|
||||
timestamp: string;
|
||||
|
||||
/**
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
public toResponse(): ResponseTrackScan {
|
||||
return new ResponseTrackScan(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUUID } from "class-validator";
|
||||
import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl, IsUUID } from "class-validator";
|
||||
import { ChildEntity, Column, JoinTable, ManyToMany, OneToMany } from "typeorm";
|
||||
import { config } from '../../config';
|
||||
import { ResponsePrincipal } from '../responses/ResponsePrincipal';
|
||||
@@ -106,10 +106,10 @@ export class User extends Principal {
|
||||
* The user's profile picture.
|
||||
* We haven't decided yet if this will be a bas64 encoded image or just a link to the profile picture.
|
||||
*/
|
||||
@Column({ nullable: true, unique: false })
|
||||
@Column({ nullable: false, unique: false })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
profilePic?: string;
|
||||
@IsUrl()
|
||||
profilePic: string;
|
||||
|
||||
/**
|
||||
* The last time the user requested a password reset.
|
||||
@@ -128,6 +128,26 @@ export class User extends Principal {
|
||||
@OneToMany(() => UserAction, action => action.user, { nullable: true })
|
||||
actions: UserAction[]
|
||||
|
||||
/**
|
||||
* Resolves all permissions granted to this user through groups or directly to the string enum format.
|
||||
*/
|
||||
public get allPermissions(): string[] {
|
||||
let returnPermissions: string[] = new Array<string>();
|
||||
|
||||
if (!this.permissions) { return returnPermissions; }
|
||||
for (let permission of this.permissions) {
|
||||
returnPermissions.push(permission.toString());
|
||||
}
|
||||
|
||||
if (!this.groups) { return returnPermissions; }
|
||||
for (let group of this.groups) {
|
||||
for (let permission of group.permissions) {
|
||||
returnPermissions.push(permission.toString());
|
||||
}
|
||||
}
|
||||
return Array.from(new Set(returnPermissions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
|
||||
@@ -52,4 +52,11 @@ export class UserAction {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
changed: string;
|
||||
|
||||
/**
|
||||
* Turns this entity into it's response class.
|
||||
*/
|
||||
public toResponse() {
|
||||
return new Error("NotImplemented");
|
||||
}
|
||||
}
|
||||
@@ -9,5 +9,8 @@ export enum PermissionTarget {
|
||||
USER = 'USER',
|
||||
USERGROUP = 'USERGROUP',
|
||||
PERMISSION = 'PERMISSION',
|
||||
STATSCLIENT = 'STATSCLIENT'
|
||||
STATSCLIENT = 'STATSCLIENT',
|
||||
DONOR = 'DONOR',
|
||||
SCAN = 'SCAN',
|
||||
STATION = 'STATION'
|
||||
}
|
||||
26
src/models/responses/ResponseDonor.ts
Normal file
26
src/models/responses/ResponseDonor.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import {
|
||||
IsBoolean
|
||||
} from "class-validator";
|
||||
import { Donor } from '../entities/Donor';
|
||||
import { ResponseParticipant } from './ResponseParticipant';
|
||||
|
||||
/**
|
||||
* Defines the donor response.
|
||||
*/
|
||||
export class ResponseDonor extends ResponseParticipant {
|
||||
|
||||
/**
|
||||
* Does this donor need a receipt?
|
||||
*/
|
||||
@IsBoolean()
|
||||
receiptNeeded: boolean;
|
||||
|
||||
/**
|
||||
* Creates a ResponseRunner object from a runner.
|
||||
* @param runner The user the response shall be build for.
|
||||
*/
|
||||
public constructor(donor: Donor) {
|
||||
super(donor);
|
||||
this.receiptNeeded = donor.receiptNeeded;
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,8 @@ export class ResponseRunner extends ResponseParticipant {
|
||||
*/
|
||||
public constructor(runner: Runner) {
|
||||
super(runner);
|
||||
this.distance = runner.scans.filter(scan => { scan.valid === true }).reduce((sum, current) => sum + current.distance, 0);
|
||||
if (!runner.scans) { this.distance = 0 }
|
||||
else { this.distance = runner.validScans.reduce((sum, current) => sum + current.distance, 0); }
|
||||
this.group = runner.group;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {
|
||||
IsArray,
|
||||
IsNotEmpty,
|
||||
IsObject
|
||||
|
||||
IsObject,
|
||||
IsOptional
|
||||
} from "class-validator";
|
||||
import { Address } from '../entities/Address';
|
||||
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
|
||||
@@ -17,7 +18,7 @@ export class ResponseRunnerOrganisation extends ResponseRunnerGroup {
|
||||
* The runnerOrganisation's address.
|
||||
*/
|
||||
@IsObject()
|
||||
@IsNotEmpty()
|
||||
@IsOptional()
|
||||
address?: Address;
|
||||
|
||||
/**
|
||||
|
||||
46
src/models/responses/ResponseScan.ts
Normal file
46
src/models/responses/ResponseScan.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { IsBoolean, IsInt, IsNotEmpty, IsPositive } from "class-validator";
|
||||
import { Scan } from '../entities/Scan';
|
||||
import { ResponseRunner } from './ResponseRunner';
|
||||
|
||||
/**
|
||||
* Defines the scan response.
|
||||
*/
|
||||
export class ResponseScan {
|
||||
/**
|
||||
* The scans's id.
|
||||
*/
|
||||
@IsInt()
|
||||
id: number;;
|
||||
|
||||
/**
|
||||
* The scan's associated runner.
|
||||
* This is important to link ran distances to runners.
|
||||
*/
|
||||
@IsNotEmpty()
|
||||
runner: ResponseRunner;
|
||||
|
||||
/**
|
||||
* Is the scan valid (for fraud reasons).
|
||||
* The determination of validity will work differently for every child class.
|
||||
*/
|
||||
@IsBoolean()
|
||||
valid: boolean = true;
|
||||
|
||||
/**
|
||||
* The scans's length/distance in meters.
|
||||
*/
|
||||
@IsInt()
|
||||
@IsPositive()
|
||||
distance: number;
|
||||
|
||||
/**
|
||||
* Creates a ResponseScan object from a scan.
|
||||
* @param scan The scan the response shall be build for.
|
||||
*/
|
||||
public constructor(scan: Scan) {
|
||||
this.id = scan.id;
|
||||
this.runner = scan.runner.toResponse();
|
||||
this.distance = scan.distance;
|
||||
this.valid = scan.valid;
|
||||
}
|
||||
}
|
||||
70
src/models/responses/ResponseScanStation.ts
Normal file
70
src/models/responses/ResponseScanStation.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
|
||||
IsBoolean,
|
||||
IsInt,
|
||||
|
||||
IsNotEmpty,
|
||||
|
||||
IsObject,
|
||||
|
||||
IsOptional,
|
||||
IsString
|
||||
} from "class-validator";
|
||||
import { ScanStation } from '../entities/ScanStation';
|
||||
import { ResponseTrack } from './ResponseTrack';
|
||||
|
||||
/**
|
||||
* Defines the statsClient response.
|
||||
*/
|
||||
export class ResponseScanStation {
|
||||
/**
|
||||
* The client's id.
|
||||
*/
|
||||
@IsInt()
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* The client's description.
|
||||
*/
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
description?: string;
|
||||
|
||||
/**
|
||||
* The client's api key.
|
||||
* Only visible on creation or regeneration.
|
||||
*/
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
key: string;
|
||||
|
||||
/**
|
||||
* The client's api key prefix.
|
||||
*/
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
prefix: string;
|
||||
|
||||
@IsObject()
|
||||
@IsNotEmpty()
|
||||
track: ResponseTrack;
|
||||
|
||||
/**
|
||||
* Is this station enabled?
|
||||
*/
|
||||
@IsBoolean()
|
||||
enabled?: boolean = true;
|
||||
|
||||
/**
|
||||
* Creates a ResponseStatsClient object from a statsClient.
|
||||
* @param client The statsClient the response shall be build for.
|
||||
*/
|
||||
public constructor(station: ScanStation) {
|
||||
this.id = station.id;
|
||||
this.description = station.description;
|
||||
this.prefix = station.prefix;
|
||||
this.key = "Only visible on creation.";
|
||||
this.track = station.track;
|
||||
this.enabled = station.enabled;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { IsInt, IsString } from "class-validator";
|
||||
import { IsInt, IsOptional, IsString } from "class-validator";
|
||||
import { TrackLapTimeCantBeNegativeError } from '../../errors/TrackErrors';
|
||||
import { Track } from '../entities/Track';
|
||||
|
||||
/**
|
||||
@@ -23,6 +24,14 @@ export class ResponseTrack {
|
||||
@IsInt()
|
||||
distance: number;
|
||||
|
||||
/**
|
||||
* The minimum time a runner should take to run a lap on this track (in seconds).
|
||||
* Will be used for fraud detection.
|
||||
*/
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
minimumLapTime?: number;
|
||||
|
||||
/**
|
||||
* Creates a ResponseTrack object from a track.
|
||||
* @param track The track the response shall be build for.
|
||||
@@ -31,5 +40,9 @@ export class ResponseTrack {
|
||||
this.id = track.id;
|
||||
this.name = track.name;
|
||||
this.distance = track.distance;
|
||||
this.minimumLapTime = track.minimumLapTime;
|
||||
if (this.minimumLapTime < 0) {
|
||||
throw new TrackLapTimeCantBeNegativeError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
48
src/models/responses/ResponseTrackScan.ts
Normal file
48
src/models/responses/ResponseTrackScan.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { IsDateString, IsNotEmpty } from "class-validator";
|
||||
import { RunnerCard } from '../entities/RunnerCard';
|
||||
import { ScanStation } from '../entities/ScanStation';
|
||||
import { TrackScan } from '../entities/TrackScan';
|
||||
import { ResponseScan } from './ResponseScan';
|
||||
import { ResponseTrack } from './ResponseTrack';
|
||||
|
||||
/**
|
||||
* Defines the trackScan response.
|
||||
*/
|
||||
export class ResponseTrackScan extends ResponseScan {
|
||||
/**
|
||||
* The scan's associated track.
|
||||
*/
|
||||
@IsNotEmpty()
|
||||
track: ResponseTrack;
|
||||
|
||||
/**
|
||||
* The runnerCard associated with the scan.
|
||||
*/
|
||||
@IsNotEmpty()
|
||||
card: RunnerCard;
|
||||
|
||||
/**
|
||||
* The scanning station that created the scan.
|
||||
*/
|
||||
@IsNotEmpty()
|
||||
station: ScanStation;
|
||||
|
||||
/**
|
||||
* The scan's creation timestamp.
|
||||
*/
|
||||
@IsDateString()
|
||||
@IsNotEmpty()
|
||||
timestamp: string;
|
||||
|
||||
/**
|
||||
* Creates a ResponseTrackScan object from a scan.
|
||||
* @param scan The trackSscan the response shall be build for.
|
||||
*/
|
||||
public constructor(scan: TrackScan) {
|
||||
super(scan);
|
||||
this.track = new ResponseTrack(scan.track);
|
||||
this.card = scan.card;
|
||||
this.station = scan.station;
|
||||
this.timestamp = scan.timestamp;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
IsOptional,
|
||||
IsString
|
||||
} from "class-validator";
|
||||
import { Permission } from '../entities/Permission';
|
||||
import { User } from '../entities/User';
|
||||
import { UserGroup } from '../entities/UserGroup';
|
||||
import { ResponsePrincipal } from './ResponsePrincipal';
|
||||
@@ -57,11 +56,10 @@ export class ResponseUser extends ResponsePrincipal {
|
||||
enabled: boolean = true;
|
||||
|
||||
/**
|
||||
* The user's profile pic.
|
||||
* The user's profile pic (or rather a url pointing to it).
|
||||
*/
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
profilePic?: string;
|
||||
profilePic: string;
|
||||
|
||||
/**
|
||||
* The groups that the user is a part of.
|
||||
@@ -75,7 +73,7 @@ export class ResponseUser extends ResponsePrincipal {
|
||||
*/
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
permissions: Permission[];
|
||||
permissions: string[];
|
||||
|
||||
/**
|
||||
* Creates a ResponseUser object from a user.
|
||||
@@ -92,6 +90,7 @@ export class ResponseUser extends ResponsePrincipal {
|
||||
this.enabled = user.enabled;
|
||||
this.profilePic = user.profilePic;
|
||||
this.groups = user.groups;
|
||||
this.permissions = user.permissions;
|
||||
this.permissions = user.allPermissions;
|
||||
this.groups.forEach(function (g) { delete g.permissions });
|
||||
}
|
||||
}
|
||||
|
||||
94
src/tests/donors/donor_add.spec.ts
Normal file
94
src/tests/donors/donor_add.spec.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import axios from 'axios';
|
||||
import { config } from '../../config';
|
||||
const base = "http://localhost:" + config.internal_port
|
||||
|
||||
let access_token;
|
||||
let axios_config;
|
||||
|
||||
beforeAll(async () => {
|
||||
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
|
||||
access_token = res.data["access_token"];
|
||||
axios_config = {
|
||||
headers: { "authorization": "Bearer " + access_token },
|
||||
validateStatus: undefined
|
||||
};
|
||||
});
|
||||
|
||||
// ---------------
|
||||
describe('POST /api/donors with errors', () => {
|
||||
it('creating a new donor without any parameters should return 400', async () => {
|
||||
const res1 = await axios.post(base + '/api/donors', null, axios_config);
|
||||
expect(res1.status).toEqual(400);
|
||||
expect(res1.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('creating a new donor without a last name should return 400', async () => {
|
||||
const res2 = await axios.post(base + '/api/donors', {
|
||||
"firstname": "first",
|
||||
"middlename": "middle"
|
||||
}, axios_config);
|
||||
expect(res2.status).toEqual(400);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('creating a new donor with a invalid address should return 404', async () => {
|
||||
const res2 = await axios.post(base + '/api/donors', {
|
||||
"firstname": "first",
|
||||
"middlename": "middle",
|
||||
"lastname": "last",
|
||||
"address": 0
|
||||
}, axios_config);
|
||||
expect(res2.status).toEqual(404);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('creating a new donor with a invalid phone number should return 400', async () => {
|
||||
const res2 = await axios.post(base + '/api/donors', {
|
||||
"firstname": "first",
|
||||
"middlename": "middle",
|
||||
"lastname": "last",
|
||||
"phone": "123"
|
||||
}, axios_config);
|
||||
expect(res2.status).toEqual(400);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('creating a new donor with a invalid mail address should return 400', async () => {
|
||||
const res2 = await axios.post(base + '/api/donors', {
|
||||
"firstname": "string",
|
||||
"middlename": "string",
|
||||
"lastname": "string",
|
||||
"phone": null,
|
||||
"email": "123",
|
||||
}, axios_config);
|
||||
expect(res2.status).toEqual(400);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('creating a new donor without an address but with receiptNeeded=true 406', async () => {
|
||||
const res2 = await axios.post(base + '/api/donors', {
|
||||
"firstname": "string",
|
||||
"middlename": "string",
|
||||
"lastname": "string",
|
||||
"receiptNeeded": true
|
||||
}, axios_config);
|
||||
expect(res2.status).toEqual(406);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('POST /api/donors working', () => {
|
||||
it('creating a new donor with only needed params should return 200', async () => {
|
||||
const res2 = await axios.post(base + '/api/donors', {
|
||||
"firstname": "first",
|
||||
"lastname": "last"
|
||||
}, axios_config);
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('creating a new donor with all non-relationship optional params should return 200', async () => {
|
||||
const res3 = await axios.post(base + '/api/donors', {
|
||||
"firstname": "first",
|
||||
"middlename": "middle",
|
||||
"lastname": "last",
|
||||
"receiptNeeded": false
|
||||
}, axios_config);
|
||||
expect(res3.status).toEqual(200);
|
||||
expect(res3.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
47
src/tests/donors/donor_delete.spec.ts
Normal file
47
src/tests/donors/donor_delete.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import axios from 'axios';
|
||||
import { config } from '../../config';
|
||||
const base = "http://localhost:" + config.internal_port
|
||||
|
||||
let access_token;
|
||||
let axios_config;
|
||||
|
||||
beforeAll(async () => {
|
||||
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
|
||||
access_token = res.data["access_token"];
|
||||
axios_config = {
|
||||
headers: { "authorization": "Bearer " + access_token },
|
||||
validateStatus: undefined
|
||||
};
|
||||
});
|
||||
|
||||
describe('adding + deletion (non-existant)', () => {
|
||||
it('delete', async () => {
|
||||
const res2 = await axios.delete(base + '/api/donors/0', axios_config);
|
||||
expect(res2.status).toEqual(204);
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('add+delete', () => {
|
||||
let added_donor;
|
||||
it('creating a new donor with only needed params should return 200', async () => {
|
||||
const res2 = await axios.post(base + '/api/donors', {
|
||||
"firstname": "first",
|
||||
"lastname": "last"
|
||||
}, axios_config);
|
||||
added_donor = res2.data;
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('delete donor', async () => {
|
||||
const res3 = await axios.delete(base + '/api/donors/' + added_donor.id, axios_config);
|
||||
expect(res3.status).toEqual(200);
|
||||
expect(res3.headers['content-type']).toContain("application/json")
|
||||
let deleted_runner = res3.data
|
||||
expect(deleted_runner).toEqual(added_donor);
|
||||
});
|
||||
it('check if donor really was deleted', async () => {
|
||||
const res4 = await axios.get(base + '/api/donors/' + added_donor.id, axios_config);
|
||||
expect(res4.status).toEqual(404);
|
||||
expect(res4.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
57
src/tests/donors/donor_get.spec.ts
Normal file
57
src/tests/donors/donor_get.spec.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import axios from 'axios';
|
||||
import { config } from '../../config';
|
||||
const base = "http://localhost:" + config.internal_port
|
||||
let access_token;
|
||||
let axios_config;
|
||||
|
||||
beforeAll(async () => {
|
||||
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
|
||||
access_token = res.data["access_token"];
|
||||
axios_config = {
|
||||
headers: { "authorization": "Bearer " + access_token },
|
||||
validateStatus: undefined
|
||||
};
|
||||
});
|
||||
|
||||
describe('GET /api/donors', () => {
|
||||
it('basic get should return 200', async () => {
|
||||
const res = await axios.get(base + '/api/donors', axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('GET /api/donors/0', () => {
|
||||
it('basic get should return 404', async () => {
|
||||
const res = await axios.get(base + '/api/donors/0', axios_config);
|
||||
expect(res.status).toEqual(404);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('GET /api/donors after adding', () => {
|
||||
let added_donor;
|
||||
it('creating a new donor with only needed params should return 200', async () => {
|
||||
const res2 = await axios.post(base + '/api/donors', {
|
||||
"firstname": "first",
|
||||
"lastname": "last"
|
||||
}, axios_config);
|
||||
added_donor = res2.data;
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('explicit get should return 200', async () => {
|
||||
const res3 = await axios.get(base + '/api/donors/' + added_donor.id, axios_config);
|
||||
expect(res3.status).toEqual(200);
|
||||
expect(res3.headers['content-type']).toContain("application/json")
|
||||
let gotten_donor = res3.data
|
||||
expect(gotten_donor).toEqual(added_donor);
|
||||
});
|
||||
it('get from all runners should return 200', async () => {
|
||||
const res4 = await axios.get(base + '/api/donors/', axios_config);
|
||||
expect(res4.status).toEqual(200);
|
||||
expect(res4.headers['content-type']).toContain("application/json")
|
||||
let gotten_donors = res4.data
|
||||
expect(gotten_donors).toContainEqual(added_donor);
|
||||
});
|
||||
});
|
||||
75
src/tests/donors/donor_update.spec.ts
Normal file
75
src/tests/donors/donor_update.spec.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import axios from 'axios';
|
||||
import { config } from '../../config';
|
||||
const base = "http://localhost:" + config.internal_port
|
||||
|
||||
let access_token;
|
||||
let axios_config;
|
||||
|
||||
beforeAll(async () => {
|
||||
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
|
||||
access_token = res.data["access_token"];
|
||||
axios_config = {
|
||||
headers: { "authorization": "Bearer " + access_token },
|
||||
validateStatus: undefined
|
||||
};
|
||||
});
|
||||
|
||||
describe('Update donor name after adding', () => {
|
||||
let added_donor;
|
||||
it('creating a new runner with only needed params should return 200', async () => {
|
||||
const res2 = await axios.post(base + '/api/donors', {
|
||||
"firstname": "first",
|
||||
"lastname": "last"
|
||||
}, axios_config);
|
||||
added_donor = res2.data;
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('valid update should return 200', async () => {
|
||||
let donor_copy = added_donor
|
||||
donor_copy.firstname = "second"
|
||||
const res3 = await axios.put(base + '/api/donors/' + added_donor.id, donor_copy, axios_config);
|
||||
expect(res3.status).toEqual(200);
|
||||
expect(res3.headers['content-type']).toContain("application/json")
|
||||
let updated_donor = res3.data
|
||||
expect(updated_donor).toEqual(donor_copy);
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('Update donor id after adding(should fail)', () => {
|
||||
let added_donor;
|
||||
it('creating a new donor with only needed params should return 200', async () => {
|
||||
const res2 = await axios.post(base + '/api/donors', {
|
||||
"firstname": "first",
|
||||
"lastname": "last"
|
||||
}, axios_config);
|
||||
added_donor = res2.data;
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('invalid update should return 406', async () => {
|
||||
added_donor.id++;
|
||||
const res3 = await axios.put(base + '/api/donors/' + (added_donor.id - 1), added_donor, axios_config);
|
||||
expect(res3.status).toEqual(406);
|
||||
expect(res3.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('Update donor without address but receiptNeeded=true should fail', () => {
|
||||
let added_donor;
|
||||
it('creating a new donor with only needed params should return 200', async () => {
|
||||
const res2 = await axios.post(base + '/api/donors', {
|
||||
"firstname": "first",
|
||||
"lastname": "last",
|
||||
}, axios_config);
|
||||
added_donor = res2.data;
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('invalid update should return 406', async () => {
|
||||
added_donor.receiptNeeded = true;
|
||||
const res3 = await axios.put(base + '/api/donors/' + added_donor.id, added_donor, axios_config);
|
||||
expect(res3.status).toEqual(406);
|
||||
expect(res3.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
231
src/tests/scans/scans_add.spec.ts
Normal file
231
src/tests/scans/scans_add.spec.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
import axios from 'axios';
|
||||
import { config } from '../../config';
|
||||
const base = "http://localhost:" + config.internal_port
|
||||
|
||||
let access_token;
|
||||
let axios_config;
|
||||
|
||||
beforeAll(async () => {
|
||||
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
|
||||
access_token = res.data["access_token"];
|
||||
axios_config = {
|
||||
headers: { "authorization": "Bearer " + access_token },
|
||||
validateStatus: undefined
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
describe('POST /api/scans illegally', () => {
|
||||
let added_org;
|
||||
let added_runner;
|
||||
it('creating a new org with just a name should return 200', async () => {
|
||||
const res1 = await axios.post(base + '/api/organisations', {
|
||||
"name": "test123"
|
||||
}, axios_config);
|
||||
added_org = res1.data
|
||||
expect(res1.status).toEqual(200);
|
||||
expect(res1.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('creating a new runner with only needed params should return 200', async () => {
|
||||
const res2 = await axios.post(base + '/api/runners', {
|
||||
"firstname": "first",
|
||||
"lastname": "last",
|
||||
"group": added_org.id
|
||||
}, axios_config);
|
||||
added_runner = res2.data;
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('no input should return 400', async () => {
|
||||
const res = await axios.post(base + '/api/scans', null, axios_config);
|
||||
expect(res.status).toEqual(400);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('no distance should return 400', async () => {
|
||||
const res = await axios.post(base + '/api/scans', {
|
||||
"runner": added_runner.id
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(400);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('illegal distance input should return 400', async () => {
|
||||
const res = await axios.post(base + '/api/scans', {
|
||||
"runner": added_runner.id,
|
||||
"distance": -1
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(400);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('invalid runner input should return 404', async () => {
|
||||
const res = await axios.post(base + '/api/scans', {
|
||||
"runner": 999999999999999999999999,
|
||||
"distance": 100
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(404);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('POST /api/scans successfully', () => {
|
||||
let added_org;
|
||||
let added_runner;
|
||||
it('creating a new org with just a name should return 200', async () => {
|
||||
const res1 = await axios.post(base + '/api/organisations', {
|
||||
"name": "test123"
|
||||
}, axios_config);
|
||||
added_org = res1.data
|
||||
expect(res1.status).toEqual(200);
|
||||
expect(res1.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('creating a new runner with only needed params should return 200', async () => {
|
||||
const res2 = await axios.post(base + '/api/runners', {
|
||||
"firstname": "first",
|
||||
"lastname": "last",
|
||||
"group": added_org.id
|
||||
}, axios_config);
|
||||
delete res2.data.group;
|
||||
added_runner = res2.data;
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('creating a scan with the minimum amount of parameters should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/scans', {
|
||||
"runner": added_runner.id,
|
||||
"distance": 200
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
delete res.data.id;
|
||||
expect(res.data).toEqual({
|
||||
"runner": added_runner,
|
||||
"distance": 200,
|
||||
"valid": true
|
||||
});
|
||||
});
|
||||
it('creating a valid scan should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/scans', {
|
||||
"runner": added_runner.id,
|
||||
"distance": 200,
|
||||
"valid": true
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
delete res.data.id;
|
||||
expect(res.data).toEqual({
|
||||
"runner": added_runner,
|
||||
"distance": 200,
|
||||
"valid": true
|
||||
});
|
||||
});
|
||||
it('creating a invalid scan should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/scans', {
|
||||
"runner": added_runner.id,
|
||||
"distance": 200,
|
||||
"valid": false
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
delete res.data.id;
|
||||
expect(res.data).toEqual({
|
||||
"runner": added_runner,
|
||||
"distance": 200,
|
||||
"valid": false
|
||||
});
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('POST /api/scans successfully via scan station', () => {
|
||||
let added_org;
|
||||
let added_runner;
|
||||
let added_track;
|
||||
let added_station;
|
||||
it('creating a new org with just a name should return 200', async () => {
|
||||
const res1 = await axios.post(base + '/api/organisations', {
|
||||
"name": "test123"
|
||||
}, axios_config);
|
||||
added_org = res1.data
|
||||
expect(res1.status).toEqual(200);
|
||||
expect(res1.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('creating a new runner with only needed params should return 200', async () => {
|
||||
const res2 = await axios.post(base + '/api/runners', {
|
||||
"firstname": "first",
|
||||
"lastname": "last",
|
||||
"group": added_org.id
|
||||
}, axios_config);
|
||||
delete res2.data.group;
|
||||
added_runner = res2.data;
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('creating a track with the minimum amount of parameters should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/tracks', {
|
||||
"name": "testtrack",
|
||||
"distance": 200,
|
||||
}, axios_config);
|
||||
added_track = res.data;
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
});
|
||||
it('creating a station with minimum parameters should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/stations', {
|
||||
"track": added_track.id
|
||||
}, axios_config);
|
||||
added_station = res.data;
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
});
|
||||
it('creating a scan with the minimum amount of parameters should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/scans', {
|
||||
"runner": added_runner.id,
|
||||
"distance": 200
|
||||
}, {
|
||||
headers: { "authorization": "Bearer " + added_station.key },
|
||||
validateStatus: undefined
|
||||
});
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
delete res.data.id;
|
||||
expect(res.data).toEqual({
|
||||
"runner": added_runner,
|
||||
"distance": 200,
|
||||
"valid": true
|
||||
});
|
||||
});
|
||||
it('creating a valid scan should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/scans', {
|
||||
"runner": added_runner.id,
|
||||
"distance": 200,
|
||||
"valid": true
|
||||
}, {
|
||||
headers: { "authorization": "Bearer " + added_station.key },
|
||||
validateStatus: undefined
|
||||
});
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
delete res.data.id;
|
||||
expect(res.data).toEqual({
|
||||
"runner": added_runner,
|
||||
"distance": 200,
|
||||
"valid": true
|
||||
});
|
||||
});
|
||||
it('creating a invalid scan should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/scans', {
|
||||
"runner": added_runner.id,
|
||||
"distance": 200,
|
||||
"valid": false
|
||||
}, {
|
||||
headers: { "authorization": "Bearer " + added_station.key },
|
||||
validateStatus: undefined
|
||||
});
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
delete res.data.id;
|
||||
expect(res.data).toEqual({
|
||||
"runner": added_runner,
|
||||
"distance": 200,
|
||||
"valid": false
|
||||
});
|
||||
});
|
||||
});
|
||||
68
src/tests/scans/scans_delete.spec.ts
Normal file
68
src/tests/scans/scans_delete.spec.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import axios from 'axios';
|
||||
import { config } from '../../config';
|
||||
const base = "http://localhost:" + config.internal_port
|
||||
|
||||
let access_token;
|
||||
let axios_config;
|
||||
|
||||
beforeAll(async () => {
|
||||
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
|
||||
access_token = res.data["access_token"];
|
||||
axios_config = {
|
||||
headers: { "authorization": "Bearer " + access_token },
|
||||
validateStatus: undefined
|
||||
};
|
||||
});
|
||||
|
||||
// ---------------
|
||||
|
||||
describe('DELETE scan', () => {
|
||||
let added_org;
|
||||
let added_runner;
|
||||
let added_scan;
|
||||
it('creating a new org with just a name should return 200', async () => {
|
||||
const res1 = await axios.post(base + '/api/organisations', {
|
||||
"name": "test123"
|
||||
}, axios_config);
|
||||
added_org = res1.data
|
||||
expect(res1.status).toEqual(200);
|
||||
expect(res1.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('creating a new runner with only needed params should return 200', async () => {
|
||||
const res2 = await axios.post(base + '/api/runners', {
|
||||
"firstname": "first",
|
||||
"lastname": "last",
|
||||
"group": added_org.id
|
||||
}, axios_config);
|
||||
added_runner = res2.data;
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('correct distance and runner input should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/scans', {
|
||||
"runner": added_runner.id,
|
||||
"distance": 1000
|
||||
}, axios_config);
|
||||
added_scan = res.data;
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('delete scan', async () => {
|
||||
const res2 = await axios.delete(base + '/api/scans/' + added_scan.id, axios_config);
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
expect(res2.data).toEqual(added_scan);
|
||||
});
|
||||
it('check if scan really was deleted', async () => {
|
||||
const res3 = await axios.get(base + '/api/scans/' + added_scan.id, axios_config);
|
||||
expect(res3.status).toEqual(404);
|
||||
expect(res3.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('DELETE scan (non-existant)', () => {
|
||||
it('delete', async () => {
|
||||
const res2 = await axios.delete(base + '/api/scans/0', axios_config);
|
||||
expect(res2.status).toEqual(204);
|
||||
});
|
||||
});
|
||||
69
src/tests/scans/scans_get.spec.ts
Normal file
69
src/tests/scans/scans_get.spec.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import axios from 'axios';
|
||||
import { config } from '../../config';
|
||||
const base = "http://localhost:" + config.internal_port
|
||||
|
||||
let access_token;
|
||||
let axios_config;
|
||||
|
||||
beforeAll(async () => {
|
||||
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
|
||||
access_token = res.data["access_token"];
|
||||
axios_config = {
|
||||
headers: { "authorization": "Bearer " + access_token },
|
||||
validateStatus: undefined
|
||||
};
|
||||
});
|
||||
|
||||
describe('GET /api/scans sucessfully', () => {
|
||||
it('basic get should return 200', async () => {
|
||||
const res = await axios.get(base + '/api/scans', axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('GET /api/scans illegally', () => {
|
||||
it('get for non-existant track should return 404', async () => {
|
||||
const res = await axios.get(base + '/api/scans/-1', axios_config);
|
||||
expect(res.status).toEqual(404);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('adding + getting scans', () => {
|
||||
let added_org;
|
||||
let added_runner;
|
||||
let added_scan;
|
||||
it('creating a new org with just a name should return 200', async () => {
|
||||
const res1 = await axios.post(base + '/api/organisations', {
|
||||
"name": "test123"
|
||||
}, axios_config);
|
||||
added_org = res1.data
|
||||
expect(res1.status).toEqual(200);
|
||||
expect(res1.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('creating a new runner with only needed params should return 200', async () => {
|
||||
const res2 = await axios.post(base + '/api/runners', {
|
||||
"firstname": "first",
|
||||
"lastname": "last",
|
||||
"group": added_org.id
|
||||
}, axios_config);
|
||||
added_runner = res2.data;
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('correct distance and runner input should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/scans', {
|
||||
"runner": added_runner.id,
|
||||
"distance": 1000
|
||||
}, axios_config);
|
||||
added_scan = res.data;
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('check if scans was added (no parameter validation)', async () => {
|
||||
const res = await axios.get(base + '/api/scans/' + added_scan.id, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
});
|
||||
});
|
||||
174
src/tests/scans/scans_update.spec.ts
Normal file
174
src/tests/scans/scans_update.spec.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import axios from 'axios';
|
||||
import { config } from '../../config';
|
||||
const base = "http://localhost:" + config.internal_port
|
||||
|
||||
let access_token;
|
||||
let axios_config;
|
||||
|
||||
beforeAll(async () => {
|
||||
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
|
||||
access_token = res.data["access_token"];
|
||||
axios_config = {
|
||||
headers: { "authorization": "Bearer " + access_token },
|
||||
validateStatus: undefined
|
||||
};
|
||||
});
|
||||
|
||||
describe('adding + updating illegally', () => {
|
||||
let added_org;
|
||||
let added_runner;
|
||||
let added_scan;
|
||||
it('creating a new org with just a name should return 200', async () => {
|
||||
const res1 = await axios.post(base + '/api/organisations', {
|
||||
"name": "test123"
|
||||
}, axios_config);
|
||||
added_org = res1.data
|
||||
expect(res1.status).toEqual(200);
|
||||
expect(res1.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('creating a new runner with only needed params should return 200', async () => {
|
||||
const res2 = await axios.post(base + '/api/runners', {
|
||||
"firstname": "first",
|
||||
"lastname": "last",
|
||||
"group": added_org.id
|
||||
}, axios_config);
|
||||
delete res2.data.group;
|
||||
added_runner = res2.data;
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('creating a scan with the minimum amount of parameters should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/scans', {
|
||||
"runner": added_runner.id,
|
||||
"distance": 200
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
added_scan = res.data;
|
||||
});
|
||||
it('updating empty should return 400', async () => {
|
||||
const res2 = await axios.put(base + '/api/scans/' + added_scan.id, null, axios_config);
|
||||
expect(res2.status).toEqual(400);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('updating with wrong id should return 406', async () => {
|
||||
const res2 = await axios.put(base + '/api/scans/' + added_scan.id, {
|
||||
"id": added_scan.id + 1,
|
||||
"runner": added_runner.id,
|
||||
"distance": added_scan.distance
|
||||
}, axios_config);
|
||||
expect(res2.status).toEqual(406);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('update with negative distance should return 400', async () => {
|
||||
const res2 = await axios.put(base + '/api/scans/' + added_scan.id, {
|
||||
"id": added_scan.id,
|
||||
"runner": added_runner.id,
|
||||
"distance": -1
|
||||
}, axios_config);
|
||||
expect(res2.status).toEqual(400);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('update with invalid runner id should return 404', async () => {
|
||||
const res2 = await axios.put(base + '/api/scans/' + added_scan.id, {
|
||||
"id": added_scan.id,
|
||||
"runner": 9999999999999999999999999,
|
||||
"distance": 123
|
||||
}, axios_config);
|
||||
expect(res2.status).toEqual(404);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('adding + updating successfilly', () => {
|
||||
let added_org;
|
||||
let added_runner;
|
||||
let added_runner2;
|
||||
let added_scan;
|
||||
it('creating a new org with just a name should return 200', async () => {
|
||||
const res1 = await axios.post(base + '/api/organisations', {
|
||||
"name": "test123"
|
||||
}, axios_config);
|
||||
added_org = res1.data
|
||||
expect(res1.status).toEqual(200);
|
||||
expect(res1.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('creating a new runner with only needed params should return 200', async () => {
|
||||
const res2 = await axios.post(base + '/api/runners', {
|
||||
"firstname": "first",
|
||||
"lastname": "last",
|
||||
"group": added_org.id
|
||||
}, axios_config);
|
||||
delete res2.data.group;
|
||||
added_runner = res2.data;
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('creating a scan with the minimum amount of parameters should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/scans', {
|
||||
"runner": added_runner.id,
|
||||
"distance": 200
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
added_scan = res.data;
|
||||
});
|
||||
it('valid distance update should return 200', async () => {
|
||||
const res2 = await axios.put(base + '/api/scans/' + added_scan.id, {
|
||||
"id": added_scan.id,
|
||||
"runner": added_runner.id,
|
||||
"distance": 100
|
||||
}, axios_config);
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
expect(res2.data).toEqual({
|
||||
"id": added_scan.id,
|
||||
"runner": added_runner,
|
||||
"distance": 100,
|
||||
"valid": true
|
||||
|
||||
});
|
||||
});
|
||||
it('valid valid update should return 200', async () => {
|
||||
const res2 = await axios.put(base + '/api/scans/' + added_scan.id, {
|
||||
"id": added_scan.id,
|
||||
"runner": added_runner.id,
|
||||
"distance": 100,
|
||||
"valid": false
|
||||
}, axios_config);
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
expect(res2.data).toEqual({
|
||||
"id": added_scan.id,
|
||||
"runner": added_runner,
|
||||
"distance": 100,
|
||||
"valid": false
|
||||
});
|
||||
});
|
||||
it('creating a new runner with only needed params should return 200', async () => {
|
||||
const res2 = await axios.post(base + '/api/runners', {
|
||||
"firstname": "first",
|
||||
"lastname": "last",
|
||||
"group": added_org.id
|
||||
}, axios_config);
|
||||
delete res2.data.group;
|
||||
added_runner2 = res2.data;
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('valid runner update should return 200', async () => {
|
||||
const res2 = await axios.put(base + '/api/scans/' + added_scan.id, {
|
||||
"id": added_scan.id,
|
||||
"runner": added_runner2.id,
|
||||
"distance": added_scan.distance
|
||||
}, axios_config);
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json");
|
||||
expect(res2.data).toEqual({
|
||||
"id": added_scan.id,
|
||||
"runner": added_runner2,
|
||||
"distance": added_scan.distance,
|
||||
"valid": added_scan.valid
|
||||
});
|
||||
});
|
||||
});
|
||||
113
src/tests/scanstations/scanstations_add.spec.ts
Normal file
113
src/tests/scanstations/scanstations_add.spec.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import axios from 'axios';
|
||||
import { config } from '../../config';
|
||||
const base = "http://localhost:" + config.internal_port
|
||||
|
||||
let access_token;
|
||||
let axios_config;
|
||||
|
||||
beforeAll(async () => {
|
||||
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
|
||||
access_token = res.data["access_token"];
|
||||
axios_config = {
|
||||
headers: { "authorization": "Bearer " + access_token },
|
||||
validateStatus: undefined
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
describe('POST /api/stations illegally', () => {
|
||||
it('no track input should return 400', async () => {
|
||||
const res = await axios.post(base + '/api/stations', {
|
||||
"description": "string",
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(400);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('illegal track input should return 404', async () => {
|
||||
const res = await axios.post(base + '/api/stations', {
|
||||
"description": "string",
|
||||
"track": -1
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(400);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('POST /api/stations successfully', () => {
|
||||
let added_track;
|
||||
it('creating a track with the minimum amount of parameters should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/tracks', {
|
||||
"name": "testtrack",
|
||||
"distance": 200,
|
||||
}, axios_config);
|
||||
added_track = res.data;
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
});
|
||||
it('creating a station with minimum parameters should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/stations', {
|
||||
"track": added_track.id
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
delete res.data.id;
|
||||
delete res.data.prefix;
|
||||
delete res.data.key;
|
||||
expect(res.data).toEqual({
|
||||
"track": added_track,
|
||||
"description": null,
|
||||
"enabled": true
|
||||
});
|
||||
});
|
||||
it('creating a station with all parameters (optional set to true/empty) should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/stations', {
|
||||
"track": added_track.id,
|
||||
"enabled": true,
|
||||
"description": null
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
delete res.data.id;
|
||||
delete res.data.prefix;
|
||||
delete res.data.key;
|
||||
expect(res.data).toEqual({
|
||||
"track": added_track,
|
||||
"description": null,
|
||||
"enabled": true
|
||||
});
|
||||
});
|
||||
it('creating a disabled station with all parameters (optional set to true/empty) should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/stations', {
|
||||
"track": added_track.id,
|
||||
"enabled": false,
|
||||
"description": null
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
delete res.data.id;
|
||||
delete res.data.prefix;
|
||||
delete res.data.key;
|
||||
expect(res.data).toEqual({
|
||||
"track": added_track,
|
||||
"description": null,
|
||||
"enabled": false
|
||||
});
|
||||
});
|
||||
it('creating a station with all parameters (optional set) should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/stations', {
|
||||
"track": added_track.id,
|
||||
"enabled": true,
|
||||
"description": "test station for testing"
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
delete res.data.id;
|
||||
delete res.data.prefix;
|
||||
delete res.data.key;
|
||||
expect(res.data).toEqual({
|
||||
"track": added_track,
|
||||
"description": "test station for testing",
|
||||
"enabled": true
|
||||
});
|
||||
});
|
||||
});
|
||||
58
src/tests/scanstations/scanstations_delete.spec.ts
Normal file
58
src/tests/scanstations/scanstations_delete.spec.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import axios from 'axios';
|
||||
import { config } from '../../config';
|
||||
const base = "http://localhost:" + config.internal_port
|
||||
|
||||
let access_token;
|
||||
let axios_config;
|
||||
|
||||
beforeAll(async () => {
|
||||
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
|
||||
access_token = res.data["access_token"];
|
||||
axios_config = {
|
||||
headers: { "authorization": "Bearer " + access_token },
|
||||
validateStatus: undefined
|
||||
};
|
||||
});
|
||||
|
||||
// ---------------
|
||||
describe('DELETE station', () => {
|
||||
let added_track;
|
||||
let added_station;
|
||||
it('creating a track with the minimum amount of parameters should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/tracks', {
|
||||
"name": "testtrack",
|
||||
"distance": 200,
|
||||
}, axios_config);
|
||||
added_track = res.data;
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
});
|
||||
it('creating a station with minimum parameters should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/stations', {
|
||||
"track": added_track.id
|
||||
}, axios_config);
|
||||
added_station = res.data;
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
});
|
||||
it('delete station', async () => {
|
||||
const res2 = await axios.delete(base + '/api/stations/' + added_station.id, axios_config);
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
delete res2.data.key;
|
||||
delete added_station.key;
|
||||
expect(res2.data).toEqual(added_station);
|
||||
});
|
||||
it('check if station really was deleted', async () => {
|
||||
const res3 = await axios.get(base + '/api/stations/' + added_station.id, axios_config);
|
||||
expect(res3.status).toEqual(404);
|
||||
expect(res3.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('DELETE station (non-existant)', () => {
|
||||
it('delete', async () => {
|
||||
const res2 = await axios.delete(base + '/api/stations/0', axios_config);
|
||||
expect(res2.status).toEqual(204);
|
||||
});
|
||||
});
|
||||
59
src/tests/scanstations/scanstations_get.spec.ts
Normal file
59
src/tests/scanstations/scanstations_get.spec.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import axios from 'axios';
|
||||
import { config } from '../../config';
|
||||
const base = "http://localhost:" + config.internal_port
|
||||
|
||||
let access_token;
|
||||
let axios_config;
|
||||
|
||||
beforeAll(async () => {
|
||||
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
|
||||
access_token = res.data["access_token"];
|
||||
axios_config = {
|
||||
headers: { "authorization": "Bearer " + access_token },
|
||||
validateStatus: undefined
|
||||
};
|
||||
});
|
||||
|
||||
describe('GET /api/stations sucessfully', () => {
|
||||
it('basic get should return 200', async () => {
|
||||
const res = await axios.get(base + '/api/stations', axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('GET /api/stations illegally', () => {
|
||||
it('get for non-existant track should return 404', async () => {
|
||||
const res = await axios.get(base + '/api/stations/-1', axios_config);
|
||||
expect(res.status).toEqual(404);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('adding + getting stations', () => {
|
||||
let added_track;
|
||||
let added_station;
|
||||
it('creating a track should return 200', async () => {
|
||||
const res1 = await axios.post(base + '/api/tracks', {
|
||||
"name": "test123",
|
||||
"distance": 123
|
||||
}, axios_config);
|
||||
added_track = res1.data
|
||||
expect(res1.status).toEqual(200);
|
||||
expect(res1.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('correct description and track input for station creation return 200', async () => {
|
||||
const res = await axios.post(base + '/api/stations', {
|
||||
"track": added_track.id,
|
||||
"description": "I am but a simple test."
|
||||
}, axios_config);
|
||||
added_station = res.data;
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('check if station was added (no parameter validation)', async () => {
|
||||
const res = await axios.get(base + '/api/stations/' + added_station.id, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
});
|
||||
});
|
||||
103
src/tests/scanstations/scanstations_update.spec.ts
Normal file
103
src/tests/scanstations/scanstations_update.spec.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import axios from 'axios';
|
||||
import { config } from '../../config';
|
||||
const base = "http://localhost:" + config.internal_port
|
||||
|
||||
let access_token;
|
||||
let axios_config;
|
||||
|
||||
beforeAll(async () => {
|
||||
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
|
||||
access_token = res.data["access_token"];
|
||||
axios_config = {
|
||||
headers: { "authorization": "Bearer " + access_token },
|
||||
validateStatus: undefined
|
||||
};
|
||||
});
|
||||
|
||||
describe('adding + updating illegally', () => {
|
||||
let added_track;
|
||||
let added_station;
|
||||
it('creating a track with the minimum amount of parameters should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/tracks', {
|
||||
"name": "testtrack",
|
||||
"distance": 200,
|
||||
}, axios_config);
|
||||
added_track = res.data;
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
});
|
||||
it('creating a station with minimum parameters should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/stations', {
|
||||
"track": added_track.id
|
||||
}, axios_config);
|
||||
added_station = res.data;
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
});
|
||||
it('updateing id should return 406', async () => {
|
||||
const res2 = await axios.put(base + '/api/stations/' + added_station.id, {
|
||||
"id": added_station.id + 1
|
||||
}, axios_config);
|
||||
expect(res2.status).toEqual(406);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('adding + updating successfilly', () => {
|
||||
let added_track;
|
||||
let added_station;
|
||||
it('creating a track with the minimum amount of parameters should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/tracks', {
|
||||
"name": "testtrack",
|
||||
"distance": 200,
|
||||
}, axios_config);
|
||||
added_track = res.data;
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
});
|
||||
it('creating a station with minimum parameters should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/stations', {
|
||||
"track": added_track.id
|
||||
}, axios_config);
|
||||
added_station = res.data;
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
});
|
||||
it('updateing nothing should return 200', async () => {
|
||||
const res = await axios.put(base + '/api/stations/' + added_station.id, {
|
||||
"id": added_station.id
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
delete res.data.key;
|
||||
delete added_station.key;
|
||||
expect(res.data).toEqual(added_station);
|
||||
});
|
||||
it('updateing description should return 200', async () => {
|
||||
const res = await axios.put(base + '/api/stations/' + added_station.id, {
|
||||
"id": added_station.id,
|
||||
"description": "Hello there! General stationi you're a scanning one."
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
expect(res.data.description).toEqual("Hello there! General stationi you're a scanning one.");
|
||||
});
|
||||
it('updateing enabled to false should return 200', async () => {
|
||||
const res = await axios.put(base + '/api/stations/' + added_station.id, {
|
||||
"id": added_station.id,
|
||||
"enabled": false
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
expect(res.data.enabled).toEqual(false);
|
||||
});
|
||||
it('updateing enabled to true should return 200', async () => {
|
||||
const res = await axios.put(base + '/api/stations/' + added_station.id, {
|
||||
"id": added_station.id,
|
||||
"enabled": true
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
expect(res.data.enabled).toEqual(true);
|
||||
});
|
||||
});
|
||||
@@ -1,116 +0,0 @@
|
||||
import axios from 'axios';
|
||||
import { config } from '../config';
|
||||
const base = "http://localhost:" + config.internal_port
|
||||
|
||||
let access_token;
|
||||
let axios_config;
|
||||
|
||||
beforeAll(async () => {
|
||||
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
|
||||
access_token = res.data["access_token"];
|
||||
axios_config = {
|
||||
headers: { "authorization": "Bearer " + access_token },
|
||||
validateStatus: undefined
|
||||
};
|
||||
});
|
||||
|
||||
describe('GET /api/tracks', () => {
|
||||
it('basic get should return 200', async () => {
|
||||
const res = await axios.get(base + '/api/tracks', axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('correct distance input should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/tracks', {
|
||||
"name": "string",
|
||||
"distance": 400
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('POST /api/tracks', () => {
|
||||
it('illegal distance input should return 400', async () => {
|
||||
const res = await axios.post(base + '/api/tracks', {
|
||||
"name": "string",
|
||||
"distance": -1
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(400);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('correct distance input should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/tracks', {
|
||||
"name": "string",
|
||||
"distance": 400
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('adding + getting tracks', () => {
|
||||
it('correct distance input should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/tracks', {
|
||||
"name": "string",
|
||||
"distance": 1000
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('check if track was added', async () => {
|
||||
const res = await axios.get(base + '/api/tracks', axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
let added_track = res.data[res.data.length - 1]
|
||||
delete added_track.id
|
||||
expect(added_track).toEqual({
|
||||
"name": "string",
|
||||
"distance": 1000
|
||||
})
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('adding + getting + updating', () => {
|
||||
let added_track_id
|
||||
it('correct distance input should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/tracks', {
|
||||
"name": "string",
|
||||
"distance": 1500
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('get should return 200', async () => {
|
||||
const res1 = await axios.get(base + '/api/tracks', axios_config);
|
||||
expect(res1.status).toEqual(200);
|
||||
expect(res1.headers['content-type']).toContain("application/json")
|
||||
let added_track = res1.data[res1.data.length - 1]
|
||||
added_track_id = added_track.id
|
||||
delete added_track.id
|
||||
expect(added_track).toEqual({
|
||||
"name": "string",
|
||||
"distance": 1500
|
||||
})
|
||||
})
|
||||
it('get should return 200', async () => {
|
||||
const res2 = await axios.put(base + '/api/tracks/' + added_track_id, {
|
||||
"id": added_track_id,
|
||||
"name": "apitrack",
|
||||
"distance": 5100
|
||||
}, axios_config);
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
})
|
||||
it('get should return 200', async () => {
|
||||
const res3 = await axios.get(base + '/api/tracks', axios_config);
|
||||
expect(res3.status).toEqual(200);
|
||||
expect(res3.headers['content-type']).toContain("application/json")
|
||||
let added_track2 = res3.data[res3.data.length - 1]
|
||||
delete added_track2.id
|
||||
expect(added_track2).toEqual({
|
||||
"name": "apitrack",
|
||||
"distance": 5100
|
||||
})
|
||||
});
|
||||
});
|
||||
90
src/tests/tracks/track_add.spec.ts
Normal file
90
src/tests/tracks/track_add.spec.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import axios from 'axios';
|
||||
import { config } from '../../config';
|
||||
const base = "http://localhost:" + config.internal_port
|
||||
|
||||
let access_token;
|
||||
let axios_config;
|
||||
|
||||
beforeAll(async () => {
|
||||
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
|
||||
access_token = res.data["access_token"];
|
||||
axios_config = {
|
||||
headers: { "authorization": "Bearer " + access_token },
|
||||
validateStatus: undefined
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
describe('POST /api/tracks illegally', () => {
|
||||
it('no distance input should return 400', async () => {
|
||||
const res = await axios.post(base + '/api/tracks', {
|
||||
"name": "string",
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(400);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('illegal distance input should return 400', async () => {
|
||||
const res = await axios.post(base + '/api/tracks', {
|
||||
"name": "string",
|
||||
"distance": -1
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(400);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('negative minimum lap time input should return 406', async () => {
|
||||
const res = await axios.post(base + '/api/tracks', {
|
||||
"name": "string",
|
||||
"distance": 200,
|
||||
"minimumLapTime": -1
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(406);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('POST /api/tracks successfully', () => {
|
||||
it('creating a track with the minimum amount of parameters should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/tracks', {
|
||||
"name": "testtrack",
|
||||
"distance": 200,
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
delete res.data.id;
|
||||
expect(res.data).toEqual({
|
||||
"name": "testtrack",
|
||||
"distance": 200,
|
||||
"minimumLapTime": null
|
||||
})
|
||||
});
|
||||
it('creating a track with all parameters (optional set to null) should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/tracks', {
|
||||
"name": "testtrack",
|
||||
"distance": 200,
|
||||
"minimumLapTime": null
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
delete res.data.id;
|
||||
expect(res.data).toEqual({
|
||||
"name": "testtrack",
|
||||
"distance": 200,
|
||||
"minimumLapTime": null
|
||||
})
|
||||
});
|
||||
it('creating a track with all parameters should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/tracks', {
|
||||
"name": "testtrack",
|
||||
"distance": 200,
|
||||
"minimumLapTime": 123
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
delete res.data.id;
|
||||
expect(res.data).toEqual({
|
||||
"name": "testtrack",
|
||||
"distance": 200,
|
||||
"minimumLapTime": 123
|
||||
})
|
||||
});
|
||||
});
|
||||
53
src/tests/tracks/track_delete.spec.ts
Normal file
53
src/tests/tracks/track_delete.spec.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import axios from 'axios';
|
||||
import { config } from '../../config';
|
||||
const base = "http://localhost:" + config.internal_port
|
||||
|
||||
let access_token;
|
||||
let axios_config;
|
||||
|
||||
beforeAll(async () => {
|
||||
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
|
||||
access_token = res.data["access_token"];
|
||||
axios_config = {
|
||||
headers: { "authorization": "Bearer " + access_token },
|
||||
validateStatus: undefined
|
||||
};
|
||||
});
|
||||
|
||||
// ---------------
|
||||
describe('DETELE track', () => {
|
||||
let added_track
|
||||
it('creating a track with the minimum amount of parameters should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/tracks', {
|
||||
"name": "testtrack",
|
||||
"distance": 200,
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
added_track = res.data
|
||||
});
|
||||
it('delete track', async () => {
|
||||
const res2 = await axios.delete(base + '/api/tracks/' + added_track.id, axios_config);
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
let deleted_track = res2.data
|
||||
delete deleted_track.id;
|
||||
expect(res2.data).toEqual({
|
||||
"name": "testtrack",
|
||||
"distance": 200,
|
||||
"minimumLapTime": null
|
||||
});
|
||||
});
|
||||
it('check if track really was deleted', async () => {
|
||||
const res3 = await axios.get(base + '/api/tracks/' + added_track.id, axios_config);
|
||||
expect(res3.status).toEqual(404);
|
||||
expect(res3.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('DELETE track (non-existant)', () => {
|
||||
it('delete', async () => {
|
||||
const res2 = await axios.delete(base + '/api/tracks/0', axios_config);
|
||||
expect(res2.status).toEqual(204);
|
||||
});
|
||||
});
|
||||
98
src/tests/tracks/track_update.spec.ts
Normal file
98
src/tests/tracks/track_update.spec.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import axios from 'axios';
|
||||
import { config } from '../../config';
|
||||
const base = "http://localhost:" + config.internal_port
|
||||
|
||||
let access_token;
|
||||
let axios_config;
|
||||
|
||||
beforeAll(async () => {
|
||||
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
|
||||
access_token = res.data["access_token"];
|
||||
axios_config = {
|
||||
headers: { "authorization": "Bearer " + access_token },
|
||||
validateStatus: undefined
|
||||
};
|
||||
});
|
||||
|
||||
describe('adding + updating illegally', () => {
|
||||
let added_track;
|
||||
it('correct distance input should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/tracks', {
|
||||
"name": "apitrack",
|
||||
"distance": 1500
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
added_track = res.data;
|
||||
});
|
||||
it('update with negative distance should return 400', async () => {
|
||||
const res2 = await axios.put(base + '/api/tracks/' + added_track.id, {
|
||||
"id": added_track.id,
|
||||
"name": "apitrack",
|
||||
"distance": -1
|
||||
}, axios_config);
|
||||
expect(res2.status).toEqual(400);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('update with negative laptime should return 406', async () => {
|
||||
const res2 = await axios.put(base + '/api/tracks/' + added_track.id, {
|
||||
"id": added_track.id,
|
||||
"name": "apitrack",
|
||||
"distance": 2,
|
||||
"minimumLapTime": -1
|
||||
}, axios_config);
|
||||
expect(res2.status).toEqual(406);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('adding + updating successfilly', () => {
|
||||
let added_track;
|
||||
it('correct distance input should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/tracks', {
|
||||
"name": "apitrack2",
|
||||
"distance": 1500
|
||||
}, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
added_track = res.data;
|
||||
});
|
||||
it('valid name change should return 200', async () => {
|
||||
const res2 = await axios.put(base + '/api/tracks/' + added_track.id, {
|
||||
"id": added_track.id,
|
||||
"name": "apitrackk",
|
||||
"distance": 1500
|
||||
}, axios_config);
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('valid distance change should return 200', async () => {
|
||||
const res2 = await axios.put(base + '/api/tracks/' + added_track.id, {
|
||||
"id": added_track.id,
|
||||
"name": "apitrack2",
|
||||
"distance": 5100
|
||||
}, axios_config);
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('valid laptime change (to a set number) should return 200', async () => {
|
||||
const res2 = await axios.put(base + '/api/tracks/' + added_track.id, {
|
||||
"id": added_track.id,
|
||||
"name": "apitrack2",
|
||||
"distance": 5100,
|
||||
"minimumLapTime": 3
|
||||
}, axios_config);
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('valid laptime change (to a set null) should return 200', async () => {
|
||||
const res2 = await axios.put(base + '/api/tracks/' + added_track.id, {
|
||||
"id": added_track.id,
|
||||
"name": "apitrack2",
|
||||
"distance": 5100,
|
||||
"minimumLapTime": null
|
||||
}, axios_config);
|
||||
expect(res2.status).toEqual(200);
|
||||
expect(res2.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
49
src/tests/tracks/tracks_get.spec.ts
Normal file
49
src/tests/tracks/tracks_get.spec.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import axios from 'axios';
|
||||
import { config } from '../../config';
|
||||
const base = "http://localhost:" + config.internal_port
|
||||
|
||||
let access_token;
|
||||
let axios_config;
|
||||
|
||||
beforeAll(async () => {
|
||||
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
|
||||
access_token = res.data["access_token"];
|
||||
axios_config = {
|
||||
headers: { "authorization": "Bearer " + access_token },
|
||||
validateStatus: undefined
|
||||
};
|
||||
});
|
||||
|
||||
describe('GET /api/tracks sucessfully', () => {
|
||||
it('basic get should return 200', async () => {
|
||||
const res = await axios.get(base + '/api/tracks', axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('GET /api/tracks illegally', () => {
|
||||
it('get for non-existant track should return 404', async () => {
|
||||
const res = await axios.get(base + '/api/tracks/-1', axios_config);
|
||||
expect(res.status).toEqual(404);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
});
|
||||
// ---------------
|
||||
describe('adding + getting tracks', () => {
|
||||
let added_track;
|
||||
it('correct distance input should return 200', async () => {
|
||||
const res = await axios.post(base + '/api/tracks', {
|
||||
"name": "string",
|
||||
"distance": 1000
|
||||
}, axios_config);
|
||||
added_track = res.data;
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json")
|
||||
});
|
||||
it('check if track was added (no parameter validation)', async () => {
|
||||
const res = await axios.get(base + '/api/tracks/' + added_track.id, axios_config);
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.headers['content-type']).toContain("application/json");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user