From 895c281f61c484d377d3a4347ff3078eb8c0df18 Mon Sep 17 00:00:00 2001 From: Quildra Date: Sun, 26 Nov 2023 13:52:47 +0000 Subject: [PATCH] Added upload error handling --- .../src/upload/upload.service.ts | 10 +++- packages/bridge-ui/src/app/app.config.ts | 2 + .../race-details/race-details.component.html | 1 - .../upload-replay-dialog.component.html | 1 + .../upload-replay-dialog.component.ts | 8 +++ .../app/interceptors/snackbar.interceptor.ts | 49 +++++++++++++++++++ packages/bridge-ui/src/styles.scss | 6 +++ 7 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 packages/bridge-ui/src/app/interceptors/snackbar.interceptor.ts diff --git a/packages/bridge-server/src/upload/upload.service.ts b/packages/bridge-server/src/upload/upload.service.ts index e182233..bc022c6 100644 --- a/packages/bridge-server/src/upload/upload.service.ts +++ b/packages/bridge-server/src/upload/upload.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable, NotAcceptableException } from '@nestjs/common'; import { RacersService } from 'src/racers/racers.service'; import { RaceResultsService } from 'src/race-results/race-results.service'; import { RacesService } from 'src/races/races.service'; @@ -49,15 +49,23 @@ export class UploadService { } if(race.endDate.getTime() < Date.now()) { + throw new NotAcceptableException('RaceClosed', {cause: new Error(), description: 'Race closed for new entries.'}) console.log("Too Late!") return; } if(Date.now() < race.startDate.getTime()) { + throw new NotAcceptableException('RaceNotOpen', {cause: new Error(), description: 'Race not yet open for entries.'}) console.log("Too Soon!") return; } + if(body.fileTime < race.startDate.getTime()) { + throw new NotAcceptableException('EarlyReplay', {cause: new Error(), description: 'Replay generated before the race started.'}) + console.log("Race Too Soon!") + return; + } + let result = await this.raceResultsService.create(race.id, currentRacer.id, replay.bestTime, file.path); console.log(result); diff --git a/packages/bridge-ui/src/app/app.config.ts b/packages/bridge-ui/src/app/app.config.ts index 632dae6..35c1101 100644 --- a/packages/bridge-ui/src/app/app.config.ts +++ b/packages/bridge-ui/src/app/app.config.ts @@ -6,6 +6,7 @@ import { provideAnimations } from '@angular/platform-browser/animations'; import { routes } from './app.routes'; import { AuthInterceptor } from './interceptors/auth.interceptor'; +import { SnackbarInterceptor } from './interceptors/snackbar.interceptor'; export const appConfig: ApplicationConfig = { providers: [ @@ -16,5 +17,6 @@ export const appConfig: ApplicationConfig = { ), importProvidersFrom(MatNativeDateModule), {provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true}, + {provide: HTTP_INTERCEPTORS, useClass: SnackbarInterceptor, multi: true}, ] }; 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 b1b6514..2b6cc9f 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 @@ -41,7 +41,6 @@

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

-
diff --git a/packages/bridge-ui/src/app/components/upload-replay-dialog/upload-replay-dialog.component.html b/packages/bridge-ui/src/app/components/upload-replay-dialog/upload-replay-dialog.component.html index 9af9522..23b53c4 100644 --- a/packages/bridge-ui/src/app/components/upload-replay-dialog/upload-replay-dialog.component.html +++ b/packages/bridge-ui/src/app/components/upload-replay-dialog/upload-replay-dialog.component.html @@ -1,6 +1,7 @@

Replay Upload

+

Replay files can be found in Documents/Trackmania/Replays/My Replays

File {{fileName || "No file uploaded yet."}} diff --git a/packages/bridge-ui/src/app/components/upload-replay-dialog/upload-replay-dialog.component.ts b/packages/bridge-ui/src/app/components/upload-replay-dialog/upload-replay-dialog.component.ts index b12f447..908f26a 100644 --- a/packages/bridge-ui/src/app/components/upload-replay-dialog/upload-replay-dialog.component.ts +++ b/packages/bridge-ui/src/app/components/upload-replay-dialog/upload-replay-dialog.component.ts @@ -11,8 +11,11 @@ import { MatDialogModule } from '@angular/material/dialog'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; +import { MatSnackBar } from '@angular/material/snack-bar'; import { ReplaysService } from '../../services/replays.service'; +import { catchError, throwError } from 'rxjs'; +import { HttpErrorResponse } from '@angular/common/http'; @Component({ selector: 'app-upload-replay-dialog', @@ -38,12 +41,15 @@ export class UploadReplayDialogComponent { fileName: string = ""; file?: File; seasonId: string; + errors: string[]; constructor( private replaysService: ReplaysService, @Inject(MAT_DIALOG_DATA) additionalData: any, + private _snackBar: MatSnackBar ) { this.seasonId = additionalData.seasonId; + this.errors = []; } onFileSelected(event: any) { @@ -64,6 +70,8 @@ export class UploadReplayDialogComponent { formData.append("file", this.file); let local = this.seasonId; formData.append("seasonId", local); + let filetime = this.file.lastModified; + formData.append("fileTime", filetime.toString()); const upload$ = this.replaysService.uploadReplay(formData); upload$.subscribe(); } diff --git a/packages/bridge-ui/src/app/interceptors/snackbar.interceptor.ts b/packages/bridge-ui/src/app/interceptors/snackbar.interceptor.ts new file mode 100644 index 0000000..296d1e8 --- /dev/null +++ b/packages/bridge-ui/src/app/interceptors/snackbar.interceptor.ts @@ -0,0 +1,49 @@ + +import { Injectable } from '@angular/core'; +import { + HttpRequest, + HttpHandler, + HttpEvent, + HttpInterceptor, + HttpResponse +} from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { catchError, tap, } from 'rxjs/operators'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Injectable() +export class SnackbarInterceptor implements HttpInterceptor { + + constructor(private snackBar: MatSnackBar) { } + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + return next.handle(request).pipe( + tap(e => { + if (request.method == "POST" || request.method == "PUT") + if (e instanceof HttpResponse && e.status == 200) { + this.snackBar.open('Saved successfully.', 'close', { duration: 2000, panelClass: 'successSnack' }); + } + }), + catchError(resp => { + console.log(resp); + let message = resp.error.error + switch(resp.error.message) { + case "RaceClosed": { + message = "Upload Failed: Race closed to new uploads."; + break; + } + case "RaceNotOpen": { + message = "Upload Failed: Race not open for uploads yet."; + break; + } + case "EarlyReplay": { + message = "Upload Failed: Replay made before race open."; + break; + } + } + this.snackBar.open(message, 'dismiss', { panelClass: ['errorSnack'], verticalPosition: 'top'}); + return throwError(resp); + }) + ); + } +} \ No newline at end of file diff --git a/packages/bridge-ui/src/styles.scss b/packages/bridge-ui/src/styles.scss index e04b086..3ed6fe7 100644 --- a/packages/bridge-ui/src/styles.scss +++ b/packages/bridge-ui/src/styles.scss @@ -55,4 +55,10 @@ body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } .docs-markdown code { padding: 3px +} + +.errorSnack { + --mdc-snackbar-container-color: #f44336; + --mdc-snackbar-supporting-text-color: #fff; + --mat-snack-bar-button-color: #fff; } \ No newline at end of file