Compare commits

...

26 Commits
1.3.10 ... 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
8e6d67428c
chore(release): 1.4.0
All checks were successful
Build release images / build-container (push) Successful in 1m14s
2025-04-28 19:41:55 +02:00
7ffb7523aa
Merge branch 'CreateAnonymousDonation-dedicated-enitity-controller' into dev 2025-04-28 19:41:35 +02:00
f4bf309821
feat(donations): Implement response type to indicate possible missing donor 2025-04-28 19:35:07 +02:00
02b1cb9904
refactor(donations): Make anon prepaid 2025-04-28 19:32:06 +02:00
7697acff82
fix(donations): Move donor over to the types that need it 2025-04-28 19:25:41 +02:00
bacfc437f9
chore(release): 1.3.12
All checks were successful
Build release images / build-container (push) Successful in 1m24s
2025-04-28 11:05:10 +02:00
9875b4f392
wip 2025-04-28 11:04:22 +02:00
ce9b765b81
refactor(config): improve consola error logs 2025-04-28 11:03:33 +02:00
2ab6e985e3
refactor: make Donation.donor optional 2025-04-28 10:56:06 +02:00
d06f6a4407
chore(release): 1.3.11
All checks were successful
Build release images / build-container (push) Successful in 1m40s
2025-04-17 20:46:56 +02:00
a50d72f2f5
feat(RunnerController): add selfservice_links parameter to getRunners method 2025-04-17 20:45:28 +02:00
40 changed files with 678 additions and 51 deletions

View File

