From 02f39e40e1db4460337aabdc81bd4f21681cf6b5 Mon Sep 17 00:00:00 2001 From: Quildra Date: Thu, 21 Dec 2023 17:01:18 +0000 Subject: [PATCH] Use class-transformer for more type saftey --- package-lock.json | 12 ++++ packages/bridge-ui/package.json | 2 + .../race-details/race-details.component.html | 14 ++-- .../race-details/race-details.component.ts | 34 ++-------- .../season-card/season-card.component.html | 2 +- .../season-card/season-card.component.ts | 10 --- .../season-standings.component.html | 4 +- .../season-standings.component.ts | 20 ------ .../bridge-ui/src/app/models/race.model.ts | 37 ++++++++++- .../src/app/models/raceEntry.model.ts | 21 +++++- .../bridge-ui/src/app/models/racer.model.ts | 28 ++++++++ .../src/app/models/season-standing.model.ts | 12 +++- .../bridge-ui/src/app/models/season.model.ts | 27 +++++++- .../bridge-ui/src/app/models/user.model.ts | 51 +++++++++++++++ .../src/app/pages/user/user.component.html | 4 +- .../src/app/pages/user/user.component.ts | 3 +- .../src/app/services/race-result.service.ts | 65 ------------------- .../src/app/services/racers.service.ts | 18 ----- .../src/app/services/races.service.ts | 23 ++++--- .../src/app/services/seasons.service.ts | 49 ++------------ .../services/server-side-events.service.ts | 2 +- .../src/app/services/users.service.ts | 18 ++--- .../src/app/services/utils.service.ts | 15 +++++ packages/bridge-ui/src/main.ts | 1 + 24 files changed, 248 insertions(+), 224 deletions(-) create mode 100644 packages/bridge-ui/src/app/models/user.model.ts create mode 100644 packages/bridge-ui/src/app/services/utils.service.ts diff --git a/package-lock.json b/package-lock.json index 1cff74d..03a1824 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9474,6 +9474,11 @@ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -24018,9 +24023,11 @@ "@angular/router": "^17.0.0", "@auth0/auth0-angular": "^2.2.1", "bridge-shared": "^1.0.0", + "class-transformer": "^0.5.1", "eventemitter3": "^5.0.1", "jwt-decode": "^4.0.0", "mat-icon-button-sizes": "^1.0.6", + "reflect-metadata": "^0.2.1", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.14.2" @@ -24044,6 +24051,11 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "packages/bridge-ui/node_modules/reflect-metadata": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.1.tgz", + "integrity": "sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==" } } } diff --git a/packages/bridge-ui/package.json b/packages/bridge-ui/package.json index 508ed60..2aab66e 100644 --- a/packages/bridge-ui/package.json +++ b/packages/bridge-ui/package.json @@ -22,9 +22,11 @@ "@angular/router": "^17.0.0", "@auth0/auth0-angular": "^2.2.1", "bridge-shared": "^1.0.0", + "class-transformer": "^0.5.1", "eventemitter3": "^5.0.1", "jwt-decode": "^4.0.0", "mat-icon-button-sizes": "^1.0.6", + "reflect-metadata": "^0.2.1", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.14.2" diff --git a/packages/bridge-ui/src/app/components/race-details/race-details.component.html b/packages/bridge-ui/src/app/components/race-details/race-details.component.html index f843e04..224bd23 100644 --- a/packages/bridge-ui/src/app/components/race-details/race-details.component.html +++ b/packages/bridge-ui/src/app/components/race-details/race-details.component.html @@ -38,10 +38,10 @@ }
-

Author: {{formatMilliseconds(race.authorTime)}}

-

Gold: {{formatMilliseconds(race.goldTime)}}

-

Silver: {{formatMilliseconds(race.silverTime)}}

-

Bronze: {{formatMilliseconds(race.bronzeTime)}}

+

Author: {{utilsService.formatMilliseconds(race.authorTime)}}

+

Gold: {{utilsService.formatMilliseconds(race.goldTime)}}

+

Silver: {{utilsService.formatMilliseconds(race.silverTime)}}

+

Bronze: {{utilsService.formatMilliseconds(race.bronzeTime)}}


