Compare commits

...

15 Commits
1.4.0 ... dev

Author SHA1 Message Date
e27e819609
chore(release): 1.5.2
All checks were successful
Build release images / build-container (push) Successful in 1m9s
2025-05-26 19:30:33 +02:00
0f532b139c
feat(mailer): Log error message when sending selfservice forgotten mail fails 2025-05-26 19:30:16 +02:00
eebcc2e328
feat(mailer): Add logging for selfservice forgotten mail requests 2025-05-26 19:29:37 +02:00
284954d064
chore(release): 1.5.1
All checks were successful
Build release images / build-container (push) Successful in 1m12s
2025-05-26 19:24:04 +02:00
401ca923a6
feat(mailer): Log error when sending selfservice forgotten mail fails 2025-05-26 19:23:30 +02:00
bf1f6411e0
chore(release): 1.5.0
All checks were successful
Build release images / build-container (push) Successful in 1m17s
2025-05-06 19:41:36 +02:00
f225cc4954
feat(responses): Added created_at/updated_at 2025-05-06 19:38:20 +02:00
728f8a14e9
feat(entities): Added created/updated at to all entities 2025-05-06 19:33:30 +02:00
a4480589a0
feat(participants): Added created/updated at 2025-05-06 19:29:46 +02:00
0ad9eeb52f
chore(release): 1.4.3
All checks were successful
Build release images / build-container (push) Successful in 1m15s
2025-05-01 16:02:40 +02:00
4494afc64b
feat(runners): Include collected distance donation amount in runner detail 2025-05-01 16:02:28 +02:00
f4747c51de
chore(release): 1.4.2
All checks were successful
Build release images / build-container (push) Successful in 1m16s
2025-05-01 15:57:57 +02:00
07a0195f12
fix(donations): Fixed creation bug 2025-05-01 15:56:42 +02:00
7ac98229d1
chore(release): 1.4.1
All checks were successful
Build release images / build-container (push) Successful in 1m19s
2025-04-28 21:36:31 +02:00
dd5b538783
refactor(auth): Increased token timeouts to 24hrs/7days 2025-04-28 21:36:12 +02:00
33 changed files with 517 additions and 35 deletions

View File

