Code + comment cleanup for the entities

ref #39
This commit is contained in:
Nicolai Ort 2020-12-21 15:29:32 +01:00
parent a03f1a438d
commit d20d738218
21 changed files with 156 additions and 87 deletions

View File

@ -10,7 +10,8 @@ import { Participant } from "./Participant";
import { RunnerOrganisation } from "./RunnerOrganisation";
/**
* Defines a address (to be used for contact information).
* Defines the Address entity.
* Implemented this way to prevent any formatting differences.
*/
@Entity()
export class Address {
@ -23,6 +24,7 @@ export class Address {
/**
* The address's description.
* Optional and mostly for UX.
*/
@Column({ nullable: true })
@IsString()
@ -49,6 +51,8 @@ export class Address {
/**
* The address's postal code.
* This will get checked against the postal code syntax for the configured country.
* TODO: Implement the config option.
*/
@Column()
@IsString()

View File

@ -4,19 +4,21 @@ import { Donation } from "./Donation";
import { Runner } from "./Runner";
/**
* Defines a distance based donation.
* Here people donate a certain amout per kilometer
* Defines the DistanceDonation entity.
* For distanceDonations a donor pledges to donate a certain amount for each kilometer ran by a runner.
*/
@ChildEntity()
export class DistanceDonation extends Donation {
/**
* The runner associated.
* The donation's associated runner.
* Used as the source of the donation's distance.
*/
@IsNotEmpty()
@ManyToOne(() => Runner, runner => runner.distanceDonations)
runner: Runner;
/**
* The donation's amount donated per distance.
* The amount the donor set to be donated per kilometer that the runner ran.
*/
@Column()
@ -26,12 +28,12 @@ export class DistanceDonation extends Donation {
/**
* The donation's amount in cents (or whatever your currency's smallest unit is.).
* The exact implementation may differ for each type of donation.
* Get's calculated from the runner's distance ran and the amount donated per kilometer.
*/
public get amount(): number {
let calculatedAmount = -1;
try {
calculatedAmount = this.amountPerDistance * this.runner.distance;
calculatedAmount = this.amountPerDistance * (this.runner.distance / 1000);
} catch (error) {
throw error;
}

View File

@ -6,7 +6,9 @@ import { Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typ
import { Participant } from "./Participant";
/**
* Defines the donation interface.
* Defines the Donation entity.
* A donation just associates a donor with a donation amount.
* The specifics of the amoun's determination has to be implemented in child classes.
*/
@Entity()
@TableInheritance({ column: { name: "type", type: "varchar" } })

View File

@ -3,13 +3,13 @@ import { ChildEntity, Column } from "typeorm";
import { Participant } from "./Participant";
/**
* Defines a donor.
* Defines the Donor entity.
*/
@ChildEntity()
export class Donor extends Participant {
/**
* Does this donor need a receipt?.
* Default: false
* Does this donor need a receipt?
* Will later be used to automaticly generate donation receipts.
*/
@Column()
@IsBoolean()

View File

@ -3,7 +3,8 @@ import { ChildEntity, Column } from "typeorm";
import { Donation } from "./Donation";
/**
* Defines a fixed donation.
* Defines the FixedDonation entity.
* In the past there was no easy way to track fixed donations (eg. for creating donation receipts).
*/
@ChildEntity()
export class FixedDonation extends Donation {

View File

@ -13,13 +13,14 @@ import { Address } from "./Address";
import { RunnerGroup } from "./RunnerGroup";
/**
* Defines a group's contact.
* Defines the GroupContact entity.
* Mainly it's own class to reduce duplicate code and enable contact's to be associated with multiple groups.
*/
@Entity()
export class GroupContact {
/**
* Autogenerated unique id (primary key).
*/
* Autogenerated unique id (primary key).
*/
@PrimaryGeneratedColumn()
@IsInt()
id: number;
@ -34,7 +35,6 @@ export class GroupContact {
/**
* The contact's middle name.
* Optional
*/
@Column({ nullable: true })
@IsOptional()
@ -51,7 +51,7 @@ export class GroupContact {
/**
* The contact's address.
* Optional
* This is a address object to prevent any formatting differences.
*/
@IsOptional()
@ManyToOne(() => Address, address => address.participants, { nullable: true })
@ -59,7 +59,7 @@ export class GroupContact {
/**
* The contact's phone number.
* Optional
* This will be validated against the configured country phone numer syntax (default: international).
*/
@Column({ nullable: true })
@IsOptional()
@ -68,7 +68,7 @@ export class GroupContact {
/**
* The contact's email address.
* Optional
* Could later be used to automaticly send mails concerning the contact's associated groups.
*/
@Column({ nullable: true })
@IsOptional()

View File

@ -13,7 +13,8 @@ import { Address } from "./Address";
import { Donation } from "./Donation";
/**
* Defines the participant interface.
* Defines the Participant entity.
* Participans can donate and therefor be associated with donation entities.
*/
@Entity()
@TableInheritance({ column: { name: "type", type: "varchar" } })
@ -35,7 +36,6 @@ export abstract class Participant {
/**
* The participant's middle name.
* Optional
*/
@Column({ nullable: true })
@IsOptional()
@ -52,14 +52,14 @@ export abstract class Participant {
/**
* The participant's address.
* Optional
* This is a address object to prevent any formatting differences.
*/
@ManyToOne(() => Address, address => address.participants, { nullable: true })
address?: Address;
/**
* The participant's phone number.
* Optional
* This will be validated against the configured country phone numer syntax (default: international).
*/
@Column({ nullable: true })
@IsOptional()
@ -68,7 +68,7 @@ export abstract class Participant {
/**
* The participant's email address.
* Optional
* Can be used to contact the participant.
*/
@Column({ nullable: true })
@IsOptional()
@ -77,6 +77,7 @@ export abstract class Participant {
/**
* 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[];

View File

@ -8,7 +8,9 @@ import { PermissionAction } from '../enums/PermissionAction';
import { PermissionTarget } from '../enums/PermissionTargets';
import { Principal } from './Principal';
/**
* Defines the Permission interface.
* Defines the Permission entity.
* Permissions can be granted to principals.
* The permissions possible targets and actions are defined in enums.
*/
@Entity()
export class Permission {
@ -20,13 +22,14 @@ export class Permission {
id: number;
/**
* The permissions principal
* The permission's principal.
*/
@ManyToOne(() => Principal, principal => principal.permissions)
principal: Principal;
/**
* The target
* The permission's target.
* This get's stored as the enum value's string representation for compatability reasons.
*/
@Column({ type: 'varchar' })
@IsNotEmpty()
@ -34,14 +37,16 @@ export class Permission {
target: PermissionTarget;
/**
* The action type
* The permission's action.
* This get's stored as the enum value's string representation for compatability reasons.
*/
@Column({ type: 'varchar' })
@IsEnum(PermissionAction)
action: PermissionAction;
/**
* Turn this into a string for exporting (and jwts).
* Turn this into a string for exporting and jwts.
* Mainly used to shrink the size of jwts (otherwise the would contain entire objects).
*/
public toString(): string {
return this.target + ":" + this.action;

View File

@ -4,23 +4,27 @@ import { ResponsePrincipal } from '../responses/ResponsePrincipal';
import { Permission } from './Permission';
/**
* Defines a admin user.
* Defines the principal entity.
* A principal basicly is any entity that can receive permissions for the api (users and their groups).
*/
@Entity()
@TableInheritance({ column: { name: "type", type: "varchar" } })
export abstract class Principal {
/**
* autogenerated unique id (primary key).
* Autogenerated unique id (primary key).
*/
@PrimaryGeneratedColumn()
@IsInt()
id: number;
/**
* permissions
*/
* The participant's permissions.
*/
@OneToMany(() => Permission, permission => permission.principal, { nullable: true })
permissions: Permission[];
/**
* Turns this entity into it's response class.
*/
public abstract toResponse(): ResponsePrincipal;
}

View File

@ -7,44 +7,52 @@ import { RunnerGroup } from "./RunnerGroup";
import { Scan } from "./Scan";
/**
* Defines a runner.
* Defines the runner entity.
* Runners differ from participants in being able to actually accumulate a ran distance through scans.
* Runner's get organized in groups.
*/
@ChildEntity()
export class Runner extends Participant {
/**
* The runner's associated group.
* Can be a runner team or organisation.
*/
@IsNotEmpty()
@ManyToOne(() => RunnerGroup, group => group.runners, { nullable: false })
group: RunnerGroup;
/**
* Used to link runners to donations.
* The runner's associated distanceDonations.
* Used to link runners to distanceDonations in order to calculate the donation's amount based on the distance the runner ran.
*/
@OneToMany(() => DistanceDonation, distanceDonation => distanceDonation.runner, { nullable: true })
distanceDonations: DistanceDonation[];
/**
* Used to link runners to cards.
* The runner's associated cards.
* Used to link runners to cards - yes a runner be associated with multiple cards this came in handy in the past.
*/
@OneToMany(() => RunnerCard, card => card.runner, { nullable: true })
cards: RunnerCard[];
/**
* Used to link runners to a scans
* The runner's associated scans.
* Used to link runners to scans (valid and fraudulant).
*/
@OneToMany(() => Scan, scan => scan.runner, { nullable: true })
scans: Scan[];
/**
* Returns all valid scans associated with this runner.
* This is implemented here to avoid duplicate code in other files.
*/
public get validScans(): Scan[] {
return this.scans.filter(scan => { scan.valid === true });
}
/**
* Returns the total distance ran by this runner.
* Returns the total distance ran by this runner based on all his valid scans.
* This is implemented here to avoid duplicate code in other files.
*/
@IsInt()
public get distance(): number {

View File

@ -11,7 +11,9 @@ import { Runner } from "./Runner";
import { TrackScan } from "./TrackScan";
/**
* Defines a card that can be scanned via a scanner station.
* Defines the RunnerCard entity.
* A runnerCard is a physical representation for a runner.
* It can be associated with a runner to create scans via the scan station's.
*/
@Entity()
export class RunnerCard {
@ -23,7 +25,8 @@ export class RunnerCard {
id: number;
/**
* The runner that is currently associated with this card.
* The card's currently associated runner.
* To increase reusability a card can be reassigned.
*/
@IsOptional()
@ManyToOne(() => Runner, runner => runner.cards, { nullable: true })
@ -32,7 +35,7 @@ export class RunnerCard {
/**
* The card's code.
* This has to be able to being converted to something barcode compatible.
* could theoretically be autogenerated
* Will get automaticlly generated (not implemented yet).
*/
@Column()
@IsEAN()
@ -49,7 +52,8 @@ export class RunnerCard {
enabled: boolean = true;
/**
* Used to link cards to a track scans.
* The card's associated scans.
* Used to link cards to track scans.
*/
@OneToMany(() => TrackScan, scan => scan.track, { nullable: true })
scans: TrackScan[];

View File

@ -9,7 +9,8 @@ import { GroupContact } from "./GroupContact";
import { Runner } from "./Runner";
/**
* Defines the runnerGroup interface.
* Defines the RunnerGroup entity.
* This is used to group runners together (as the name suggests).
*/
@Entity()
@TableInheritance({ column: { name: "type", type: "varchar" } })
@ -31,13 +32,14 @@ export abstract class RunnerGroup {
/**
* The group's contact.
* Optional
* This is mostly a feature for the group managers and public relations.
*/
@IsOptional()
@ManyToOne(() => GroupContact, contact => contact.groups, { nullable: true })
contact?: GroupContact;
/**
* The group's associated runners.
* Used to link runners to a runner group.
*/
@OneToMany(() => Runner, runner => runner.group, { nullable: true })

View File

@ -5,22 +5,23 @@ import { RunnerGroup } from "./RunnerGroup";
import { RunnerTeam } from "./RunnerTeam";
/**
* Defines a runner organisation (business or school for example).
* Defines the RunnerOrganisation entity.
* This usually is a school, club or company.
*/
@ChildEntity()
export class RunnerOrganisation extends RunnerGroup {
/**
* The organisations's address.
* Optional
*/
@IsOptional()
@ManyToOne(() => Address, address => address.groups, { nullable: true })
address?: Address;
/**
* Used to link teams to runner groups.
*/
* The organisation's teams.
* Used to link teams to a organisation.
*/
@OneToMany(() => RunnerTeam, team => team.parentGroup, { nullable: true })
teams: RunnerTeam[];
}

View File

@ -4,14 +4,15 @@ import { RunnerGroup } from "./RunnerGroup";
import { RunnerOrganisation } from "./RunnerOrganisation";
/**
* Defines a runner team (class or deparment for example).
* Defines the RunnerTeam entity.
* This usually is a school class or department in a company.
*/
@ChildEntity()
export class RunnerTeam extends RunnerGroup {
/**
* The team's parent group.
* Optional
* Every team has to be part of a runnerOrganisation - this get's checked on creation and update.
*/
@IsNotEmpty()
@ManyToOne(() => RunnerOrganisation, org => org.teams, { nullable: true })

View File

@ -9,7 +9,8 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } f
import { Runner } from "./Runner";
/**
* Defines the scan interface.
* Defines the Scan entity.
* A scan basicly adds a certain distance to a runner's total ran distance.
*/
@Entity()
@TableInheritance({ column: { name: "type", type: "varchar" } })
@ -22,7 +23,8 @@ export abstract class Scan {
id: number;
/**
* The associated runner.
* The scan's associated runner.
* This is important to link ran distances to runners.
*/
@IsNotEmpty()
@ManyToOne(() => Runner, runner => runner.scans, { nullable: false })
@ -30,15 +32,17 @@ export abstract class Scan {
/**
* 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).
* Default: true
*/
* Is the scan valid (for fraud reasons).
* The determination of validity will work differently for every child class.
* Default: true
*/
@Column()
@IsBoolean()
valid: boolean = true;

View File

@ -10,7 +10,8 @@ import { Track } from "./Track";
import { TrackScan } from "./TrackScan";
/**
* ScannerStations have the ability to create scans for specific tracks.
* Defines the ScanStation entity.
* ScanStations get used to create TrackScans for runners based on a scan of their runnerCard.
*/
@Entity()
export class ScanStation {
@ -23,6 +24,7 @@ export class ScanStation {
/**
* The station's description.
* Mostly for better UX when traceing back stuff.
*/
@Column({ nullable: true })
@IsOptional()
@ -31,6 +33,7 @@ export class ScanStation {
/**
* The track this station is associated with.
* All scans created by this station will also be associated with this track.
*/
@IsNotEmpty()
@ManyToOne(() => Track, track => track.stations, { nullable: false })
@ -38,6 +41,7 @@ export class ScanStation {
/**
* The station's api key.
* This is used to authorize a station against the api (not implemented yet).
*/
@Column()
@IsNotEmpty()
@ -45,7 +49,7 @@ export class ScanStation {
key: string;
/**
* Is the station enabled (for fraud reasons)?
* Is the station enabled (for fraud and setup reasons)?
* Default: true
*/
@Column()

View File

@ -1,7 +1,6 @@
import {
IsInt,
IsNotEmpty,
IsPositive,
IsString
} from "class-validator";
@ -10,7 +9,7 @@ import { ScanStation } from "./ScanStation";
import { TrackScan } from "./TrackScan";
/**
* Defines a track of given length.
* Defines the Track entity.
*/
@Entity()
export class Track {
@ -23,6 +22,7 @@ export class Track {
/**
* The track's name.
* Mainly here for UX.
*/
@Column()
@IsString()
@ -31,6 +31,7 @@ export class Track {
/**
* The track's length/distance in meters.
* Will be used to calculate runner's ran distances.
*/
@Column()
@IsInt()
@ -38,13 +39,15 @@ export class Track {
distance: number;
/**
* Used to link scan stations to track.
* Used to link scan stations to a certain track.
* This makes the configuration of the scan stations easier.
*/
@OneToMany(() => ScanStation, station => station.track, { nullable: true })
stations: ScanStation[];
/**
* Used to link track scans to a track.
* The scan will derive it's distance from the track's distance.
*/
@OneToMany(() => TrackScan, scan => scan.track, { nullable: true })
scans: TrackScan[];

View File

@ -12,26 +12,30 @@ import { ScanStation } from "./ScanStation";
import { Track } from "./Track";
/**
* Defines the scan interface.
* Defines the TrackScan entity.
* A track scan usaually get's generated by a scan station.
*/
@ChildEntity()
export class TrackScan extends Scan {
/**
* The associated track.
* The scan's associated track.
* This is used to determine the scan's distance.
*/
@IsNotEmpty()
@ManyToOne(() => Track, track => track.scans, { nullable: true })
track: Track;
/**
* The associated card.
* The runnerCard associated with the scan.
* This get's saved for documentation and management purposes.
*/
@IsNotEmpty()
@ManyToOne(() => RunnerCard, card => card.scans, { nullable: true })
card: RunnerCard;
/**
* The scanning station.
* The scanning station that created the scan.
* Mainly used for logging and traceing back scans (or errors)
*/
@IsNotEmpty()
@ManyToOne(() => ScanStation, station => station.scans, { nullable: true })
@ -39,6 +43,7 @@ export class TrackScan extends Scan {
/**
* The scan's distance in meters.
* This just get's loaded from it's track.
*/
@IsInt()
@IsPositive()
@ -48,6 +53,7 @@ export class TrackScan extends Scan {
/**
* The scan's creation timestamp.
* Will be used to implement fraud detection.
*/
@Column()
@IsDateString()

View File

@ -8,26 +8,29 @@ import { UserAction } from './UserAction';
import { UserGroup } from './UserGroup';
/**
* Defines a admin user.
* Defines the User entity.
* Users are the ones that can use the "admin" webui and do stuff in the backend.
*/
@ChildEntity()
export class User extends Principal {
/**
* uuid
* The user's uuid.
* Mainly gets used as a per-user salt for the password hash.
*/
@Column({ unique: true })
@IsUUID(4)
uuid: string;
/**
* user email
* The user's e-mail address.
* Either username or email has to be set (otherwise the user couldn't log in).
*/
@Column({ nullable: true, unique: true })
@IsEmail()
email?: string;
/**
* user phone
* The user's phone number.
*/
@Column({ nullable: true })
@IsOptional()
@ -35,14 +38,15 @@ export class User extends Principal {
phone?: string;
/**
* username
* The user's username.
* Either username or email has to be set (otherwise the user couldn't log in).
*/
@Column({ nullable: true, unique: true })
@IsString()
username?: string;
/**
* firstname
* The user's first name.
*/
@Column()
@IsString()
@ -50,7 +54,7 @@ export class User extends Principal {
firstname: string;
/**
* middlename
* The user's middle name.
*/
@Column({ nullable: true })
@IsString()
@ -58,7 +62,7 @@ export class User extends Principal {
middlename?: string;
/**
* lastname
* The user's last name.
*/
@Column()
@IsString()
@ -66,7 +70,8 @@ export class User extends Principal {
lastname: string;
/**
* password
* The user's password.
* This is a argon2 hash salted with the user's uuid.
*/
@Column()
@IsString()
@ -74,7 +79,8 @@ export class User extends Principal {
password: string;
/**
* groups
* The groups this user is a part of.
* The user will inherit the groups permissions (without overwriting his own).
*/
@IsOptional()
@ManyToMany(() => UserGroup, { nullable: true })
@ -82,21 +88,23 @@ export class User extends Principal {
groups: UserGroup[];
/**
* is user enabled?
* Is this user enabled?
*/
@Column()
@IsBoolean()
enabled: boolean = true;
/**
* jwt refresh count
* The user's jwt refresh token count.
* Used to invalidate jwts.
*/
@IsInt()
@Column({ default: 1 })
refreshTokenCount?: number;
/**
* profilepic
* 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: true })
@IsString()
@ -104,14 +112,15 @@ export class User extends Principal {
profilePic?: string;
/**
* actions
* The actions performed by this user.
* For documentation purposes only, will be implemented later.
*/
@IsOptional()
@OneToMany(() => UserAction, action => action.user, { nullable: true })
actions: UserAction[]
/**
* Turn this into a response.
* Turns this entity into it's response class.
*/
public toResponse(): ResponsePrincipal {
return new ResponseUser(this);

View File

@ -1,14 +1,17 @@
import {
IsEnum,
IsInt,
IsNotEmpty,
IsOptional,
IsString
} from "class-validator";
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import { PermissionAction } from '../enums/PermissionAction';
import { User } from './User';
/**
* Defines the UserAction interface.
* Defines the UserAction entity.
* Will later be used to document a user's actions.
*/
@Entity()
export class UserAction {
@ -20,7 +23,7 @@ export class UserAction {
id: number;
/**
* user
* The user that performed the action.
*/
@ManyToOne(() => User, user => user.actions)
user: User
@ -34,15 +37,16 @@ export class UserAction {
target: string;
/**
* The actions's action (e.g. UPDATE)
* The actions's action (e.g. UPDATE).
* Directly pulled from the PermissionAction Enum.
*/
@Column()
@IsNotEmpty()
@IsString()
action: string;
@Column({ type: 'varchar' })
@IsEnum(PermissionAction)
action: PermissionAction;
/**
* The description of change (before-> after; e.g. distance:15->17)
* The description of the change (before-> after; e.g. distance:15->17).
* Will later be defined in more detail.
*/
@Column({ nullable: true })
@IsOptional()

View File

@ -9,7 +9,8 @@ import { ResponseUserGroup } from '../responses/ResponseUserGroup';
import { Principal } from './Principal';
/**
* Defines the UserGroup interface.
* Defines the UserGroup entity.
* This entity describes a group of users with a set of permissions.
*/
@ChildEntity()
export class UserGroup extends Principal {
@ -30,6 +31,9 @@ export class UserGroup extends Principal {
@IsString()
description?: string;
/**
* Turns this entity into it's response class.
*/
public toResponse(): ResponsePrincipal {
return new ResponseUserGroup(this);
}