Spaces:
Sleeping
Sleeping
| import { Injectable } from '@angular/core'; | |
| import { environment } from '../../../environments/environment'; | |
| import {BehaviorSubject, Observable, OperatorFunction, shareReplay, throwError, timer} from 'rxjs'; | |
| import { HttpClient, HttpErrorResponse } from '@angular/common/http'; | |
| import { StateModel } from '../models/state.model'; | |
| import { AuthenticationService } from './authentication.service'; | |
| import { catchError, retry } from 'rxjs/operators'; | |
| ({ | |
| providedIn: 'root', | |
| }) | |
| export class AppStateService { | |
| private readonly _apiUrl = environment.apiUrl; | |
| private readonly _state = new BehaviorSubject<StateModel>({ | |
| leaderboards: [], | |
| submissions: [], | |
| datasets: [], | |
| tasks: [], | |
| controlPanelSubmissions: [], | |
| adminSessionStatus: '', | |
| }); | |
| public readonly state$ = this._state.asObservable(); | |
| constructor(public http: HttpClient, public authService: AuthenticationService) { | |
| console.log(this._apiUrl); | |
| this.refreshTasks(); | |
| this.refreshDatasets(); | |
| this.refreshLeaderboards(); | |
| this.refreshSubmissions(); | |
| authService.$authStatus.subscribe((status: string) => { | |
| this._setState({ | |
| ...this.getState(), | |
| adminSessionStatus: status, | |
| }); | |
| }); | |
| } | |
| private _setState(state: StateModel): void { | |
| this._state.next(state); | |
| } | |
| public getState(): StateModel { | |
| return this._state.getValue(); | |
| } | |
| private makeRequest<T>(request: Observable<T>, stateKey?: keyof StateModel, callback?: (data: any) => void) { | |
| let obs = request.pipe( | |
| this.retryStrategy(), | |
| shareReplay(1), | |
| catchError(this.handleRequestError.bind(this)) | |
| ); | |
| obs.subscribe( | |
| (data) => { | |
| if (callback) { | |
| callback(data); | |
| } else if (stateKey) { | |
| this._setState({ | |
| ...this.getState(), | |
| [stateKey]: data | |
| }); | |
| } | |
| } | |
| ); | |
| return obs; | |
| } | |
| private handleRequestError(error: any): Observable<never> { | |
| let errorMessage = 'An unknown error occurred!'; | |
| if (error.error instanceof ErrorEvent) { | |
| errorMessage = `Client Error: ${error.error.message}`; | |
| } else { | |
| switch (error.status) { | |
| case 400: | |
| errorMessage = `Bad Request: ${error.message}`; | |
| break; | |
| case 401: | |
| errorMessage = 'Unauthorized: Please log in again.'; | |
| this.authService.logout(); | |
| break; | |
| case 404: | |
| errorMessage = `Not Found: The requested resource was not found.`; | |
| break; | |
| case 500: | |
| errorMessage = `Internal Server Error: Please try again later.`; | |
| break; | |
| case 503: | |
| errorMessage = `Service Unavailable: Please try again later.`; | |
| break; | |
| default: | |
| errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`; | |
| break; | |
| } | |
| } | |
| // Rethrow the error so it can be handled globally | |
| return throwError(() => new Error(errorMessage)); | |
| } | |
| // Improved retry strategy with exponential backoff and jitter | |
| private retryStrategy<T>() { | |
| return <OperatorFunction<T, T>>((source) => | |
| source.pipe( | |
| retry({ | |
| count: 3, // Maximum of 3 retry attempts | |
| delay: (error, retryCount) => { | |
| if (![500, 503].includes(error.status)) { | |
| // Do not retry for errors other than 500 and 503 | |
| return throwError(() => error); | |
| } | |
| // Exponential backoff with jitter | |
| const jitter = Math.random() * 500; // Jitter value between 0-500ms | |
| const backoffDelay = Math.pow(2, retryCount) * 1000 + jitter; // Exponential backoff | |
| console.log(`Retrying request after ${backoffDelay}ms (attempt #${retryCount})`); | |
| return timer(backoffDelay); | |
| } | |
| }) | |
| ) | |
| ); | |
| } | |
| // ======================== | |
| // Public API | |
| // ======================== | |
| public submit( | |
| modelName: string, | |
| modelLink: string, | |
| teamName: string, | |
| contactEmail: string, | |
| task: string, | |
| dataset: string, | |
| isPublic: boolean, | |
| fileContent: string | |
| ) { | |
| return this.makeRequest( | |
| this.http.post<Object>(`${this._apiUrl}/submission/${task}/${dataset}`, { | |
| modelName, | |
| modelLink, | |
| teamName, | |
| contactEmail, | |
| isPublic, | |
| fileContent, | |
| }) | |
| ); | |
| } | |
| public refreshTasks() { | |
| return this.makeRequest(this.http.get(this._apiUrl + '/tasks'), 'tasks'); | |
| } | |
| public refreshDatasets() { | |
| return this.makeRequest(this.http.get(this._apiUrl + '/datasets'), 'datasets'); | |
| } | |
| public refreshLeaderboards() { | |
| return this.makeRequest(this.http.get(this._apiUrl + '/leaderboards'), 'leaderboards'); | |
| } | |
| public refreshSubmissions() { | |
| return this.makeRequest(this.http.get(this._apiUrl + '/submissions'), 'submissions'); | |
| } | |
| public authenticate(password: string) { | |
| let obs = this.authService.login(password); | |
| obs.subscribe( | |
| next => { | |
| console.log('Login successful'); | |
| this._setState({ | |
| ...this.getState(), | |
| adminSessionStatus: 'authenticated', | |
| }); | |
| }); | |
| return obs; | |
| } | |
| public refreshControlPanel() { | |
| const headers = this.authService.getAuthHeaders(); | |
| return this.makeRequest( | |
| this.http.get(`${this._apiUrl}/control-panel-submissions`, { headers }), | |
| 'controlPanelSubmissions' | |
| ); | |
| } | |
| public updateSubmission(entry: any) { | |
| const headers = this.authService.getAuthHeaders(); | |
| return this.makeRequest( | |
| this.http.put(`${this._apiUrl}/submission/${entry.id}`, entry, { headers }), | |
| undefined, | |
| () => this.refreshControlPanel() // Refresh control panel after update | |
| ); | |
| } | |
| public deleteSubmission(id: number) { | |
| const headers = this.authService.getAuthHeaders(); | |
| return this.makeRequest( | |
| this.http.delete(`${this._apiUrl}/submission/${id}`, { headers }), | |
| undefined, | |
| () => this.refreshControlPanel() // Refresh control panel after deletion | |
| ); | |
| } | |
| } | |