Merge branch 'dev' into feature/79-profile_pics
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				continuous-integration/drone/pr Build is failing
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	continuous-integration/drone/pr Build is failing
				
			This commit is contained in:
		
							
								
								
									
										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; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										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; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										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; | ||||
|     } | ||||
| } | ||||
| @@ -80,4 +80,11 @@ export class Address { | ||||
|    */ | ||||
|   @OneToMany(() => IAddressUser, addressUser => addressUser.address, { nullable: true }) | ||||
|   addressUsers: IAddressUser[]; | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   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"); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -32,4 +32,11 @@ export abstract class Donation { | ||||
|    * 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,6 @@ | ||||
| import { IsBoolean } from "class-validator"; | ||||
| import { ChildEntity, Column, OneToMany } from "typeorm"; | ||||
| import { ResponseDonor } from '../responses/ResponseDonor'; | ||||
| import { Donation } from './Donation'; | ||||
| import { Participant } from "./Participant"; | ||||
|  | ||||
| @@ -22,4 +23,11 @@ export class Donor extends Participant { | ||||
|  */ | ||||
|   @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"); | ||||
|   } | ||||
| } | ||||
| @@ -81,4 +81,11 @@ export class GroupContact implements IAddressUser { | ||||
|     */ | ||||
|   @OneToMany(() => RunnerGroup, group => group.contact, { nullable: true }) | ||||
|   groups: RunnerGroup[]; | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   public toResponse() { | ||||
|     return new Error("NotImplemented"); | ||||
|   } | ||||
| } | ||||
| @@ -12,4 +12,9 @@ export abstract class IAddressUser { | ||||
|  | ||||
|     @ManyToOne(() => Address, address => address.addressUsers, { nullable: true }) | ||||
|     address?: Address | ||||
|  | ||||
|     /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|     public abstract toResponse(); | ||||
| } | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import { | ||||
| } from "class-validator"; | ||||
| import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; | ||||
| import { config } from '../../config'; | ||||
| import { ResponseParticipant } from '../responses/ResponseParticipant'; | ||||
| import { Address } from "./Address"; | ||||
| import { IAddressUser } from './IAddressUser'; | ||||
|  | ||||
| @@ -74,4 +75,9 @@ export abstract class Participant implements IAddressUser { | ||||
|   @IsOptional() | ||||
|   @IsEmail() | ||||
|   email?: string; | ||||
|  | ||||
|   /** | ||||
|    * Turns this entity into it's response class. | ||||
|    */ | ||||
|   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"; | ||||
| @@ -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,5 +1,6 @@ | ||||
| import { IsInt, IsOptional } from "class-validator"; | ||||
| import { ChildEntity, ManyToOne, OneToMany } from "typeorm"; | ||||
| import { ResponseRunnerOrganisation } from '../responses/ResponseRunnerOrganisation'; | ||||
| import { Address } from './Address'; | ||||
| import { IAddressUser } from './IAddressUser'; | ||||
| import { Runner } from './Runner'; | ||||
| @@ -54,4 +55,11 @@ export class RunnerOrganisation extends RunnerGroup implements IAddressUser { | ||||
|   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); | ||||
|     } | ||||
| } | ||||
| @@ -6,6 +6,7 @@ import { | ||||
|   IsString | ||||
| } from "class-validator"; | ||||
| import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm"; | ||||
| import { ResponseTrack } from '../responses/ResponseTrack'; | ||||
| import { ScanStation } from "./ScanStation"; | ||||
| import { TrackScan } from "./TrackScan"; | ||||
|  | ||||
| @@ -61,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); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -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"); | ||||
|   } | ||||
| } | ||||
| @@ -10,5 +10,7 @@ export enum PermissionTarget { | ||||
|     USERGROUP = 'USERGROUP', | ||||
|     PERMISSION = 'PERMISSION', | ||||
|     STATSCLIENT = 'STATSCLIENT', | ||||
|     DONOR = 'DONOR' | ||||
|     DONOR = 'DONOR', | ||||
|     SCAN = 'SCAN', | ||||
|     STATION = 'STATION' | ||||
| } | ||||
| @@ -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; | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										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; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										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; | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user