Merge pull request 'feature/11-new_classes' (#15) from feature/11-new_classes into dev

Reviewed-on: #15
This commit is contained in:
Nicolai Ort 2020-12-02 17:48:16 +00:00
commit 7d9e003a6d
20 changed files with 992 additions and 3 deletions

87
src/models/Address.ts Normal file
View File

@ -0,0 +1,87 @@
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm";
import {
IsInt,
IsNotEmpty,
IsOptional,
IsPostalCode,
IsString,
} from "class-validator";
import { Participant } from "./Participant";
import { RunnerOrganisation } from "./RunnerOrganisation";
/**
* Defines a address (to be used for contact information).
*/
@Entity()
export class Address {
/**
* Autogenerated unique id (primary key).
*/
@PrimaryGeneratedColumn()
@IsOptional()
@IsInt()
id: number;
/**
* The address's description.
*/
@Column()
@IsString()
@IsOptional()
description?: string;
/**
* The address's first line.
* Containing the street and house number.
*/
@Column()
@IsString()
@IsNotEmpty()
address1: string;
/**
* The address's second line.
* Containing optional information.
*/
@Column()
@IsString()
@IsOptional()
address2?: string;
/**
* The address's postal code.
*/
@Column()
@IsString()
@IsNotEmpty()
@IsPostalCode("DE")
postalcode: string;
/**
* The address's city.
*/
@Column()
@IsString()
@IsNotEmpty()
city: string;
/**
* The address's country.
*/
@Column()
@IsString()
@IsNotEmpty()
country: string;
/**
* Used to link the address to participants.
*/
@OneToMany(() => Participant, participant => participant.address)
participants: Participant[];
/**
* Used to link the address to runner groups.
*/
@OneToMany(() => RunnerOrganisation, group => group.address)
groups: RunnerOrganisation[];
}

View File

@ -0,0 +1,41 @@
import { Entity, Column, ManyToOne } from "typeorm";
import { IsInt, IsNotEmpty, IsPositive,} from "class-validator";
import { Donation } from "./Donation";
import { Runner } from "./Runner";
/**
* Defines a distance based donation.
* Here people donate a certain amout per kilometer
*/
@Entity()
export class DistanceDonation extends Donation {
/**
* The runner associated.
*/
@IsNotEmpty()
@ManyToOne(() => Runner, runner => runner.distanceDonations)
runner: Runner;
/**
* The amount the donor set to be donated per kilometer that the runner ran.
*/
@Column()
@IsInt()
@IsPositive()
amountPerDistance: number;
/**
* The donation's amount in cents (or whatever your currency's smallest unit is.).
* The exact implementation may differ for each type of donation.
*/
@IsInt()
public get amount(): number {
let calculatedAmount = -1;
try {
calculatedAmount = this.amountPerDistance * this.runner.distance;
} catch (error) {
throw error;
}
return calculatedAmount;
}
}

35
src/models/Donation.ts Normal file
View File

@ -0,0 +1,35 @@
import { PrimaryGeneratedColumn, Column, ManyToOne, Entity } from "typeorm";
import {
IsInt,
IsNotEmpty,
IsOptional,
IsPositive,
} from "class-validator";
import { Participant } from "./Participant";
/**
* Defines the donation interface.
*/
@Entity()
export abstract class Donation {
/**
* Autogenerated unique id (primary key).
*/
@PrimaryGeneratedColumn()
@IsOptional()
@IsInt()
id: number;
/**
* The donations's donor.
*/
@IsNotEmpty()
@ManyToOne(() => Participant, donor => donor.donations)
donor: Participant;
/**
* The donation's amount in cents (or whatever your currency's smallest unit is.).
* The exact implementation may differ for each type of donation.
*/
abstract amount: number;
}

17
src/models/Donor.ts Normal file
View File

@ -0,0 +1,17 @@
import { Entity, Column } from "typeorm";
import { IsBoolean } from "class-validator";
import { Participant } from "./Participant";
/**
* Defines a donor.
*/
@Entity()
export class Donor extends Participant {
/**
* Does this donor need a receipt?.
* Default: false
*/
@Column()
@IsBoolean()
receiptNeeded: boolean = false;
}

View File

@ -0,0 +1,18 @@
import { Entity, Column } from "typeorm";
import { IsInt, IsPositive,} from "class-validator";
import { Donation } from "./Donation";
/**
* Defines a fixed donation.
*/
@Entity()
export class FixedDonation extends Donation {
/**
* The donation's amount in cents (or whatever your currency's smallest unit is.).
*/
@Column()
@IsInt()
@IsPositive()
amount: number;
}

View File

@ -0,0 +1,89 @@
import { PrimaryGeneratedColumn, Column, OneToMany, ManyToOne, Entity } from "typeorm";
import {
IsEmail,
IsInt,
IsNotEmpty,
IsOptional,
IsPhoneNumber,
IsString,
} from "class-validator";
import { Address } from "./Address";
import { Donation } from "./Donation";
import { RunnerGroup } from "./RunnerGroup";
/**
* Defines a group's contact.
*/
@Entity()
export class GroupContact {
/**
* Autogenerated unique id (primary key).
*/
@PrimaryGeneratedColumn()
@IsOptional()
@IsInt()
id: number;
/**
* The contact's first name.
*/
@Column()
@IsNotEmpty()
@IsString()
firstname: string;
/**
* The contact's middle name.
* Optional
*/
@Column()
@IsOptional()
@IsString()
middlename?: string;
/**
* The contact's last name.
*/
@Column()
@IsOptional()
@IsString()
lastname: string;
/**
* The contact's address.
* Optional
*/
@IsOptional()
@ManyToOne(() => Address, address => address.participants)
address?: Address;
/**
* The contact's phone number.
* Optional
*/
@Column()
@IsOptional()
@IsPhoneNumber("DE")
phone?: string;
/**
* The contact's email address.
* Optional
*/
@Column()
@IsOptional()
@IsEmail()
email?: string;
/**
* Used to link the contact as the donor of a donation.
*/
@OneToMany(() => Donation, donation => donation.donor)
donations: Donation[];
/**
* Used to link runners to donations.
*/
@OneToMany(() => RunnerGroup, group => group.contact)
groups: RunnerGroup[];
}

82
src/models/Participant.ts Normal file
View File

@ -0,0 +1,82 @@
import { PrimaryGeneratedColumn, Column, OneToMany, ManyToOne, Entity } from "typeorm";
import {
IsEmail,
IsInt,
IsNotEmpty,
IsOptional,
IsPhoneNumber,
IsString,
} from "class-validator";
import { Address } from "./Address";
import { Donation } from "./Donation";
/**
* Defines the participant interface.
*/
@Entity()
export abstract class Participant {
/**
* Autogenerated unique id (primary key).
*/
@PrimaryGeneratedColumn()
@IsOptional()
@IsInt()
id: number;
/**
* The participant's first name.
*/
@Column()
@IsNotEmpty()
@IsString()
firstname: string;
/**
* The participant's middle name.
* Optional
*/
@Column()
@IsOptional()
@IsString()
middlename?: string;
/**
* The participant's last name.
*/
@Column()
@IsOptional()
@IsString()
lastname: string;
/**
* The participant's address.
* Optional
*/
@IsOptional()
@ManyToOne(() => Address, address => address.participants)
address?: Address;
/**
* The participant's phone number.
* Optional
*/
@Column()
@IsOptional()
@IsPhoneNumber("DE")
phone?: string;
/**
* The participant's email address.
* Optional
*/
@Column()
@IsOptional()
@IsEmail()
email?: string;
/**
* Used to link the participant as the donor of a donation.
*/
@OneToMany(() => Donation, donation => donation.donor)
donations: Donation[];
}

50
src/models/Permission.ts Normal file
View File

@ -0,0 +1,50 @@
import { PrimaryGeneratedColumn, Column, OneToMany, Entity, ManyToOne } from "typeorm";
import {
IsInt,
IsNotEmpty,
IsOptional,
IsString,
} from "class-validator";
import { User } from './User';
import { UserGroup } from './UserGroup';
/**
* Defines the Permission interface.
*/
@Entity()
export abstract class Permission {
/**
* Autogenerated unique id (primary key).
*/
@PrimaryGeneratedColumn()
@IsOptional()
@IsInt()
id: number;
/**
* users
*/
@OneToMany(() => User, user => user.permissions)
users: User[]
/**
* groups
*/
@OneToMany(() => UserGroup, group => group.permissions)
groups: UserGroup[]
/**
* The target
*/
@Column()
@IsNotEmpty()
@IsString()
target: string;
/**
* The action type
*/
@Column()
@IsNotEmpty()
@IsString()
action: string;
}

44
src/models/Runner.ts Normal file
View File

@ -0,0 +1,44 @@
import { Entity, Column, OneToMany, ManyToOne } from "typeorm";
import { IsInt, IsNotEmpty,} from "class-validator";
import { Participant } from "./Participant";
import { RunnerGroup } from "./RunnerGroup";
import { DistanceDonation } from "./DistanceDonation";
import { RunnerCard } from "./RunnerCard";
import { Scan } from "./Scan";
/**
* Defines a runner.
*/
@Entity()
export class Runner extends Participant {
/**
* The runner's associated group.
*/
@IsNotEmpty()
@ManyToOne(() => RunnerGroup, group => group.runners)
group: RunnerGroup;
/**
* Used to link runners to donations.
*/
@OneToMany(() => DistanceDonation, distanceDonation => distanceDonation.runner)
distanceDonations: DistanceDonation[];
/**
* Used to link runners to cards.
*/
@OneToMany(() => RunnerCard, card => card.runner)
cards: RunnerCard[];
/**
* Used to link runners to a scans
*/
@OneToMany(() => Scan, scan => scan.runner)
scans: Scan[];
@IsInt()
public get distance() : number {
return this.scans.filter(scan => scan.valid === true).reduce((sum, current) => sum + current.distance, 0);
}
}

56
src/models/RunnerCard.ts Normal file
View File

@ -0,0 +1,56 @@
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany } from "typeorm";
import {
IsBoolean,
IsInt,
IsNotEmpty,
IsOptional,
IsString,
} from "class-validator";
import { Runner } from "./Runner";
import { TrackScan } from "./TrackScan";
/**
* Defines a card that can be scanned via a scanner station.
*/
@Entity()
export class RunnerCard {
/**
* Autogenerated unique id (primary key).
*/
@PrimaryGeneratedColumn()
@IsOptional()
@IsInt()
id: number;
/**
* The runner that is currently associated with this card.
*/
@IsOptional()
@ManyToOne(() => Runner, runner => runner.cards)
runner: Runner;
/**
* The card's code.
* This has to be able to being converted to something barcode compatible.
* Probably gonna be autogenerated.
*/
@Column()
@IsString()
@IsNotEmpty()
//TODO: Generate this
code: string;
/**
* Is the card enabled (for fraud reasons)?
* Default: true
*/
@Column()
@IsBoolean()
enabled: boolean = true;
/**
* Used to link cards to a track scans.
*/
@OneToMany(() => TrackScan, scan => scan.track)
scans: TrackScan[];
}

