Browse Source

Added upload error handling

new_auth
Quildra 2 years ago
parent
commit
895c281f61
  1. 10
      packages/bridge-server/src/upload/upload.service.ts
  2. 2
      packages/bridge-ui/src/app/app.config.ts
  3. 1
      packages/bridge-ui/src/app/components/race-details/race-details.component.html
  4. 1
      packages/bridge-ui/src/app/components/upload-replay-dialog/upload-replay-dialog.component.html
  5. 8
      packages/bridge-ui/src/app/components/upload-replay-dialog/upload-replay-dialog.component.ts
  6. 49
      packages/bridge-ui/src/app/interceptors/snackbar.interceptor.ts
  7. 6
      packages/bridge-ui/src/styles.scss

10
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 { RacersService } from 'src/racers/racers.service';
import { RaceResultsService } from 'src/race-results/race-results.service'; import { RaceResultsService } from 'src/race-results/race-results.service';
import { RacesService } from 'src/races/races.service'; import { RacesService } from 'src/races/races.service';
@ -49,15 +49,23 @@ export class UploadService {
} }
if(race.endDate.getTime() < Date.now()) { if(race.endDate.getTime() < Date.now()) {
throw new NotAcceptableException('RaceClosed', {cause: new Error(), description: 'Race closed for new entries.'})
console.log("Too Late!") console.log("Too Late!")
return; return;
} }
if(Date.now() < race.startDate.getTime()) { if(Date.now() < race.startDate.getTime()) {
throw new NotAcceptableException('RaceNotOpen', {cause: new Error(), description: 'Race not yet open for entries.'})
console.log("Too Soon!") console.log("Too Soon!")
return; 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); let result = await this.raceResultsService.create(race.id, currentRacer.id, replay.bestTime, file.path);
console.log(result); console.log(result);

2
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 { routes } from './app.routes';
import { AuthInterceptor } from './interceptors/auth.interceptor'; import { AuthInterceptor } from './interceptors/auth.interceptor';
import { SnackbarInterceptor } from './interceptors/snackbar.interceptor';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
@ -16,5 +17,6 @@ export const appConfig: ApplicationConfig = {
), ),
importProvidersFrom(MatNativeDateModule), importProvidersFrom(MatNativeDateModule),
{provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true}, {provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: SnackbarInterceptor, multi: true},
] ]
}; };

1
packages/bridge-ui/src/app/components/race-details/race-details.component.html

@ -41,7 +41,6 @@
<div><img class="inline-medal" src="/assets/medal_bronze.png"/><p>Bronze: {{formatMilliseconds(race.bronzeTime)}}</p></div> <div><img class="inline-medal" src="/assets/medal_bronze.png"/><p>Bronze: {{formatMilliseconds(race.bronzeTime)}}</p></div>
</div> </div>
</div> </div>
<mat-divider></mat-divider>
<br/> <br/>
<div> <div>
<table mat-table [dataSource]="sortedResults" class="mat-elevation-z8 results-table"> <table mat-table [dataSource]="sortedResults" class="mat-elevation-z8 results-table">

1
packages/bridge-ui/src/app/components/upload-replay-dialog/upload-replay-dialog.component.html

@ -1,6 +1,7 @@
<div class="signInForm"> <div class="signInForm">
<input type="file" accept=".gbx" class="file-input" (change)="onFileSelected($event)" #fileUpload> <input type="file" accept=".gbx" class="file-input" (change)="onFileSelected($event)" #fileUpload>
<h1 mat-dialog-title>Replay Upload</h1> <h1 mat-dialog-title>Replay Upload</h1>
<p>Replay files can be found in Documents/Trackmania/Replays/My Replays</p>
<div mat-dialog-content class="full-width"> <div mat-dialog-content class="full-width">
<mat-label>File</mat-label> <mat-label>File</mat-label>
{{fileName || "No file uploaded yet."}} {{fileName || "No file uploaded yet."}}

8
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 { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatSnackBar } from '@angular/material/snack-bar';
import { ReplaysService } from '../../services/replays.service'; import { ReplaysService } from '../../services/replays.service';
import { catchError, throwError } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';
@Component({ @Component({
selector: 'app-upload-replay-dialog', selector: 'app-upload-replay-dialog',
@ -38,12 +41,15 @@ export class UploadReplayDialogComponent {
fileName: string = ""; fileName: string = "";
file?: File; file?: File;
seasonId: string; seasonId: string;
errors: string[];
constructor( constructor(
private replaysService: ReplaysService, private replaysService: ReplaysService,
@Inject(MAT_DIALOG_DATA) additionalData: any, @Inject(MAT_DIALOG_DATA) additionalData: any,
private _snackBar: MatSnackBar
) { ) {
this.seasonId = additionalData.seasonId; this.seasonId = additionalData.seasonId;
this.errors = [];
} }
onFileSelected(event: any) { onFileSelected(event: any) {
@ -64,6 +70,8 @@ export class UploadReplayDialogComponent {
formData.append("file", this.file); formData.append("file", this.file);
let local = this.seasonId; let local = this.seasonId;
formData.append("seasonId", local); formData.append("seasonId", local);
let filetime = this.file.lastModified;
formData.append("fileTime", filetime.toString());
const upload$ = this.replaysService.uploadReplay(formData); const upload$ = this.replaysService.uploadReplay(formData);
upload$.subscribe(); upload$.subscribe();
} }

49
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<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
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);
})
);
}
}

6
packages/bridge-ui/src/styles.scss

@ -56,3 +56,9 @@ body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
.docs-markdown code { .docs-markdown code {
padding: 3px padding: 3px
} }
.errorSnack {
--mdc-snackbar-container-color: #f44336;
--mdc-snackbar-supporting-text-color: #fff;
--mat-snack-bar-button-color: #fff;
}
Loading…
Cancel
Save