@ -2,11 +2,56 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC. All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [1.5.2](https://git.odit.services/lfk/backend/compare/1.5.1...1.5.2)
- feat(mailer): Add logging for selfservice forgotten mail requests [`eebcc2e`](https://git.odit.services/lfk/backend/commit/eebcc2e3284230135e3911b4edaecd1a9cfd2100)
- feat(mailer): Log error message when sending selfservice forgotten mail fails [`0f532b1`](https://git.odit.services/lfk/backend/commit/0f532b139c2bc5cd89ca2dbff0867825a9363250)
#### [1.5.1](https://git.odit.services/lfk/backend/compare/1.5.0...1.5.1)
> 26 May 2025
- chore(release): 1.5.1 [`284954d`](https://git.odit.services/lfk/backend/commit/284954d064f09951c13584e9d50a83be2c4b9f72)
- feat(mailer): Log error when sending selfservice forgotten mail fails [`401ca92`](https://git.odit.services/lfk/backend/commit/401ca923a61bc5988e73209c086bc9a5a4fa04f9)
#### [1.5.0](https://git.odit.services/lfk/backend/compare/1.4.3...1.5.0)
> 6 May 2025
- feat(entities): Added created/updated at to all entities [`728f8a1`](https://git.odit.services/lfk/backend/commit/728f8a14e9fb7360fce92640bfa5658af8cadb4f)
- feat(responses): Added created_at/updated_at [`f225cc4`](https://git.odit.services/lfk/backend/commit/f225cc49548605de48cf6c6e6f7c86b163236545)
- feat(participants): Added created/updated at [`a448058`](https://git.odit.services/lfk/backend/commit/a4480589a0e23a4481332ab5efa0777c62bbab56)
- chore(release): 1.5.0 [`bf1f641`](https://git.odit.services/lfk/backend/commit/bf1f6411e0f5113842a537f5bcf632638bdf1048)
#### [1.4.3](https://git.odit.services/lfk/backend/compare/1.4.2...1.4.3)
> 1 May 2025
- feat(runners): Include collected distance donation amount in runner detail [`4494afc`](https://git.odit.services/lfk/backend/commit/4494afc64b433d26b54a293fe156d13c40faad95)
- chore(release): 1.4.3 [`0ad9eeb`](https://git.odit.services/lfk/backend/commit/0ad9eeb52f18af3ea7d86fe1bf15edb04f4cfd2d)
#### [1.4.2](https://git.odit.services/lfk/backend/compare/1.4.1...1.4.2)
> 1 May 2025
- fix(donations): Fixed creation bug [`07a0195`](https://git.odit.services/lfk/backend/commit/07a0195f125519f239d255a0cc081ddbde8f1da3)
- chore(release): 1.4.2 [`f4747c5`](https://git.odit.services/lfk/backend/commit/f4747c51de71d9b28cca1b00a91de3cfd6f0f56e)
#### [1.4.1](https://git.odit.services/lfk/backend/compare/1.4.0...1.4.1)
> 28 April 2025
- chore(release): 1.4.1 [`7ac9822`](https://git.odit.services/lfk/backend/commit/7ac98229d17e7cb019d5dcc5402870490a97f910)
- refactor(auth): Increased token timeouts to 24hrs/7days [`dd5b538`](https://git.odit.services/lfk/backend/commit/dd5b538783f9c806f0c883cd391754fb5c842ec8)
#### [1.4.0](https://git.odit.services/lfk/backend/compare/1.3.12...1.4.0) #### [1.4.0](https://git.odit.services/lfk/backend/compare/1.3.12...1.4.0)
> 28 April 2025
- feat(donations): Implement response type to indicate possible missing donor [`f4bf309`](https://git.odit.services/lfk/backend/commit/f4bf309821c140f2bc0ae8b6d96c7458fcc80978) - feat(donations): Implement response type to indicate possible missing donor [`f4bf309`](https://git.odit.services/lfk/backend/commit/f4bf309821c140f2bc0ae8b6d96c7458fcc80978)
- wip [`9875b4f`](https://git.odit.services/lfk/backend/commit/9875b4f3926e04b502e7af64c17f54fd3c1d8e3e) - wip [`9875b4f`](https://git.odit.services/lfk/backend/commit/9875b4f3926e04b502e7af64c17f54fd3c1d8e3e)
- refactor(donations): Make anon prepaid [`02b1cb9`](https://git.odit.services/lfk/backend/commit/02b1cb9904cc593faeac025ae302a8684f650f5e) - refactor(donations): Make anon prepaid [`02b1cb9`](https://git.odit.services/lfk/backend/commit/02b1cb9904cc593faeac025ae302a8684f650f5e)
- chore(release): 1.4.0 [`8e6d674`](https://git.odit.services/lfk/backend/commit/8e6d67428c85b6ee504a379ff13a3a951f7b9543)
- fix(donations): Move donor over to the types that need it [`7697acf`](https://git.odit.services/lfk/backend/commit/7697acff82b23d0c05dbbd17fee6e70eb1b7061c) - fix(donations): Move donor over to the types that need it [`7697acf`](https://git.odit.services/lfk/backend/commit/7697acff82b23d0c05dbbd17fee6e70eb1b7061c)
#### [1.3.12](https://git.odit.services/lfk/backend/compare/1.3.11...1.3.12) #### [1.3.12](https://git.odit.services/lfk/backend/compare/1.3.11...1.3.12)

View File

@ -1,6 +1,6 @@
{ {
"name": "@odit/lfk-backend", "name": "@odit/lfk-backend",
"version": "1.4.0", "version": "1.5.2",
"main": "src/app.ts", "main": "src/app.ts",
"repository": "https://git.odit.services/lfk/backend", "repository": "https://git.odit.services/lfk/backend",
"author": { "author": {

View File

@ -60,7 +60,7 @@ export class RunnerController {
@OnUndefined(RunnerNotFoundError) @OnUndefined(RunnerNotFoundError)
@OpenAPI({ description: 'Lists all information about the runner whose id got provided.' }) @OpenAPI({ description: 'Lists all information about the runner whose id got provided.' })
async getOne(@Param('id') id: number) { async getOne(@Param('id') id: number) {
let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] }) let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations'] })
if (!runner) { throw new RunnerNotFoundError(); } if (!runner) { throw new RunnerNotFoundError(); }
return new ResponseRunner(runner, true); return new ResponseRunner(runner, true);
} }

View File

@ -76,6 +76,21 @@ export class Mailer {
*/ */
public static async sendSelfserviceForgottenMail(to_address: string, runner_id: number, firstname: string, middlename: string, lastname: string, token: string, locale: string = "en") { public static async sendSelfserviceForgottenMail(to_address: string, runner_id: number, firstname: string, middlename: string, lastname: string, token: string, locale: string = "en") {
try { try {
console.log("Mail request", {
to: to_address,
templateName: 'welcome',
language: locale,
data: {
to: to_address,
templateName: 'welcome',
language: locale,
data: {
name: `${firstname} ${middlename} ${lastname}`,
barcode_content: `${runner_id}`,
link: `${process.env.SELFSERVICE_URL}/profile/${token}`
}
}
})
await axios.request({ await axios.request({
method: 'POST', method: 'POST',
url: `${Mailer.base}/api/v1/email`, url: `${Mailer.base}/api/v1/email`,
@ -96,6 +111,7 @@ export class Mailer {
}); });
} catch (error) { } catch (error) {
if (Mailer.testing) { return true; } if (Mailer.testing) { return true; }
console.error("Error while sending selfservice forgotten mail:", error.message);
throw new MailSendingError(); throw new MailSendingError();
} }
} }

View File

@ -61,11 +61,11 @@ export class CreateAuth {
} }
//Create the access token //Create the access token
const timestamp_accesstoken_expiry = Math.floor(Date.now() / 1000) + 5 * 60 const timestamp_accesstoken_expiry = Math.floor(Date.now() / 1000) + 24 * 60 * 60
newAuth.access_token = JwtCreator.createAccess(found_user, timestamp_accesstoken_expiry); newAuth.access_token = JwtCreator.createAccess(found_user, timestamp_accesstoken_expiry);
newAuth.access_token_expires_at = timestamp_accesstoken_expiry newAuth.access_token_expires_at = timestamp_accesstoken_expiry
//Create the refresh token //Create the refresh token
const timestamp_refresh_expiry = Math.floor(Date.now() / 1000) + 10 * 36000 const timestamp_refresh_expiry = Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60
newAuth.refresh_token = JwtCreator.createRefresh(found_user, timestamp_refresh_expiry); newAuth.refresh_token = JwtCreator.createRefresh(found_user, timestamp_refresh_expiry);
newAuth.refresh_token_expires_at = timestamp_refresh_expiry newAuth.refresh_token_expires_at = timestamp_refresh_expiry
return newAuth; return newAuth;

View File

@ -1,4 +1,4 @@
import { IsInt, IsPositive } from 'class-validator'; import { IsInt, IsOptional, IsPositive } from 'class-validator';
import { getConnection } from 'typeorm'; import { getConnection } from 'typeorm';
import { RunnerNotFoundError } from '../../../errors/RunnerErrors'; import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
import { DistanceDonation } from '../../entities/DistanceDonation'; import { DistanceDonation } from '../../entities/DistanceDonation';
@ -22,6 +22,7 @@ export class CreateDistanceDonation extends CreateDonation {
* The donation's paid amount in the smalles unit of your currency (default: euro cent). * The donation's paid amount in the smalles unit of your currency (default: euro cent).
*/ */
@IsInt() @IsInt()
@IsOptional()
paidAmount?: number; paidAmount?: number;
/** /**

View File

@ -1,4 +1,4 @@
import { Exclude } from 'class-transformer'; import { IsInt, IsOptional } from 'class-validator';
import { getConnection } from 'typeorm'; import { getConnection } from 'typeorm';
import { Donation } from '../../entities/Donation'; import { Donation } from '../../entities/Donation';
import { Donor } from '../../entities/Donor'; import { Donor } from '../../entities/Donor';
@ -7,10 +7,12 @@ import { Donor } from '../../entities/Donor';
* This class is used to create a new Donation entity from a json body (post request). * This class is used to create a new Donation entity from a json body (post request).
*/ */
export abstract class CreateDonation { export abstract class CreateDonation {
@Exclude() @IsInt()
@IsOptional()
donor: number; donor: number;
@Exclude() @IsInt()
@IsOptional()
paidAmount?: number; paidAmount?: number;
/** /**

View File

@ -1,8 +1,10 @@
import { import {
IsInt,
IsNotEmpty, IsNotEmpty,
IsPositive,
IsString IsString
} from "class-validator"; } from "class-validator";
import { Column, Entity, PrimaryColumn } from "typeorm"; import { BeforeInsert, BeforeUpdate, Column, Entity, PrimaryColumn } from "typeorm";
/** /**
* Defines the ConfigFlag entity. * Defines the ConfigFlag entity.
@ -24,4 +26,25 @@ export class ConfigFlag {
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
value: string; value: string;
@Column({ type: 'bigint', nullable: true, readonly: true })
@IsInt()
@IsPositive()
created_at: number;
@Column({ type: 'bigint', nullable: true })
@IsInt()
@IsPositive()
updated_at: number;
@BeforeInsert()
public setCreatedAt() {
this.created_at = Math.floor(Date.now() / 1000);
this.updated_at = Math.floor(Date.now() / 1000);
}
@BeforeUpdate()
public setUpdatedAt() {
this.updated_at = Math.floor(Date.now() / 1000);
}
} }

View File

@ -1,7 +1,8 @@
import { import {
IsInt IsInt,
IsPositive
} from "class-validator"; } from "class-validator";
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; import { BeforeInsert, BeforeUpdate, Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
import { ResponseDonation } from '../responses/ResponseDonation'; import { ResponseDonation } from '../responses/ResponseDonation';
import { Donor } from './Donor'; import { Donor } from './Donor';
@ -40,6 +41,27 @@ export abstract class Donation {
@IsInt() @IsInt()
paidAmount: number; paidAmount: number;
@Column({ type: 'bigint', nullable: true, readonly: true })
@IsInt()
@IsPositive()
created_at: number;
@Column({ type: 'bigint', nullable: true })
@IsInt()
@IsPositive()
updated_at: number;
@BeforeInsert()
public setCreatedAt() {
this.created_at = Math.floor(Date.now() / 1000);
this.updated_at = Math.floor(Date.now() / 1000);
}
@BeforeUpdate()
public setUpdatedAt() {
this.updated_at = Math.floor(Date.now() / 1000);
}
/** /**
* Turns this entity into it's response class. * Turns this entity into it's response class.
*/ */

View File

@ -5,9 +5,11 @@ import {
IsOptional, IsOptional,
IsPhoneNumber, IsPhoneNumber,
IsPositive,
IsString IsString
} from "class-validator"; } from "class-validator";
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm"; import { BeforeInsert, BeforeUpdate, Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { config } from '../../config'; import { config } from '../../config';
import { ResponseGroupContact } from '../responses/ResponseGroupContact'; import { ResponseGroupContact } from '../responses/ResponseGroupContact';
import { Address } from "./Address"; import { Address } from "./Address";
@ -81,6 +83,27 @@ export class GroupContact {
@OneToMany(() => RunnerGroup, group => group.contact, { nullable: true }) @OneToMany(() => RunnerGroup, group => group.contact, { nullable: true })
groups: RunnerGroup[]; groups: RunnerGroup[];
@Column({ type: 'bigint', nullable: true, readonly: true })
@IsInt()
@IsPositive()
created_at: number;
@Column({ type: 'bigint', nullable: true })
@IsInt()
@IsPositive()
updated_at: number;
@BeforeInsert()
public setCreatedAt() {
this.created_at = Math.floor(Date.now() / 1000);
this.updated_at = Math.floor(Date.now() / 1000);
}
@BeforeUpdate()
public setUpdatedAt() {
this.updated_at = Math.floor(Date.now() / 1000);
}
/** /**
* Turns this entity into it's response class. * Turns this entity into it's response class.
*/ */

View File

@ -5,9 +5,11 @@ import {
IsOptional, IsOptional,
IsPhoneNumber, IsPhoneNumber,
IsPositive,
IsString IsString
} from "class-validator"; } from "class-validator";
import { Column, Entity, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; import { BeforeInsert, BeforeUpdate, Column, Entity, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
import { config } from '../../config'; import { config } from '../../config';
import { ResponseParticipant } from '../responses/ResponseParticipant'; import { ResponseParticipant } from '../responses/ResponseParticipant';
import { Address } from "./Address"; import { Address } from "./Address";
@ -83,6 +85,27 @@ export abstract class Participant {
@IsString() @IsString()
created_via?: string; created_via?: string;
@Column({ type: 'bigint', nullable: true, readonly: true })
@IsInt()
@IsPositive()
created_at: number;
@Column({ type: 'bigint', nullable: true })
@IsInt()
@IsPositive()
updated_at: number;
@BeforeInsert()
public setCreatedAt() {
this.created_at = Math.floor(Date.now() / 1000);
this.updated_at = Math.floor(Date.now() / 1000);
}
@BeforeUpdate()
public setUpdatedAt() {
this.updated_at = Math.floor(Date.now() / 1000);
}
/** /**
* Turns this entity into it's response class. * Turns this entity into it's response class.
*/ */

View File

@ -1,9 +1,10 @@
import { import {
IsEnum, IsEnum,
IsInt, IsInt,
IsNotEmpty IsNotEmpty,
IsPositive
} from "class-validator"; } from "class-validator";
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; import { BeforeInsert, BeforeUpdate, Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import { PermissionAction } from '../enums/PermissionAction'; import { PermissionAction } from '../enums/PermissionAction';
import { PermissionTarget } from '../enums/PermissionTargets'; import { PermissionTarget } from '../enums/PermissionTargets';
import { ResponsePermission } from '../responses/ResponsePermission'; import { ResponsePermission } from '../responses/ResponsePermission';
@ -45,6 +46,27 @@ export class Permission {
@IsEnum(PermissionAction) @IsEnum(PermissionAction)
action: PermissionAction; action: PermissionAction;
@Column({ type: 'bigint', nullable: true, readonly: true })
@IsInt()
@IsPositive()
created_at: number;
@Column({ type: 'bigint', nullable: true })
@IsInt()
@IsPositive()
updated_at: number;
@BeforeInsert()
public setCreatedAt() {
this.created_at = Math.floor(Date.now() / 1000);
this.updated_at = Math.floor(Date.now() / 1000);
}
@BeforeUpdate()
public setUpdatedAt() {
this.updated_at = Math.floor(Date.now() / 1000);
}
/** /**
* 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). * Mainly used to shrink the size of jwts (otherwise the would contain entire objects).

View File

@ -1,5 +1,5 @@
import { IsInt } from 'class-validator'; import { IsInt, IsPositive } from 'class-validator';
import { Entity, OneToMany, PrimaryGeneratedColumn, TableInheritance } from 'typeorm'; import { BeforeInsert, BeforeUpdate, Column, Entity, OneToMany, PrimaryGeneratedColumn, TableInheritance } from 'typeorm';
import { ResponsePrincipal } from '../responses/ResponsePrincipal'; import { ResponsePrincipal } from '../responses/ResponsePrincipal';
import { Permission } from './Permission'; import { Permission } from './Permission';
@ -23,6 +23,27 @@ export abstract class Principal {
@OneToMany(() => Permission, permission => permission.principal, { nullable: true }) @OneToMany(() => Permission, permission => permission.principal, { nullable: true })
permissions: Permission[]; permissions: Permission[];
@Column({ type: 'bigint', nullable: true, readonly: true })
@IsInt()
@IsPositive()
created_at: number;
@Column({ type: 'bigint', nullable: true })
@IsInt()
@IsPositive()
updated_at: number;
@BeforeInsert()
public setCreatedAt() {
this.created_at = Math.floor(Date.now() / 1000);
this.updated_at = Math.floor(Date.now() / 1000);
}
@BeforeUpdate()
public setUpdatedAt() {
this.updated_at = Math.floor(Date.now() / 1000);
}
/** /**
* Turns this entity into it's response class. * Turns this entity into it's response class.
*/ */

View File

@ -3,9 +3,10 @@ import {
IsInt, IsInt,
IsOptional IsOptional,
IsPositive
} from "class-validator"; } from "class-validator";
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; import { BeforeInsert, BeforeUpdate, Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { RunnerCardIdOutOfRangeError } from '../../errors/RunnerCardErrors'; import { RunnerCardIdOutOfRangeError } from '../../errors/RunnerCardErrors';
import { ResponseRunnerCard } from '../responses/ResponseRunnerCard'; import { ResponseRunnerCard } from '../responses/ResponseRunnerCard';
import { Runner } from "./Runner"; import { Runner } from "./Runner";
@ -48,6 +49,27 @@ export class RunnerCard {
@OneToMany(() => TrackScan, scan => scan.track, { nullable: true }) @OneToMany(() => TrackScan, scan => scan.track, { nullable: true })
scans: TrackScan[]; scans: TrackScan[];
@Column({ type: 'bigint', nullable: true, readonly: true })
@IsInt()
@IsPositive()
created_at: number;
@Column({ type: 'bigint', nullable: true })
@IsInt()
@IsPositive()
updated_at: number;
@BeforeInsert()
public setCreatedAt() {
this.created_at = Math.floor(Date.now() / 1000);
this.updated_at = Math.floor(Date.now() / 1000);
}
@BeforeUpdate()
public setUpdatedAt() {
this.updated_at = Math.floor(Date.now() / 1000);
}
/** /**
* Generates a ean-13 compliant string for barcode generation. * Generates a ean-13 compliant string for barcode generation.
*/ */

View File

@ -2,9 +2,10 @@ import {
IsInt, IsInt,
IsNotEmpty, IsNotEmpty,
IsOptional, IsOptional,
IsPositive,
IsString IsString
} from "class-validator"; } from "class-validator";
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; import { BeforeInsert, BeforeUpdate, Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
import { ResponseRunnerGroup } from '../responses/ResponseRunnerGroup'; import { ResponseRunnerGroup } from '../responses/ResponseRunnerGroup';
import { GroupContact } from "./GroupContact"; import { GroupContact } from "./GroupContact";
import { Runner } from "./Runner"; import { Runner } from "./Runner";
@ -46,6 +47,27 @@ export abstract class RunnerGroup {
@OneToMany(() => Runner, runner => runner.group, { nullable: true }) @OneToMany(() => Runner, runner => runner.group, { nullable: true })
runners: Runner[]; runners: Runner[];
@Column({ type: 'bigint', nullable: true, readonly: true })
@IsInt()
@IsPositive()
created_at: number;
@Column({ type: 'bigint', nullable: true })
@IsInt()
@IsPositive()
updated_at: number;
@BeforeInsert()
public setCreatedAt() {
this.created_at = Math.floor(Date.now() / 1000);
this.updated_at = Math.floor(Date.now() / 1000);
}
@BeforeUpdate()
public setUpdatedAt() {
this.updated_at = Math.floor(Date.now() / 1000);
}
/** /**
* Returns the total distance ran by this group's runners based on all their valid scans. * Returns the total distance ran by this group's runners based on all their valid scans.
*/ */

View File

@ -5,7 +5,7 @@ import {
IsPositive IsPositive
} from "class-validator"; } from "class-validator";
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; import { BeforeInsert, BeforeUpdate, Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
import { ResponseScan } from '../responses/ResponseScan'; import { ResponseScan } from '../responses/ResponseScan';
import { Runner } from "./Runner"; import { Runner } from "./Runner";
@ -40,6 +40,27 @@ export class Scan {
@IsBoolean() @IsBoolean()
valid: boolean = true; valid: boolean = true;
@Column({ type: 'bigint', nullable: true, readonly: true })
@IsInt()
@IsPositive()
created_at: number;
@Column({ type: 'bigint', nullable: true })
@IsInt()
@IsPositive()
updated_at: number;
@BeforeInsert()
public setCreatedAt() {
this.created_at = Math.floor(Date.now() / 1000);
this.updated_at = Math.floor(Date.now() / 1000);
}
@BeforeUpdate()
public setUpdatedAt() {
this.updated_at = Math.floor(Date.now() / 1000);
}
/** /**
* The scan's distance in meters. * The scan's distance in meters.
* This is the "real" value used by "normal" scans.. * This is the "real" value used by "normal" scans..

View File

@ -3,9 +3,10 @@ import {
IsInt, IsInt,
IsNotEmpty, IsNotEmpty,
IsOptional, IsOptional,
IsPositive,
IsString IsString
} from "class-validator"; } from "class-validator";
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; import { BeforeInsert, BeforeUpdate, Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { ResponseScanStation } from '../responses/ResponseScanStation'; import { ResponseScanStation } from '../responses/ResponseScanStation';
import { Track } from "./Track"; import { Track } from "./Track";
import { TrackScan } from "./TrackScan"; import { TrackScan } from "./TrackScan";
@ -78,6 +79,27 @@ export class ScanStation {
@IsBoolean() @IsBoolean()
enabled?: boolean = true; enabled?: boolean = true;
@Column({ type: 'bigint', nullable: true, readonly: true })
@IsInt()
@IsPositive()
created_at: number;
@Column({ type: 'bigint', nullable: true })
@IsInt()
@IsPositive()
updated_at: number;
@BeforeInsert()
public setCreatedAt() {
this.created_at = Math.floor(Date.now() / 1000);
this.updated_at = Math.floor(Date.now() / 1000);
}
@BeforeUpdate()
public setUpdatedAt() {
this.updated_at = Math.floor(Date.now() / 1000);
}
/** /**
* Turns this entity into it's response class. * Turns this entity into it's response class.
*/ */

View File

@ -1,5 +1,5 @@
import { IsInt, IsOptional, IsString } from "class-validator"; import { IsInt, IsOptional, IsPositive, IsString } from "class-validator";
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; import { BeforeInsert, BeforeUpdate, Column, Entity, PrimaryGeneratedColumn } from "typeorm";
import { ResponseStatsClient } from '../responses/ResponseStatsClient'; import { ResponseStatsClient } from '../responses/ResponseStatsClient';
/** /**
* Defines the StatsClient entity. * Defines the StatsClient entity.
@ -47,6 +47,27 @@ export class StatsClient {
@IsOptional() @IsOptional()
cleartextkey?: string; cleartextkey?: string;
@Column({ type: 'bigint', nullable: true, readonly: true })
@IsInt()
@IsPositive()
created_at: number;
@Column({ type: 'bigint', nullable: true })
@IsInt()
@IsPositive()
updated_at: number;
@BeforeInsert()
public setCreatedAt() {
this.created_at = Math.floor(Date.now() / 1000);
this.updated_at = Math.floor(Date.now() / 1000);
}
@BeforeUpdate()
public setUpdatedAt() {
this.updated_at = Math.floor(Date.now() / 1000);
}
/** /**
* Turns this entity into it's response class. * Turns this entity into it's response class.
*/ */

View File

@ -5,7 +5,7 @@ import {
IsPositive, IsPositive,
IsString IsString
} from "class-validator"; } from "class-validator";
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm"; import { BeforeInsert, BeforeUpdate, Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { ResponseTrack } from '../responses/ResponseTrack'; import { ResponseTrack } from '../responses/ResponseTrack';
import { ScanStation } from "./ScanStation"; import { ScanStation } from "./ScanStation";
import { TrackScan } from "./TrackScan"; import { TrackScan } from "./TrackScan";
@ -63,6 +63,27 @@ export class Track {
@OneToMany(() => TrackScan, scan => scan.track, { nullable: true }) @OneToMany(() => TrackScan, scan => scan.track, { nullable: true })
scans: TrackScan[]; scans: TrackScan[];
@Column({ type: 'bigint', nullable: true, readonly: true })
@IsInt()
@IsPositive()
created_at: number;
@Column({ type: 'bigint', nullable: true })
@IsInt()
@IsPositive()
updated_at: number;
@BeforeInsert()
public setCreatedAt() {
this.created_at = Math.floor(Date.now() / 1000);
this.updated_at = Math.floor(Date.now() / 1000);
}
@BeforeUpdate()
public setUpdatedAt() {
this.updated_at = Math.floor(Date.now() / 1000);
}
/** /**
* Turns this entity into it's response class. * Turns this entity into it's response class.
*/ */

View File

@ -3,9 +3,10 @@ import {
IsInt, IsInt,
IsNotEmpty, IsNotEmpty,
IsOptional, IsOptional,
IsPositive,
IsString IsString
} from "class-validator"; } from "class-validator";
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; import { BeforeInsert, BeforeUpdate, Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import { PermissionAction } from '../enums/PermissionAction'; import { PermissionAction } from '../enums/PermissionAction';
import { User } from './User'; import { User } from './User';
@ -53,6 +54,27 @@ export class UserAction {
@IsString() @IsString()
changed: string; changed: string;
@Column({ type: 'bigint', nullable: true, readonly: true })
@IsInt()
@IsPositive()
created_at: number;
@Column({ type: 'bigint', nullable: true })
@IsInt()
@IsPositive()
updated_at: number;
@BeforeInsert()
public setCreatedAt() {
this.created_at = Math.floor(Date.now() / 1000);
this.updated_at = Math.floor(Date.now() / 1000);
}
@BeforeUpdate()
public setUpdatedAt() {
this.updated_at = Math.floor(Date.now() / 1000);
}
/** /**
* Turns this entity into it's response class. * Turns this entity into it's response class.
*/ */

View File

@ -40,6 +40,14 @@ export class ResponseAnonymousDonation implements IResponse {
@IsInt() @IsInt()
paidAmount: number; paidAmount: number;
@IsInt()
@IsPositive()
created_at: number;
@IsInt()
@IsPositive()
updated_at: number;
/** /**
* Creates a ResponseDonation object from a scan. * Creates a ResponseDonation object from a scan.
* @param donation The donation the response shall be build for. * @param donation The donation the response shall be build for.
@ -54,5 +62,7 @@ export class ResponseAnonymousDonation implements IResponse {
else { else {
this.status = DonationStatus.PAID; this.status = DonationStatus.PAID;
} }
this.created_at = donation.created_at;
this.updated_at = donation.updated_at;
} }
} }

View File

@ -47,6 +47,14 @@ export class ResponseDonation implements IResponse {
@IsInt() @IsInt()
paidAmount: number; paidAmount: number;
@IsInt()
@IsPositive()
created_at: number;
@IsInt()
@IsPositive()
updated_at: number;
/** /**
* Creates a ResponseDonation object from a scan. * Creates a ResponseDonation object from a scan.
* @param donation The donation the response shall be build for. * @param donation The donation the response shall be build for.
@ -64,5 +72,7 @@ export class ResponseDonation implements IResponse {
else { else {
this.status = DonationStatus.PAID; this.status = DonationStatus.PAID;
} }
this.created_at = donation.created_at;
this.updated_at = donation.updated_at;
} }
} }

View File

@ -1,4 +1,4 @@
import { IsInt, IsObject, IsString } from "class-validator"; import { IsInt, IsObject, IsPositive, IsString } from "class-validator";
import { Address } from '../entities/Address'; import { Address } from '../entities/Address';
import { GroupContact } from '../entities/GroupContact'; import { GroupContact } from '../entities/GroupContact';
import { ResponseObjectType } from '../enums/ResponseObjectType'; import { ResponseObjectType } from '../enums/ResponseObjectType';
@ -64,6 +64,14 @@ export class ResponseGroupContact implements IResponse {
@IsObject() @IsObject()
address?: Address; address?: Address;
@IsInt()
@IsPositive()
created_at: number;
@IsInt()
@IsPositive()
updated_at: number;
/** /**
* Creates a ResponseGroupContact object from a contact. * Creates a ResponseGroupContact object from a contact.
* @param contact The contact the response shall be build for. * @param contact The contact the response shall be build for.
@ -82,5 +90,7 @@ export class ResponseGroupContact implements IResponse {
this.groups.push(group.toResponse()); this.groups.push(group.toResponse());
} }
} }
this.created_at = contact.created_at;
this.updated_at = contact.updated_at;
} }
} }

View File

@ -1,4 +1,4 @@
import { IsInt, IsObject, IsOptional, IsString } from "class-validator"; import { IsInt, IsObject, IsOptional, IsPositive, IsString } from "class-validator";
import { Address } from '../entities/Address'; import { Address } from '../entities/Address';
import { Participant } from '../entities/Participant'; import { Participant } from '../entities/Participant';
import { ResponseObjectType } from '../enums/ResponseObjectType'; import { ResponseObjectType } from '../enums/ResponseObjectType';
@ -63,6 +63,14 @@ export abstract class ResponseParticipant implements IResponse {
@IsObject() @IsObject()
address?: Address; address?: Address;
@IsInt()
@IsPositive()
created_at: number;
@IsInt()
@IsPositive()
updated_at: number;
/** /**
* Creates a ResponseParticipant object from a participant. * Creates a ResponseParticipant object from a participant.
* @param participant The participant the response shall be build for. * @param participant The participant the response shall be build for.
@ -76,5 +84,7 @@ export abstract class ResponseParticipant implements IResponse {
this.phone = participant.phone; this.phone = participant.phone;
this.email = participant.email; this.email = participant.email;
this.address = participant.address; this.address = participant.address;
this.created_at = participant.created_at;
this.updated_at = participant.updated_at;
} }
} }

View File

@ -2,7 +2,8 @@ import {
IsEnum, IsEnum,
IsInt, IsInt,
IsNotEmpty, IsNotEmpty,
IsObject IsObject,
IsPositive
} from "class-validator"; } from "class-validator";
import { Permission } from '../entities/Permission'; import { Permission } from '../entities/Permission';
import { PermissionAction } from '../enums/PermissionAction'; import { PermissionAction } from '../enums/PermissionAction';
@ -48,6 +49,14 @@ export class ResponsePermission implements IResponse {
@IsEnum(PermissionAction) @IsEnum(PermissionAction)
action: PermissionAction; action: PermissionAction;
@IsInt()
@IsPositive()
created_at: number;
@IsInt()
@IsPositive()
updated_at: number;
/** /**
* Creates a ResponsePermission object from a permission. * Creates a ResponsePermission object from a permission.
* @param permission The permission the response shall be build for. * @param permission The permission the response shall be build for.
@ -57,5 +66,7 @@ export class ResponsePermission implements IResponse {
this.principal = permission.principal.toResponse(); this.principal = permission.principal.toResponse();
this.target = permission.target; this.target = permission.target;
this.action = permission.action; this.action = permission.action;
this.created_at = permission.created_at;
this.updated_at = permission.updated_at;
} }
} }

View File

@ -1,5 +1,6 @@
import { import {
IsInt IsInt,
IsPositive
} from "class-validator"; } from "class-validator";
import { Principal } from '../entities/Principal'; import { Principal } from '../entities/Principal';
import { ResponseObjectType } from '../enums/ResponseObjectType'; import { ResponseObjectType } from '../enums/ResponseObjectType';
@ -22,11 +23,21 @@ export abstract class ResponsePrincipal implements IResponse {
@IsInt() @IsInt()
id: number; id: number;
@IsInt()
@IsPositive()
created_at: number;
@IsInt()
@IsPositive()
updated_at: number;
/** /**
* Creates a ResponsePrincipal object from a principal. * Creates a ResponsePrincipal object from a principal.
* @param principal The principal the response shall be build for. * @param principal The principal the response shall be build for.
*/ */
public constructor(principal: Principal) { public constructor(principal: Principal) {
this.id = principal.id; this.id = principal.id;
this.created_at = principal.created_at;
this.updated_at = principal.updated_at;
} }
} }

View File

@ -27,6 +27,13 @@ export class ResponseRunner extends ResponseParticipant implements IResponse {
@IsInt() @IsInt()
distance: number; distance: number;
/**
* The runner's current donation amount based on distance.
* Only available for queries for single runners.
*/
@IsInt()
donationAmount: number;
/** /**
* The runner's group. * The runner's group.
*/ */
@ -50,6 +57,10 @@ export class ResponseRunner extends ResponseParticipant implements IResponse {
else { this.distance = runner.validScans.reduce((sum, current) => sum + current.distance, 0); } else { this.distance = runner.validScans.reduce((sum, current) => sum + current.distance, 0); }
if (runner.group) { this.group = runner.group.toResponse(); } if (runner.group) { this.group = runner.group.toResponse(); }
if (runner.distanceDonations) {
this.donationAmount = runner.distanceDonations.reduce((sum, current) => sum + (current.amountPerDistance * runner.distance / 1000), 0);
}
if (generateSelfServiceLink) { if (generateSelfServiceLink) {
const token = JwtCreator.createSelfService(runner); const token = JwtCreator.createSelfService(runner);
this.selfserviceLink = `${process.env.SELFSERVICE_URL}/profile/${token}`; this.selfserviceLink = `${process.env.SELFSERVICE_URL}/profile/${token}`;

View File

@ -1,4 +1,4 @@
import { IsBoolean, IsEAN, IsInt, IsNotEmpty, IsObject, IsString } from "class-validator"; import { IsBoolean, IsEAN, IsInt, IsNotEmpty, IsObject, IsPositive, IsString } from "class-validator";
import { RunnerCard } from '../entities/RunnerCard'; import { RunnerCard } from '../entities/RunnerCard';
import { ResponseObjectType } from '../enums/ResponseObjectType'; import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse'; import { IResponse } from './IResponse';
@ -42,6 +42,14 @@ export class ResponseRunnerCard implements IResponse {
@IsBoolean() @IsBoolean()
enabled: boolean = true; enabled: boolean = true;
@IsInt()
@IsPositive()
created_at: number;
@IsInt()
@IsPositive()
updated_at: number;
/** /**
* Creates a ResponseRunnerCard object from a runner card. * Creates a ResponseRunnerCard object from a runner card.
* @param card The card the response shall be build for. * @param card The card the response shall be build for.
@ -57,5 +65,7 @@ export class ResponseRunnerCard implements IResponse {
} }
this.enabled = card.enabled; this.enabled = card.enabled;
this.created_at = card.created_at;
this.updated_at = card.updated_at;
} }
} }

View File

@ -1,4 +1,4 @@
import { IsInt, IsNotEmpty, IsNumber, IsObject, IsOptional, IsString } from "class-validator"; import { IsInt, IsNotEmpty, IsNumber, IsObject, IsOptional, IsPositive, IsString } from "class-validator";
import { RunnerGroup } from '../entities/RunnerGroup'; import { RunnerGroup } from '../entities/RunnerGroup';
import { ResponseObjectType } from '../enums/ResponseObjectType'; import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse'; import { IResponse } from './IResponse';
@ -40,6 +40,14 @@ export abstract class ResponseRunnerGroup implements IResponse {
@IsNumber() @IsNumber()
total_distance: number total_distance: number
@IsInt()
@IsPositive()
created_at: number;
@IsInt()
@IsPositive()
updated_at: number;
/** /**
* Creates a ResponseRunnerGroup object from a runnerGroup. * Creates a ResponseRunnerGroup object from a runnerGroup.
* @param group The runnerGroup the response shall be build for. * @param group The runnerGroup the response shall be build for.
@ -49,5 +57,7 @@ export abstract class ResponseRunnerGroup implements IResponse {
this.name = group.name; this.name = group.name;
if (group.contact) { this.contact = group.contact.toResponse(); }; if (group.contact) { this.contact = group.contact.toResponse(); };
if (group.runners) { this.total_distance = group.runners.reduce((p, c) => p + c.distance, 0) } if (group.runners) { this.total_distance = group.runners.reduce((p, c) => p + c.distance, 0) }
this.created_at = group.created_at;
this.updated_at = group.updated_at;
} }
} }

View File

@ -41,6 +41,14 @@ export class ResponseScan implements IResponse {
@IsPositive() @IsPositive()
distance: number; distance: number;
@IsInt()
@IsPositive()
created_at: number;
@IsInt()
@IsPositive()
updated_at: number;
/** /**
* Creates a ResponseScan object from a scan. * Creates a ResponseScan object from a scan.
* @param scan The scan the response shall be build for. * @param scan The scan the response shall be build for.
@ -50,5 +58,7 @@ export class ResponseScan implements IResponse {
if (scan.runner) { this.runner = scan.runner.toResponse(); } if (scan.runner) { this.runner = scan.runner.toResponse(); }
this.distance = scan.distance; this.distance = scan.distance;
this.valid = scan.valid; this.valid = scan.valid;
this.created_at = scan.created_at;
this.updated_at = scan.updated_at;
} }
} }

View File

@ -1,5 +1,4 @@
import { import {
IsBoolean, IsBoolean,
IsInt, IsInt,
@ -8,6 +7,7 @@ import {
IsObject, IsObject,
IsOptional, IsOptional,
IsPositive,
IsString IsString
} from "class-validator"; } from "class-validator";
import { ScanStation } from '../entities/ScanStation'; import { ScanStation } from '../entities/ScanStation';
@ -63,6 +63,14 @@ export class ResponseScanStation implements IResponse {
@IsBoolean() @IsBoolean()
enabled?: boolean = true; enabled?: boolean = true;
@IsInt()
@IsPositive()
created_at: number;
@IsInt()
@IsPositive()
updated_at: number;
/** /**
* Creates a ResponseStatsClient object from a statsClient. * Creates a ResponseStatsClient object from a statsClient.
* @param client The statsClient the response shall be build for. * @param client The statsClient the response shall be build for.
@ -74,5 +82,7 @@ export class ResponseScanStation implements IResponse {
this.key = "Only visible on creation."; this.key = "Only visible on creation.";
if (station.track) { this.track = station.track.toResponse(); } if (station.track) { this.track = station.track.toResponse(); }
this.enabled = station.enabled; this.enabled = station.enabled;
this.created_at = station.created_at;
this.updated_at = station.updated_at;
} }
} }

View File

@ -1,10 +1,10 @@
import { import {
IsInt, IsInt,
IsNotEmpty, IsNotEmpty,
IsOptional, IsOptional,
IsPositive,
IsString IsString
} from "class-validator"; } from "class-validator";
import { StatsClient } from '../entities/StatsClient'; import { StatsClient } from '../entities/StatsClient';
@ -49,6 +49,14 @@ export class ResponseStatsClient implements IResponse {
@IsNotEmpty() @IsNotEmpty()
prefix: string; prefix: string;
@IsInt()
@IsPositive()
created_at: number;
@IsInt()
@IsPositive()
updated_at: number;
/** /**
* Creates a ResponseStatsClient object from a statsClient. * Creates a ResponseStatsClient object from a statsClient.
* @param client The statsClient the response shall be build for. * @param client The statsClient the response shall be build for.
@ -58,5 +66,7 @@ export class ResponseStatsClient implements IResponse {
this.description = client.description; this.description = client.description;
this.prefix = client.prefix; this.prefix = client.prefix;
this.key = "Only visible on creation."; this.key = "Only visible on creation.";
this.created_at = client.created_at;
this.updated_at = client.updated_at;
} }
} }

View File

@ -1,4 +1,4 @@
import { IsInt, IsOptional, IsString } from "class-validator"; import { IsInt, IsOptional, IsPositive, IsString } from "class-validator";
import { TrackLapTimeCantBeNegativeError } from '../../errors/TrackErrors'; import { TrackLapTimeCantBeNegativeError } from '../../errors/TrackErrors';
import { Track } from '../entities/Track'; import { Track } from '../entities/Track';
import { ResponseObjectType } from '../enums/ResponseObjectType'; import { ResponseObjectType } from '../enums/ResponseObjectType';
@ -40,6 +40,14 @@ export class ResponseTrack implements IResponse {
@IsOptional() @IsOptional()
minimumLapTime?: number; minimumLapTime?: number;
@IsInt()
@IsPositive()
created_at: number;
@IsInt()
@IsPositive()
updated_at: number;
/** /**
* Creates a ResponseTrack object from a track. * Creates a ResponseTrack object from a track.
* @param track The track the response shall be build for. * @param track The track the response shall be build for.
@ -52,5 +60,7 @@ export class ResponseTrack implements IResponse {
if (this.minimumLapTime < 0) { if (this.minimumLapTime < 0) {
throw new TrackLapTimeCantBeNegativeError(); throw new TrackLapTimeCantBeNegativeError();
} }
this.created_at = track.created_at;
this.updated_at = track.updated_at;
} }
} }