46
src/models/RunnerGroup.ts Normal file
View File

@ -0,0 +1,46 @@
import { PrimaryGeneratedColumn, Column, OneToMany, ManyToOne, Entity } from "typeorm";
import {
IsInt,
IsNotEmpty,
IsOptional,
IsString,
} from "class-validator";
import { GroupContact } from "./GroupContact";
import { Runner } from "./Runner";
import { RunnerTeam } from "./RunnerTeam";
/**
* Defines the runnerGroup interface.
*/
@Entity()
export abstract class RunnerGroup {
/**
* Autogenerated unique id (primary key).
*/
@PrimaryGeneratedColumn()
@IsOptional()
@IsInt()
id: number;
/**
* The group's first name.
*/
@Column()
@IsNotEmpty()
@IsString()
name: string;
/**
* The group's contact.
* Optional
*/
@IsOptional()
@ManyToOne(() => GroupContact, contact => contact.groups)
contact?: GroupContact;
/**
* Used to link runners to a runner group.
*/
@OneToMany(() => Runner, runner => runner.group)
runners: Runner[];
}

View File

@ -0,0 +1,26 @@
import { Entity, Column, ManyToOne, OneToMany } from "typeorm";
import { IsOptional,} from "class-validator";
import { RunnerGroup } from "./RunnerGroup";
import { Address } from "./Address";
import { RunnerTeam } from "./RunnerTeam";
/**
* Defines a runner organisation (business or school for example).
*/
@Entity()
export class RunnerOrganisation extends RunnerGroup {
/**
* The organisations's address.
* Optional
*/
@IsOptional()
@ManyToOne(() => Address, address => address.groups)
address?: Address;
/**
* Used to link teams to runner groups.
*/
@OneToMany(() => RunnerTeam, team => team.parentGroup)
teams: RunnerTeam[];
}