@ -2,8 +2,78 @@
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)
> 28 April 2025
- 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)
- 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)
#### [1.3.12](https://git.odit.services/lfk/backend/compare/1.3.11...1.3.12)
> 28 April 2025
- chore(release): 1.3.12 [`bacfc43`](https://git.odit.services/lfk/backend/commit/bacfc437f97cac6a20c32b79ae2d6391466f78a6)
- refactor: make Donation.donor optional [`2ab6e98`](https://git.odit.services/lfk/backend/commit/2ab6e985e356f0f3d8637d81630d191cc11b8806)
- refactor(config): improve consola error logs [`ce9b765`](https://git.odit.services/lfk/backend/commit/ce9b765b81b014623e79ce64d8d835f1f86cecf3)
#### [1.3.11](https://git.odit.services/lfk/backend/compare/1.3.10...1.3.11)
> 17 April 2025
- feat(RunnerController): add selfservice_links parameter to getRunners method [`a50d72f`](https://git.odit.services/lfk/backend/commit/a50d72f2f5281b8c28ca64a0970161a35a7af95a)
- chore(release): 1.3.11 [`d06f6a4`](https://git.odit.services/lfk/backend/commit/d06f6a44072971d1853411b255f9b49eb423b3a2)
#### [1.3.10](https://git.odit.services/lfk/backend/compare/1.3.9...1.3.10) #### [1.3.10](https://git.odit.services/lfk/backend/compare/1.3.9...1.3.10)
> 11 April 2025
- chore(release): 1.3.10 [`4723d97`](https://git.odit.services/lfk/backend/commit/4723d9738eacd63fb41f23c628fbe4181bd126de)
- feat(RunnerController.getAll): debug created_via query param filter [`1a478bd`](https://git.odit.services/lfk/backend/commit/1a478bd784e01b9d5a1c6635d1004a9535c9a0e9) - feat(RunnerController.getAll): debug created_via query param filter [`1a478bd`](https://git.odit.services/lfk/backend/commit/1a478bd784e01b9d5a1c6635d1004a9535c9a0e9)
#### [1.3.9](https://git.odit.services/lfk/backend/compare/1.3.8...1.3.9) #### [1.3.9](https://git.odit.services/lfk/backend/compare/1.3.8...1.3.9)

View File

@ -1,6 +1,6 @@
{ {
"name": "@odit/lfk-backend", "name": "@odit/lfk-backend",
"version": "1.3.10", "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

@ -1,3 +1,4 @@
import consola from 'consola';
import { config as configDotenv } from 'dotenv'; import { config as configDotenv } from 'dotenv';
import { CountryCode } from 'libphonenumber-js'; import { CountryCode } from 'libphonenumber-js';
import ValidatorJS from 'validator'; import ValidatorJS from 'validator';
@ -20,12 +21,15 @@ export const config = {
} }
let errors = 0 let errors = 0
if (typeof config.internal_port !== "number") { if (typeof config.internal_port !== "number") {
consola.error("Error: APP_PORT is not a number")
errors++ errors++
} }
if (typeof config.development !== "boolean") { if (typeof config.development !== "boolean") {
consola.error("Error: NODE_ENV is not a boolean")
errors++ errors++
} }
if (config.mailer_url == "" || config.mailer_key == "") { if (config.mailer_url == "" || config.mailer_key == "") {
consola.error("Error: invalid mailer config")
errors++; errors++;
} }
function getPhoneCodeLocale(): CountryCode { function getPhoneCodeLocale(): CountryCode {

View File

@ -4,6 +4,7 @@ import { Repository, getConnectionManager } from 'typeorm';
import { DonationIdsNotMatchingError, DonationNotFoundError } from '../errors/DonationErrors'; import { DonationIdsNotMatchingError, DonationNotFoundError } from '../errors/DonationErrors';
import { DonorNotFoundError } from '../errors/DonorErrors'; import { DonorNotFoundError } from '../errors/DonorErrors';
import { RunnerNotFoundError } from '../errors/RunnerErrors'; import { RunnerNotFoundError } from '../errors/RunnerErrors';
import { CreateAnonymousDonation } from '../models/actions/create/CreateAnonymousDonation';
import { CreateDistanceDonation } from '../models/actions/create/CreateDistanceDonation'; import { CreateDistanceDonation } from '../models/actions/create/CreateDistanceDonation';
import { CreateFixedDonation } from '../models/actions/create/CreateFixedDonation'; import { CreateFixedDonation } from '../models/actions/create/CreateFixedDonation';
import { UpdateDistanceDonation } from '../models/actions/update/UpdateDistanceDonation'; import { UpdateDistanceDonation } from '../models/actions/update/UpdateDistanceDonation';
@ -11,6 +12,7 @@ import { UpdateFixedDonation } from '../models/actions/update/UpdateFixedDonatio
import { DistanceDonation } from '../models/entities/DistanceDonation'; import { DistanceDonation } from '../models/entities/DistanceDonation';
import { Donation } from '../models/entities/Donation'; import { Donation } from '../models/entities/Donation';
import { FixedDonation } from '../models/entities/FixedDonation'; import { FixedDonation } from '../models/entities/FixedDonation';
import { ResponseAnonymousDonation } from '../models/responses/ResponseAnonymousDonation';
import { ResponseDistanceDonation } from '../models/responses/ResponseDistanceDonation'; import { ResponseDistanceDonation } from '../models/responses/ResponseDistanceDonation';
import { ResponseDonation } from '../models/responses/ResponseDonation'; import { ResponseDonation } from '../models/responses/ResponseDonation';
import { ResponseEmpty } from '../models/responses/ResponseEmpty'; import { ResponseEmpty } from '../models/responses/ResponseEmpty';
@ -35,6 +37,7 @@ export class DonationController {
@Authorized("DONATION:GET") @Authorized("DONATION:GET")
@ResponseSchema(ResponseDonation, { isArray: true }) @ResponseSchema(ResponseDonation, { isArray: true })
@ResponseSchema(ResponseDistanceDonation, { isArray: true }) @ResponseSchema(ResponseDistanceDonation, { isArray: true })
@ResponseSchema(ResponseAnonymousDonation, { isArray: true })
@OpenAPI({ description: 'Lists all donations (fixed or distance based) from all donors. <br> This includes the donations\'s runner\'s distance ran(if distance donation).' }) @OpenAPI({ description: 'Lists all donations (fixed or distance based) from all donors. <br> This includes the donations\'s runner\'s distance ran(if distance donation).' })
async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) {
let responseDonations: ResponseDonation[] = new Array<ResponseDonation>(); let responseDonations: ResponseDonation[] = new Array<ResponseDonation>();
@ -56,6 +59,7 @@ export class DonationController {
@Authorized("DONATION:GET") @Authorized("DONATION:GET")
@ResponseSchema(ResponseDonation) @ResponseSchema(ResponseDonation)
@ResponseSchema(ResponseDistanceDonation) @ResponseSchema(ResponseDistanceDonation)
@ResponseSchema(ResponseAnonymousDonation)
@ResponseSchema(DonationNotFoundError, { statusCode: 404 }) @ResponseSchema(DonationNotFoundError, { statusCode: 404 })
@OnUndefined(DonationNotFoundError) @OnUndefined(DonationNotFoundError)
@OpenAPI({ description: 'Lists all information about the donation whose id got provided. This includes the donation\'s runner\'s distance ran (if distance donation).' }) @OpenAPI({ description: 'Lists all information about the donation whose id got provided. This includes the donation\'s runner\'s distance ran (if distance donation).' })
@ -76,6 +80,17 @@ export class DonationController {
return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['donor'] })).toResponse(); return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['donor'] })).toResponse();
} }
@Post('/anonymous')
@Authorized("DONATION:CREATE")
@ResponseSchema(ResponseDonation)
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Create a anonymous donation' })
async postAnonymous(@Body({ validate: true }) createDonation: CreateAnonymousDonation) {
let donation = await createDonation.toEntity();
donation = await this.fixedDonationRepository.save(donation);
return (await this.donationRepository.findOne({ id: donation.id })).toResponse();
}
@Post('/distance') @Post('/distance')
@Authorized("DONATION:CREATE") @Authorized("DONATION:CREATE")
@ResponseSchema(ResponseDistanceDonation) @ResponseSchema(ResponseDistanceDonation)

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

@ -62,13 +62,13 @@ export class RunnerOrganizationController {
@ResponseSchema(ResponseRunner, { isArray: true }) @ResponseSchema(ResponseRunner, { isArray: true })
@ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 }) @ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Lists all runners from this org and it\'s teams (if you don\'t provide the ?onlyDirect=true param). <br> This includes the runner\'s group and distance ran.' }) @OpenAPI({ description: 'Lists all runners from this org and it\'s teams (if you don\'t provide the ?onlyDirect=true param). <br> This includes the runner\'s group and distance ran.' })
async getRunners(@Param('id') id: number, @QueryParam('onlyDirect') onlyDirect: boolean) { async getRunners(@Param('id') id: number, @QueryParam('onlyDirect') onlyDirect: boolean, @QueryParam("selfservice_links", { required: false }) selfservice_links: boolean = false) {
let responseRunners: ResponseRunner[] = new Array<ResponseRunner>(); let responseRunners: ResponseRunner[] = new Array<ResponseRunner>();
let runners: Runner[]; let runners: Runner[];
if (!onlyDirect) { runners = (await this.runnerOrganizationRepository.findOne({ id: id }, { relations: ['runners', 'runners.group', 'runners.group.parentGroup', 'runners.scans', 'runners.scans.track', 'teams', 'teams.runners', 'teams.runners.group', 'teams.runners.group.parentGroup', 'teams.runners.scans', 'teams.runners.scans.track'] })).allRunners; } if (!onlyDirect) { runners = (await this.runnerOrganizationRepository.findOne({ id: id }, { relations: ['runners', 'runners.group', 'runners.group.parentGroup', 'runners.scans', 'runners.scans.track', 'teams', 'teams.runners', 'teams.runners.group', 'teams.runners.group.parentGroup', 'teams.runners.scans', 'teams.runners.scans.track'] })).allRunners; }
else { runners = (await this.runnerOrganizationRepository.findOne({ id: id }, { relations: ['runners', 'runners.group', 'runners.group.parentGroup', 'runners.scans', 'runners.scans.track'] })).runners; } else { runners = (await this.runnerOrganizationRepository.findOne({ id: id }, { relations: ['runners', 'runners.group', 'runners.group.parentGroup', 'runners.scans', 'runners.scans.track'] })).runners; }
runners.forEach(runner => { runners.forEach(runner => {
responseRunners.push(new ResponseRunner(runner)); responseRunners.push(new ResponseRunner(runner, selfservice_links));
}); });
return responseRunners; return responseRunners;
} }

View File

@ -60,11 +60,11 @@ export class RunnerTeamController {
@ResponseSchema(ResponseRunner, { isArray: true }) @ResponseSchema(ResponseRunner, { isArray: true })
@ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 }) @ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Lists all runners from this team. <br> This includes the runner\'s group and distance ran.' }) @OpenAPI({ description: 'Lists all runners from this team. <br> This includes the runner\'s group and distance ran.' })
async getRunners(@Param('id') id: number) { async getRunners(@Param('id') id: number, @QueryParam("selfservice_links", { required: false }) selfservice_links: boolean = false) {
let responseRunners: ResponseRunner[] = new Array<ResponseRunner>(); let responseRunners: ResponseRunner[] = new Array<ResponseRunner>();
const runners = (await this.runnerTeamRepository.findOne({ id: id }, { relations: ['runners', 'runners.group', 'runners.group.parentGroup', 'runners.scans', 'runners.scans.track'] })).runners; const runners = (await this.runnerTeamRepository.findOne({ id: id }, { relations: ['runners', 'runners.group', 'runners.group.parentGroup', 'runners.scans', 'runners.scans.track'] })).runners;
runners.forEach(runner => { runners.forEach(runner => {
responseRunners.push(new ResponseRunner(runner)); responseRunners.push(new ResponseRunner(runner, selfservice_links));
}); });
return responseRunners; return responseRunners;
} }

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

