diff --git a/src/api/api.service.ts b/src/api/api.service.ts new file mode 100644 index 0000000..fc6b5db --- /dev/null +++ b/src/api/api.service.ts @@ -0,0 +1,158 @@ +import {catchError, mergeMap, Observable, retryWhen, timer} from "rxjs"; +import {HttpClient, HttpErrorResponse} from "@angular/common/http"; +import {NotifyColor, OpenNotifyService} from "@service/open-notify.service"; +import {environment} from "@/config/environment"; +import {Router} from "@angular/router"; +import {Injectable} from "@angular/core"; + +export function retryWithInterval(): (source: Observable) => Observable { + return (source: Observable) => + source.pipe( + retryWhen((errors: Observable) => + errors.pipe( + mergeMap((error, index) => { + if (index < (environment.maxRetry < 0 ? Infinity : environment.maxRetry - 1) && !error.status.toString().startsWith('4') && !error.status.toString().startsWith('5')) { + console.log(`Retrying after ${environment.retryDelay}ms...`); + return timer(environment.retryDelay); + } else { + if (error.status.toString().startsWith('4')) + console.error(`Server returned a client code error`); + else + console.error(`Exceeded maximum retries (${environment.maxRetry})`); + + throw error; + } + }) + ) + ) + ); +} + +/* +@Injectable({ + providedIn: 'root' +}) +*/ + +export enum AvailableVersion { + v1 +} + +@Injectable() +export default abstract class ApiService { + constructor(private http: HttpClient, private notify: OpenNotifyService, private router: Router) { + } + + private urlApi = environment.apiUrl; + protected abstract basePath: string; + protected abstract version: AvailableVersion; + + private static addQuery(endpoint: string, queryParams?: Record | null> | null): string { + const url = new URL(endpoint); + + if (queryParams) { + Object.keys(queryParams).forEach(key => { + const value = queryParams[key]; + if (value !== null && value !== undefined) { + if (typeof(value) === typeof(Array)) { + (value as Array).forEach(x => url.searchParams.append(key, x.toString())); + } + else + url.searchParams.append(key, value.toString()); + } + }); + } + + return url.href; + } + + private static combineUrls(...parts: string[]): string { + let test = parts.map(part => part.replace(/(^\/+|\/+$)/g, '')).join('/'); + console.log(test); + return test; + } + + public get(endpoint: string = '', queryParams: Record | null> | null = null): Observable { + return this.http.get(ApiService.addQuery(ApiService.combineUrls(this.urlApi, AvailableVersion[this.version], this.basePath, endpoint), queryParams), {withCredentials: true}).pipe( + retryWithInterval(), + catchError(error => { + this.handleError(error); + throw error; + }) + ); + } + + public post(endpoint: string, data: any, queryParams: Record | null> | null = null): Observable { + return this.http.post(ApiService.addQuery(ApiService.combineUrls(this.urlApi, AvailableVersion[this.version], this.basePath, endpoint), queryParams), data, {withCredentials: true}).pipe( + retryWithInterval(), + catchError(error => { + this.handleError(error); + throw error; + }) + ); + } + + public put(endpoint: string, data: any, queryParams: Record | null> | null = null): Observable { + return this.http.put(ApiService.addQuery(ApiService.combineUrls(this.urlApi, AvailableVersion[this.version], this.basePath, endpoint), queryParams), data, {withCredentials: true}).pipe( + retryWithInterval(), + catchError(error => { + this.handleError(error); + throw error; + }) + ); + } + + public delete(endpoint: string): Observable { + return this.http.delete(ApiService.combineUrls(this.urlApi, AvailableVersion[this.version], this.basePath, endpoint), {withCredentials: true}).pipe( + retryWithInterval(), + catchError(error => { + this.handleError(error); + throw error; + }) + ); + } + + private handleError(error: HttpErrorResponse): void { + // todo: change to Retry-After condition + if (error.error.toString().includes("setup")) { + this.router.navigate(['/setup/']); + return; + } + + let message: string; + if (error.error instanceof ErrorEvent) { + message = `Произошла ошибка: ${error.error.message}`; + } else { + switch (error.status) { + case 0: + message = 'Неизвестная ошибка. Пожалуйста, попробуйте позже.'; + break; + case 400: + message = 'Ошибка запроса. Пожалуйста, проверьте отправленные данные.'; + break; + case 401: + message = 'Ошибка авторизации. Пожалуйста, выполните вход с правильными учетными данными.'; + break; + case 403: + message = 'Отказано в доступе. У вас нет разрешения на выполнение этого действия.'; + break; + case 404: + message = 'Запрашиваемый ресурс не найден.'; + break; + case 500: + message = 'Внутренняя ошибка сервера. Пожалуйста, попробуйте позже.'; + break; + case 503: + message = 'Сервер на обслуживании. Пожалуйста, попробуйте позже.'; + break; + default: + message = `Сервер вернул код ошибки: ${error.status}`; + break; + } + if (error.error?.Error) { + message += ` ${error.error.Error}`; + } + } + this.notify.open(message, NotifyColor.Danger); + } +} diff --git a/src/services/api.service.ts b/src/services/api.service.ts deleted file mode 100644 index 7970a27..0000000 --- a/src/services/api.service.ts +++ /dev/null @@ -1,110 +0,0 @@ -import {Injectable} from '@angular/core'; -import {catchError, mergeMap, Observable, retryWhen, throwError, timer} from "rxjs"; -import {HttpClient} from "@angular/common/http"; -import {NotifyColor, OpenNotifyService} from "@service/open-notify.service"; -import {environment} from "@/config/environment"; - -export function retryWithInterval(): (source: Observable) => Observable { - return (source: Observable) => - source.pipe( - retryWhen((errors: Observable) => - errors.pipe( - mergeMap((error, index) => { - if (index < (environment.maxRetry < 0 ? Infinity : environment.maxRetry - 1) && !error.status.toString().startsWith('4')) { - console.log(`Retrying after ${environment.retryDelay}ms...`); - return timer(environment.retryDelay); - } else { - console.error(`Exceeded maximum retries (${environment.maxRetry})`); - return throwError(error); - } - }) - ) - ) - ); -} - -@Injectable({ - providedIn: 'root' -}) - -export class ApiService { - constructor(private http: HttpClient, private notify: OpenNotifyService) { - } - - private urlApi = environment.apiUrl; - - get(endpoint: string): Observable { - return this.http.get(this.urlApi + endpoint).pipe( - retryWithInterval(), - catchError(error => { - this.handleError(error); - return throwError(error); - }) - ); - } - - post(endpoint: string, data: any): Observable { - return this.http.post(this.urlApi + endpoint, data).pipe( - retryWithInterval(), - catchError(error => { - this.handleError(error); - return throwError(error); - }) - ); - } - - put(endpoint: string, data: any): Observable { - return this.http.put(this.urlApi + endpoint, data).pipe( - retryWithInterval(), - catchError(error => { - this.handleError(error); - return throwError(error); - }) - ); - } - - delete(endpoint: string): Observable { - return this.http.delete(this.urlApi + endpoint).pipe( - retryWithInterval(), - catchError(error => { - this.handleError(error); - return throwError(error); - }) - ); - } - - private handleError(error: any): void { - let message: string; - if (error.error instanceof ErrorEvent) { - message = `Произошла ошибка: ${error.error.message}`; - } else { - switch (error.status) { - case 0: - message = 'Неизвестная ошибка. Пожалуйста, попробуйте позже.'; - break; - case 400: - message = 'Ошибка запроса. Пожалуйста, проверьте отправленные данные.'; - break; - case 401: - message = 'Ошибка авторизации. Пожалуйста, выполните вход с правильными учетными данными.'; - break; - case 403: - message = 'Отказано в доступе. У вас нет разрешения на выполнение этого действия.'; - break; - case 404: - message = 'Запрашиваемый ресурс не найден.'; - break; - case 500: - message = 'Внутренняя ошибка сервера. Пожалуйста, попробуйте позже.'; - break; - default: - message = `Сервер вернул код ошибки: ${error.status}`; - break; - } - if (error.error?.Error) { - message += ` ${error.error.Error}`; - } - } - this.notify.open(message, NotifyColor.Danger); - } -}