19
src/models/RunnerTeam.ts Normal file
View File

@ -0,0 +1,19 @@
import { Entity, Column, ManyToOne } from "typeorm";
import { IsNotEmpty } from "class-validator";
import { RunnerGroup } from "./RunnerGroup";
import { RunnerOrganisation } from "./RunnerOrganisation";
/**
* Defines a runner team (class or deparment for example).
*/
@Entity()
export class RunnerTeam extends RunnerGroup {
/**
* The team's parent group.
* Optional
*/
@IsNotEmpty()
@ManyToOne(() => RunnerOrganisation, org => org.teams)
parentGroup?: RunnerOrganisation;
}

45
src/models/Scan.ts Normal file
View File

@ -0,0 +1,45 @@
import { PrimaryGeneratedColumn, Column, ManyToOne, Entity } from "typeorm";
import {
IsBoolean,
IsInt,
IsNotEmpty,
IsOptional,
IsPositive,
} from "class-validator";
import { Runner } from "./Runner";
/**
* Defines the scan interface.
*/
@Entity()
export abstract class Scan {
/**
* Autogenerated unique id (primary key).
*/
@PrimaryGeneratedColumn()
@IsOptional()
@IsInt()
id: number;
/**
* The associated runner.
*/
@IsNotEmpty()
@ManyToOne(() => Runner, runner => runner.scans)
runner: Runner;
/**
* The scan's distance in meters.
*/
@IsInt()
@IsPositive()
abstract distance: number;
/**
* Is the scan valid (for fraud reasons).
* Default: true
*/
@Column()
@IsBoolean()
valid: boolean = true;
}