@@ -62,10 +62,10 @@ Name @if(element.racer.user) { - {{getRacerName(element.racer)}} + {{element.racer.getName()}} } @else { - {{getRacerName(element.racer)}} + {{element.racer.getName()}} } @@ -73,7 +73,7 @@ Time - {{formatMilliseconds(element.time)}} + {{utilsService.formatMilliseconds(element.time)}} diff --git a/packages/bridge-ui/src/app/components/race-details/race-details.component.ts b/packages/bridge-ui/src/app/components/race-details/race-details.component.ts index b66a31a..3880030 100644 --- a/packages/bridge-ui/src/app/components/race-details/race-details.component.ts +++ b/packages/bridge-ui/src/app/components/race-details/race-details.component.ts @@ -14,6 +14,8 @@ import { Race } from '../../models/race.model'; import { Racer } from '../../models/racer.model'; import { RaceEntry } from '../../models/raceEntry.model'; import { ServerSideEventsService } from '../../services/server-side-events.service'; +import { UtilsService } from '../../services/utils.service'; + @Component({ selector: 'app-race-details', @@ -75,7 +77,8 @@ export class RaceDetailsComponent implements AfterViewInit { constructor( private raceResultService: RaceResultService, private racesService: RacesService, - private sseService: ServerSideEventsService + private sseService: ServerSideEventsService, + public utilsService: UtilsService ) {} @@ -143,35 +146,6 @@ export class RaceDetailsComponent implements AfterViewInit { } } - formatMilliseconds(milliseconds: number) - { - const minutes = Math.floor(milliseconds / (1000 * 60)); - const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000); - const remainingMilliseconds = milliseconds % 1000; - - return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}:${String(remainingMilliseconds).padStart(3, '0')}`; - } - - getRacerName(racer: any): string { - if( racer != undefined) { - if( racer.name == undefined || racer.name == "" ) { - return racer.gameHandle; - } - - return racer.name + " (" + racer.gameHandle + ")" - } - - return "" - } - - getUserId(racer: any): string { - if( racer != undefined && racer.user != undefined) { - return racer.user.auth0id.replace("auth0|", ""); - } - - return "" - } - timeToMedal(time: number) { if( this.race == undefined) { return "" } if( time < this.race.authorTime ) { return "assets/medal_author.png"; } diff --git a/packages/bridge-ui/src/app/components/season-card/season-card.component.html b/packages/bridge-ui/src/app/components/season-card/season-card.component.html index 365d5da..778d640 100644 --- a/packages/bridge-ui/src/app/components/season-card/season-card.component.html +++ b/packages/bridge-ui/src/app/components/season-card/season-card.component.html @@ -5,7 +5,7 @@ {{season.subTitle}}
-

Start Date: {{getStartingDate()}}

+

Start Date: {{season.getStartingDate()}}

Number of Races: {{season.races.length}}

@if(usersService.canEditSeasons()) { diff --git a/packages/bridge-ui/src/app/pages/user/user.component.ts b/packages/bridge-ui/src/app/pages/user/user.component.ts index 80a0150..05ea38f 100644 --- a/packages/bridge-ui/src/app/pages/user/user.component.ts +++ b/packages/bridge-ui/src/app/pages/user/user.component.ts @@ -9,6 +9,7 @@ import { MatDialog } from '@angular/material/dialog'; import { UsersService } from '../../services/users.service'; import { ClaimRacerDialogComponent } from '../../components/claim-racer-dialog/claim-racer-dialog.component'; import { ActivatedRoute } from '@angular/router'; +import { User } from '../../models/user.model'; @Component({ @@ -26,7 +27,7 @@ import { ActivatedRoute } from '@angular/router'; export class UserComponent { public userId: string = ""; - public user: any = null; + public user: User | undefined | null = null; constructor( public usersService: UsersService, diff --git a/packages/bridge-ui/src/app/services/race-result.service.ts b/packages/bridge-ui/src/app/services/race-result.service.ts index 0ac14f7..47325a0 100644 --- a/packages/bridge-ui/src/app/services/race-result.service.ts +++ b/packages/bridge-ui/src/app/services/race-result.service.ts @@ -16,71 +16,6 @@ export class RaceResultService { ) { } - /* - getAllRacers(): Observable> { - return this.httpClient.get>("assets/racers.json"); - } - - getRacer(id: string): Observable { - let promise = new Promise((resolve, reject) => { - this.httpClient.get>("assets/racers.json").subscribe(data => { - for(let racer of data) { - if(racer.id == id) { - resolve(racer); - } - } - reject(undefined); - }); - }); - return from(promise); - } - */ - - getRaceResultsForRacer(racerId: string): Observable { - let promise = new Promise((resolve, reject) => { - this.httpClient.get>("assets/raceEntry.json").subscribe(data => { - let results: RaceEntry[] = []; - for(let result of data) { - if(result.racer_id == racerId) { - results.push(result); - } - } - resolve(results); - }); - }); - return from(promise); - } - - getRaceResultsForRace(raceId: string): Observable { - let promise = new Promise((resolve, reject) => { - this.httpClient.get>("assets/raceEntry.json").subscribe(data => { - let results: RaceEntry[] = []; - for(let result of data) { - if(result.race_id == raceId) { - results.push(result); - } - } - resolve(results); - }); - }); - return from(promise); - } - - getRaceResultsForRacerInRace(racerId: string, raceId: string): Observable { - let promise = new Promise((resolve, reject) => { - this.httpClient.get>("assets/raceEntry.json").subscribe(data => { - let results: RaceEntry[] = []; - for(let result of data) { - if(result.race_id == raceId && result.racer_id == racerId) { - results.push(result); - } - } - resolve(results); - }); - }); - return from(promise); - } - getGhost(raceResultId: string) { const httpOptions = { responseType: 'blob' as 'json' diff --git a/packages/bridge-ui/src/app/services/racers.service.ts b/packages/bridge-ui/src/app/services/racers.service.ts index 1a0f2e9..88ba7d7 100644 --- a/packages/bridge-ui/src/app/services/racers.service.ts +++ b/packages/bridge-ui/src/app/services/racers.service.ts @@ -12,22 +12,4 @@ export class RacersService { constructor(private httpClient: HttpClient) { } - - getAllRacers(): Observable> { - return this.httpClient.get>("assets/racers.json"); - } - - getRacer(id: string): Observable { - let promise = new Promise((resolve, reject) => { - this.httpClient.get>("assets/racers.json").subscribe(data => { - for(let racer of data) { - if(racer.id == id) { - resolve(racer); - } - } - reject(undefined); - }); - }); - return from(promise); - } } diff --git a/packages/bridge-ui/src/app/services/races.service.ts b/packages/bridge-ui/src/app/services/races.service.ts index 2a8a7b8..ad785ca 100644 --- a/packages/bridge-ui/src/app/services/races.service.ts +++ b/packages/bridge-ui/src/app/services/races.service.ts @@ -1,9 +1,11 @@ import { Injectable } from '@angular/core'; import { HttpClient } from "@angular/common/http"; import { Observable, from } from "rxjs"; +import { plainToClass } from 'class-transformer'; import { Race } from '../models/race.model'; import { ServerEndpointService } from './server-endpoint.service'; +import { RaceEntry } from '../models/raceEntry.model'; @Injectable({ providedIn: 'root' @@ -16,16 +18,19 @@ export class RacesService { ) { } - getAllRaces(): Observable> { - return this.httpClient.get>("assets/races.json"); - } - getRace(id: string) { - return this.httpClient.get(this.serverEndpointService.getCurrentEndpoint()+"races/"+id); - } - - getRaceResults(id: string) { - return this.httpClient.get(this.serverEndpointService.getCurrentEndpoint()+"races/"+id+"/results"); + //return this.httpClient.get(this.serverEndpointService.getCurrentEndpoint()+"races/"+id); + throw new Error(); + } + + getRaceResults(id: string): Observable> { + let promise = new Promise>((resolve, reject) =>{ + this.httpClient.get>(this.serverEndpointService.getCurrentEndpoint()+"races/"+id+"/results").subscribe(data => { + let _raceEntries = plainToClass(RaceEntry, data, { excludeExtraneousValues: true, enableCircularCheck: true }); + resolve(_raceEntries); + }); + }); + return from(promise); } getMapInfo(mapUID: string) { diff --git a/packages/bridge-ui/src/app/services/seasons.service.ts b/packages/bridge-ui/src/app/services/seasons.service.ts index c18939a..aaf0c4a 100644 --- a/packages/bridge-ui/src/app/services/seasons.service.ts +++ b/packages/bridge-ui/src/app/services/seasons.service.ts @@ -2,6 +2,8 @@ import { Injectable } from '@angular/core'; import { HttpClient } from "@angular/common/http"; import { Observable, from } from "rxjs"; +import { plainToClass } from 'class-transformer'; + import { Season } from '../models/season.model'; import { ServerEndpointService } from './server-endpoint.service'; @@ -20,60 +22,21 @@ export class SeasonsService { ) {} - private convertToClientSeason(data: any): Season { - let _season = new Season(); - _season.id = data.id.toString(); - _season.title = data.title; - _season.subTitle = data.subTitle; - _season.startingDate = new Date(data.startingDate); - _season.racers = []; - _season.races = data.races; - _season.standings = data.standings; - console.log(_season); - return _season; - } - getAllSeasons(): Observable> { - if(this.useTestData) { - return this.httpClient.get>("assets/Seasons.json"); - } - let promise = new Promise>((resolve, reject) =>{ this.httpClient.get>(this.serverEndpointService.getCurrentEndpoint()+"seasons/").subscribe(data => { - let seasons: Season[] = []; - for(let server_season of data) { - let _season = this.convertToClientSeason(server_season); - seasons.push(_season) - console.log(_season) - console.log(server_season) - } - resolve(seasons); - //reject(undefined); + let _seasons = plainToClass(Season, data, { excludeExtraneousValues: true, enableCircularCheck: true }); + resolve(_seasons); }); }); return from(promise); } getSeason(id: string): Observable { - if(this.useTestData) { - let promise = new Promise((resolve, reject) =>{ - this.httpClient.get>("assets/Seasons.json").subscribe(data => { - for(let season of data) { - if(season.id.toString() == id) { - resolve(season); - } - } - reject(undefined); - }); - }); - return from(promise); - } - let promise = new Promise((resolve, reject) =>{ this.httpClient.get>(this.serverEndpointService.getCurrentEndpoint()+"seasons/"+id).subscribe(data => { - let _season = this.convertToClientSeason(data); - console.log(data); - console.log(_season); + let _seasons = plainToClass(Season, data, { excludeExtraneousValues: true, enableCircularCheck: true }); + let _season = _seasons instanceof Season ? _seasons : _seasons[0] resolve(_season); }); }); diff --git a/packages/bridge-ui/src/app/services/server-side-events.service.ts b/packages/bridge-ui/src/app/services/server-side-events.service.ts index 9eafc0f..f2b73ab 100644 --- a/packages/bridge-ui/src/app/services/server-side-events.service.ts +++ b/packages/bridge-ui/src/app/services/server-side-events.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { ServerEndpointService } from './server-endpoint.service'; -//var EventEmitter2 = require('eventemitter2'); + import { EventEmitter } from 'eventemitter3'; @Injectable({ diff --git a/packages/bridge-ui/src/app/services/users.service.ts b/packages/bridge-ui/src/app/services/users.service.ts index 4efb1cf..3a460d9 100644 --- a/packages/bridge-ui/src/app/services/users.service.ts +++ b/packages/bridge-ui/src/app/services/users.service.ts @@ -1,10 +1,13 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { AuthService, User } from '@auth0/auth0-angular'; +import { AuthService } from '@auth0/auth0-angular'; import { Observable, from } from "rxjs"; import * as jwt_decode from 'jwt-decode'; +import { plainToClass, plainToInstance } from 'class-transformer'; + import { ServerEndpointService } from './server-endpoint.service'; +import { User } from '../models/user.model'; @Injectable({ providedIn: 'root' @@ -13,7 +16,7 @@ export class UsersService { isAuthenticated = false; permissions: string[] = []; - localUser: User | null | undefined = null; + localUser: any | null | undefined = null; additionalInfo : any = null; constructor( @@ -52,13 +55,12 @@ export class UsersService { } getUserdetails(id: string) { - let promise = new Promise((resolve, reject) => { + let promise = new Promise((resolve, reject) => { - this.httpClient.get>(this.serverEndpointService.getCurrentEndpoint()+"users/"+"auth0|"+id).subscribe(data => { - let _season = data; //this.convertToClientSeason(data); - console.log(data); - console.log(_season); - resolve(_season); + this.httpClient.get(this.serverEndpointService.getCurrentEndpoint()+"users/"+"auth0|"+id).subscribe(data => { + let user = plainToClass(User, data, { excludeExtraneousValues: true, enableCircularCheck: true }); + let val = user instanceof User ? user : user[0] + resolve(val); }); }); return from(promise); diff --git a/packages/bridge-ui/src/app/services/utils.service.ts b/packages/bridge-ui/src/app/services/utils.service.ts new file mode 100644 index 0000000..f185f1c --- /dev/null +++ b/packages/bridge-ui/src/app/services/utils.service.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class UtilsService { + formatMilliseconds(milliseconds: number) + { + const minutes = Math.floor(milliseconds / (1000 * 60)); + const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000); + const remainingMilliseconds = milliseconds % 1000; + + return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}:${String(remainingMilliseconds).padStart(3, '0')}`; + } +} diff --git a/packages/bridge-ui/src/main.ts b/packages/bridge-ui/src/main.ts index 35b00f3..56566d8 100644 --- a/packages/bridge-ui/src/main.ts +++ b/packages/bridge-ui/src/main.ts @@ -1,4 +1,5 @@ import { bootstrapApplication } from '@angular/platform-browser'; +import 'reflect-metadata'; import { appConfig } from './app/app.config'; import { AppComponent } from './app/app.component';