diff --git a/src/models/Address.ts b/src/models/Address.ts new file mode 100644 index 0000000..59e6570 --- /dev/null +++ b/src/models/Address.ts @@ -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[]; +} diff --git a/src/models/DistanceDonation.ts b/src/models/DistanceDonation.ts new file mode 100644 index 0000000..878f8cb --- /dev/null +++ b/src/models/DistanceDonation.ts @@ -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; + } +} diff --git a/src/models/Donation.ts b/src/models/Donation.ts new file mode 100644 index 0000000..7143c99 --- /dev/null +++ b/src/models/Donation.ts @@ -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; +} \ No newline at end of file diff --git a/src/models/Donor.ts b/src/models/Donor.ts new file mode 100644 index 0000000..5d0af07 --- /dev/null +++ b/src/models/Donor.ts @@ -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; +} \ No newline at end of file diff --git a/src/models/FixedDonation.ts b/src/models/FixedDonation.ts new file mode 100644 index 0000000..e429ddf --- /dev/null +++ b/src/models/FixedDonation.ts @@ -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; +} \ No newline at end of file diff --git a/src/models/GroupContact.ts b/src/models/GroupContact.ts new file mode 100644 index 0000000..58a07a7 --- /dev/null +++ b/src/models/GroupContact.ts @@ -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[]; +} \ No newline at end of file diff --git a/src/models/Participant.ts b/src/models/Participant.ts new file mode 100644 index 0000000..2f4327b --- /dev/null +++ b/src/models/Participant.ts @@ -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[]; +} \ No newline at end of file diff --git a/src/models/Permission.ts b/src/models/Permission.ts new file mode 100644 index 0000000..e9b049b --- /dev/null +++ b/src/models/Permission.ts @@ -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; +} \ No newline at end of file diff --git a/src/models/Runner.ts b/src/models/Runner.ts new file mode 100644 index 0000000..3a8532a --- /dev/null +++ b/src/models/Runner.ts @@ -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); + } + +} diff --git a/src/models/RunnerCard.ts b/src/models/RunnerCard.ts new file mode 100644 index 0000000..f978d0c --- /dev/null +++ b/src/models/RunnerCard.ts @@ -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[]; +} diff --git a/src/models/RunnerGroup.ts b/src/models/RunnerGroup.ts new file mode 100644 index 0000000..a7c199b --- /dev/null +++ b/src/models/RunnerGroup.ts @@ -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[]; +} \ No newline at end of file diff --git a/src/models/RunnerOrganisation.ts b/src/models/RunnerOrganisation.ts new file mode 100644 index 0000000..958b363 --- /dev/null +++ b/src/models/RunnerOrganisation.ts @@ -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[]; +} \ No newline at end of file diff --git a/src/models/RunnerTeam.ts b/src/models/RunnerTeam.ts new file mode 100644 index 0000000..f12c5eb --- /dev/null +++ b/src/models/RunnerTeam.ts @@ -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; +} \ No newline at end of file diff --git a/src/models/Scan.ts b/src/models/Scan.ts new file mode 100644 index 0000000..055e94b --- /dev/null +++ b/src/models/Scan.ts @@ -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; +} diff --git a/src/models/ScanStation.ts b/src/models/ScanStation.ts new file mode 100644 index 0000000..4415ebc --- /dev/null +++ b/src/models/ScanStation.ts @@ -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[]; +} diff --git a/src/models/Track.ts b/src/models/Track.ts index ea09063..eda6def 100644 --- a/src/models/Track.ts +++ b/src/models/Track.ts @@ -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[]; } diff --git a/src/models/TrackScan.ts b/src/models/TrackScan.ts new file mode 100644 index 0000000..ce35ee8 --- /dev/null +++ b/src/models/TrackScan.ts @@ -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; +} diff --git a/src/models/User.ts b/src/models/User.ts new file mode 100644 index 0000000..d946500 --- /dev/null +++ b/src/models/User.ts @@ -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 [] + } +} diff --git a/src/models/UserAction.ts b/src/models/UserAction.ts new file mode 100644 index 0000000..7f6dc43 --- /dev/null +++ b/src/models/UserAction.ts @@ -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; +} \ No newline at end of file diff --git a/src/models/UserGroup.ts b/src/models/UserGroup.ts new file mode 100644 index 0000000..5a19713 --- /dev/null +++ b/src/models/UserGroup.ts @@ -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; +} \ No newline at end of file