61
src/models/ScanStation.ts Normal file
View File

@ -0,0 +1,61 @@
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany } from "typeorm";
import {
IsBoolean,
IsInt,
IsNotEmpty,
IsOptional,
IsString,
} from "class-validator";
import { Track } from "./Track";
import { TrackScan } from "./TrackScan";
/**
* ScannerStations have the ability to create scans for specific tracks.
*/
@Entity()
export class ScanStation {
/**
* Autogenerated unique id (primary key).
*/
@PrimaryGeneratedColumn()
@IsOptional()
@IsInt()
id: number;
/**
* The station's description.
*/
@Column()
@IsNotEmpty()
@IsString()
description: string;
/**
* The track this station is associated with.
*/
@IsNotEmpty()
@ManyToOne(() => Track, track => track.stations)
track: Track;
/**
* The station's api key.
*/
@Column()
@IsNotEmpty()
@IsString()
key: string;
/**
* Is the station enabled (for fraud reasons)?
* Default: true
*/
@Column()
@IsBoolean()
enabled: boolean = true;
/**
* Used to link track scans to a scan station.
*/
@OneToMany(() => TrackScan, scan => scan.track)
scans: TrackScan[];
}

View File

@ -1,4 +1,4 @@
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm";
import {
IsInt,
IsNotEmpty,
@ -6,6 +6,8 @@ import {
IsPositive,
IsString,
} from "class-validator";
import { ScanStation } from "./ScanStation";
import { TrackScan } from "./TrackScan";
/**
* Defines a track of given length.
@ -29,10 +31,22 @@ export class Track {
name: string;
/**
* The track's length in meters.
* The track's length/distance in meters.
*/
@Column()
@IsInt()
@IsPositive()
length: number;
distance: number;
/**
* Used to link scan stations to track.
*/
@OneToMany(() => ScanStation, station => station.track)
stations: ScanStation[];
/**
* Used to link track scans to a track.
*/
@OneToMany(() => TrackScan, scan => scan.track)
scans: TrackScan[];
}

58
src/models/TrackScan.ts Normal file
View File