@ -0,0 +1,29 @@
import { IsInt, IsPositive } from 'class-validator';
import { FixedDonation } from '../../entities/FixedDonation';
import { CreateDonation } from './CreateDonation';
/**
* This class is used to create a new FixedDonation entity from a json body (post request).
*/
export class CreateAnonymousDonation extends CreateDonation {
/**
* The donation's amount.
* The unit is your currency's smallest unit (default: euro cent).
*/
@IsInt()
@IsPositive()
amount: number;
/**
* Creates a new FixedDonation entity from this.
*/
public async toEntity(): Promise<FixedDonation> {
let newDonation = new FixedDonation;
newDonation.amount = this.amount;
newDonation.paidAmount = this.amount;
return newDonation;
}
}

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';
@ -10,6 +10,21 @@ import { CreateDonation } from './CreateDonation';
*/ */
export class CreateDistanceDonation extends CreateDonation { export class CreateDistanceDonation extends CreateDonation {
/**
* The donation's associated donor's id.
* This is important to link donations to donors.
*/
@IsInt()
@IsPositive()
donor: number;
/**
* The donation's paid amount in the smalles unit of your currency (default: euro cent).
*/
@IsInt()
@IsOptional()
paidAmount?: number;
/** /**
* The donation's associated runner's id. * The donation's associated runner's id.
* This is important to link the runner's distance ran to the donation. * This is important to link the runner's distance ran to the donation.

View File

@ -1,6 +1,5 @@
import { IsInt, IsOptional, IsPositive } from 'class-validator'; import { IsInt, IsOptional } from 'class-validator';
import { getConnection } from 'typeorm'; import { getConnection } from 'typeorm';
import { DonorNotFoundError } from '../../../errors/DonorErrors';
import { Donation } from '../../entities/Donation'; import { Donation } from '../../entities/Donation';
import { Donor } from '../../entities/Donor'; import { Donor } from '../../entities/Donor';
@ -8,17 +7,10 @@ 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 {
/**
* The donation's associated donor's id.
* This is important to link donations to donors.
*/
@IsInt() @IsInt()
@IsPositive() @IsOptional()
donor: number; donor: number;
/**
* The donation's paid amount in the smalles unit of your currency (default: euro cent).
*/
@IsInt() @IsInt()
@IsOptional() @IsOptional()
paidAmount?: number; paidAmount?: number;
@ -33,9 +25,6 @@ export abstract class CreateDonation {
*/ */
public async getDonor(): Promise<Donor> { public async getDonor(): Promise<Donor> {
const donor = await getConnection().getRepository(Donor).findOne({ id: this.donor }); const donor = await getConnection().getRepository(Donor).findOne({ id: this.donor });
if (!donor) {
throw new DonorNotFoundError();
}
return donor; return donor;
} }
} }

View File

@ -6,6 +6,21 @@ import { CreateDonation } from './CreateDonation';
* This class is used to create a new FixedDonation entity from a json body (post request). * This class is used to create a new FixedDonation entity from a json body (post request).
*/ */
export class CreateFixedDonation extends CreateDonation { export class CreateFixedDonation extends CreateDonation {
/**
* The donation's associated donor's id.
* This is important to link donations to donors.
*/
@IsInt()
@IsPositive()
donor: number;
/**
* The donation's paid amount in the smalles unit of your currency (default: euro cent).
*/
@IsInt()
paidAmount?: number;
/** /**
* The donation's amount. * The donation's amount.
* The unit is your currency's smallest unit (default: euro cent). * The unit is your currency's smallest unit (default: euro cent).

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,8 +1,8 @@
import { import {
IsInt, IsInt,
IsNotEmpty 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';
@ -24,7 +24,6 @@ export abstract class Donation {
/** /**
* The donations's donor. * The donations's donor.
*/ */
@IsNotEmpty()
@ManyToOne(() => Donor, donor => donor.donations) @ManyToOne(() => Donor, donor => donor.donations)
donor: Donor; donor: Donor;
@ -42,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

@ -84,6 +84,6 @@ export class Runner extends Participant {
* Turns this entity into it's response class. * Turns this entity into it's response class.
*/ */
public toResponse(): ResponseRunner { public toResponse(): ResponseRunner {
return new ResponseRunner(this); return new ResponseRunner(this, true);
} }
} }

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

@ -0,0 +1,68 @@
import { IsInt, IsPositive } from "class-validator";
import { Donation } from '../entities/Donation';
import { DonationStatus } from '../enums/DonationStatus';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
/**
* Defines the donation response.
*/
export class ResponseAnonymousDonation implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.DONATION;
/**
* The donation's payment status.
* Provides you with a quick indicator of it's payment status.
*/
status: DonationStatus;
/**
* The donation's id.
*/
@IsInt()
@IsPositive()
id: number;
/**
* The donation's amount in the smalles unit of your currency (default: euro cent).
*/
@IsInt()
amount: number;
/**
* The donation's paid amount in the smalles unit of your currency (default: euro cent).
*/
@IsInt()
paidAmount: number;
@IsInt()
@IsPositive()
created_at: number;
@IsInt()
@IsPositive()
updated_at: number;
/**
* Creates a ResponseDonation object from a scan.
* @param donation The donation the response shall be build for.
*/
public constructor(donation: Donation) {
this.id = donation.id;
this.amount = donation.amount;
this.paidAmount = donation.paidAmount || 0;
if (this.paidAmount < this.amount) {
this.status = DonationStatus.OPEN;
}
else {
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;
} }
} }