@ -0,0 +1,58 @@
import { PrimaryGeneratedColumn, Column, ManyToOne, Entity } from "typeorm";
import {
IsBoolean,
IsDateString,
IsInt,
IsNotEmpty,
IsOptional,
IsPositive,
} from "class-validator";
import { Scan } from "./Scan";
import { Runner } from "./Runner";
import { Track } from "./Track";
import { RunnerCard } from "./RunnerCard";
import { ScanStation } from "./ScanStation";
/**
* Defines the scan interface.
*/
@Entity()
export class TrackScan extends Scan {
/**
* The associated track.
*/
@IsNotEmpty()
@ManyToOne(() => Track, track => track.scans)
track: Track;
/**
* The associated card.
*/
@IsNotEmpty()
@ManyToOne(() => RunnerCard, card => card.scans)
card: RunnerCard;
/**
* The scanning station.
*/
@IsNotEmpty()
@ManyToOne(() => ScanStation, station => station.scans)
station: ScanStation;
/**
* The scan's distance in meters.
*/
@IsInt()
@IsPositive()
public get distance(): number {
return this.track.distance;
}
/**
* The scan's creation timestamp.
*/
@Column()
@IsDateString()
@IsNotEmpty()
timestamp: string;
}

109
src/models/User.ts Normal file
View File

@ -0,0 +1,109 @@
import { Entity, Column, OneToMany, ManyToOne, PrimaryGeneratedColumn, Generated, Unique, JoinTable, ManyToMany } from "typeorm";
import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsString, isUUID, } from "class-validator";
import { UserGroup } from './UserGroup';
import { Permission } from './Permission';
/**
* Defines a admin user.
*/
@Entity()
export class User {
/**
* autogenerated unique id (primary key).
*/
@PrimaryGeneratedColumn()
@IsOptional()
@IsInt()
id: number;
/**
* autogenerated uuid
*/
@IsOptional()
@IsInt()
@Generated("uuid")
uuid: string;
/**
* user email
*/
@IsEmail()
email: string;
/**
* username
*/
@IsString()
username: string;
/**
* firstname
*/
@IsString()
@IsNotEmpty()
firstname: string;
/**
* middlename
*/
@IsString()
@IsOptional()
middlename: string;
/**
* lastname
*/
@IsString()
@IsNotEmpty()
lastname: string;
/**
* password
*/
@IsString()
@IsNotEmpty()
password: string;
/**
* permissions
*/
@ManyToOne(() => Permission, permission => permission.users)
permissions: Permission[];
/**
* groups
*/
@ManyToMany(() => UserGroup)
@JoinTable()
groups: UserGroup[];
/**
* is user enabled?
*/
@IsBoolean()
enabled: boolean;
/**
* jwt refresh count
*/
@IsInt()
@Column({ default: 1 })
refreshTokenCount: number;
/**
* profilepic
*/
@IsString()
profilepic: string;
/**
* calculate all permissions
*/
public get calc_permissions(): Permission[] {
let final_permissions = this.groups.forEach((permission) => {
console.log(permission);
})
// TODO: add user permissions on top of group permissions + return
return []
}
}

48
src/models/UserAction.ts Normal file
View File

@ -0,0 +1,48 @@
import { PrimaryGeneratedColumn, Column, OneToMany, Entity } from "typeorm";
import {
IsInt,
IsNotEmpty,
IsOptional,
IsString,
} from "class-validator";
/**
* Defines the UserAction interface.
*/
@Entity()
export class UserAction {
/**
* Autogenerated unique id (primary key).
*/
@PrimaryGeneratedColumn()
@IsOptional()
@IsInt()
id: number;
// TODO:
// user: relation
/**
* The actions's target (e.g. Track#2)
*/
@Column()
@IsNotEmpty()
@IsString()
target: string;
/**
* The actions's action (e.g. UPDATE)
*/
@Column()
@IsNotEmpty()
@IsString()
action: string;
/**
* The description of change (before-> after; e.g. distance:15->17)
*/
@Column()
@IsOptional()
@IsString()
changed: string;
}

44
src/models/UserGroup.ts Normal file
View File

@ -0,0 +1,44 @@
import { PrimaryGeneratedColumn, Column, OneToMany, Entity, ManyToOne } from "typeorm";
import {
IsInt,
IsNotEmpty,
IsOptional,
IsString,
} from "class-validator";
import { Permission } from "./Permission";
/**
* Defines the UserGroup interface.
*/
@Entity()
export abstract class UserGroup {
/**
* Autogenerated unique id (primary key).
*/
@PrimaryGeneratedColumn()
@IsOptional()
@IsInt()
id: number;
/**
* permissions
*/
@ManyToOne(() => Permission, permission => permission.groups)
permissions: Permission[];
/**
* The group's name
*/
@Column()
@IsNotEmpty()
@IsString()
name: string;
/**
* The group's description
*/
@Column()
@IsOptional()
@IsString()
description: string;
}