2024-10-09 03:10:11 +03:00
|
|
|
|
import {
|
|
|
|
|
BehaviorSubject,
|
|
|
|
|
catchError,
|
|
|
|
|
distinctUntilChanged,
|
|
|
|
|
filter,
|
|
|
|
|
first,
|
|
|
|
|
Observable,
|
|
|
|
|
of,
|
|
|
|
|
ReplaySubject,
|
|
|
|
|
switchMap
|
|
|
|
|
} from "rxjs";
|
2024-06-05 21:43:36 +03:00
|
|
|
|
import {HttpClient, HttpErrorResponse} from "@angular/common/http";
|
2024-06-15 00:37:12 +03:00
|
|
|
|
import {environment} from "@environment";
|
2024-06-05 21:43:36 +03:00
|
|
|
|
import {Router} from "@angular/router";
|
|
|
|
|
import {Injectable} from "@angular/core";
|
2024-08-24 04:24:44 +03:00
|
|
|
|
import {RequestBuilder, RequestData} from "@api/RequestBuilder";
|
2024-10-07 01:17:49 +03:00
|
|
|
|
import {ToastrService} from "ngx-toastr";
|
2024-12-18 06:57:27 +03:00
|
|
|
|
import {AuthRoles} from "@model/authRoles";
|
2024-06-05 21:43:36 +03:00
|
|
|
|
|
|
|
|
|
export enum AvailableVersion {
|
|
|
|
|
v1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Injectable()
|
2024-08-24 04:24:44 +03:00
|
|
|
|
export default abstract class ApiService {
|
2024-10-27 08:29:30 +03:00
|
|
|
|
constructor(protected http: HttpClient, protected notify: ToastrService, private router: Router) {
|
2024-06-05 21:43:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
2024-06-28 21:32:10 +03:00
|
|
|
|
private apiUrl = environment.apiUrl;
|
2024-10-09 03:10:11 +03:00
|
|
|
|
private static isRefreshingToken: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
|
|
|
|
private static refreshTokenSubject: ReplaySubject<any> = new ReplaySubject(1);
|
2024-08-24 04:24:44 +03:00
|
|
|
|
|
2024-06-05 21:43:36 +03:00
|
|
|
|
protected abstract basePath: string;
|
|
|
|
|
protected abstract version: AvailableVersion;
|
2024-06-27 01:50:58 +03:00
|
|
|
|
|
2024-06-05 21:43:36 +03:00
|
|
|
|
private static addQuery(endpoint: string, queryParams?: Record<string, string | number | boolean | Array<any> | null> | null): string {
|
|
|
|
|
const url = new URL(endpoint);
|
|
|
|
|
|
|
|
|
|
if (queryParams) {
|
|
|
|
|
Object.keys(queryParams).forEach(key => {
|
|
|
|
|
const value = queryParams[key];
|
|
|
|
|
if (value !== null && value !== undefined) {
|
2024-06-27 01:50:58 +03:00
|
|
|
|
if (typeof (value) === typeof (Array)) {
|
2024-06-05 21:43:36 +03:00
|
|
|
|
(value as Array<any>).forEach(x => url.searchParams.append(key, x.toString()));
|
2024-06-27 01:50:58 +03:00
|
|
|
|
} else
|
2024-06-05 21:43:36 +03:00
|
|
|
|
url.searchParams.append(key, value.toString());
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return url.href;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static combineUrls(...parts: string[]): string {
|
2024-06-28 21:32:10 +03:00
|
|
|
|
return parts.map(part => part.replace(/(^\/+|\/+$)/g, '')).join('/');
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-24 04:24:44 +03:00
|
|
|
|
protected combinedUrl(request: RequestData) {
|
|
|
|
|
return ApiService.addQuery(ApiService.combineUrls(this.apiUrl, AvailableVersion[this.version], this.basePath, request.endpoint), request.queryParams);
|
2024-06-05 21:43:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
2024-10-09 03:10:11 +03:00
|
|
|
|
private sendHttpRequest<Type>(method: 'get' | 'post' | 'delete' | 'put', request: RequestData, secondTry: boolean = false): Observable<Type> {
|
2024-08-24 04:24:44 +03:00
|
|
|
|
const doneEndpoint = this.combinedUrl(request);
|
|
|
|
|
|
|
|
|
|
return this.http.request<Type>(method, doneEndpoint, {
|
|
|
|
|
withCredentials: request.withCredentials,
|
|
|
|
|
headers: request.httpHeaders,
|
2024-10-27 08:29:30 +03:00
|
|
|
|
body: request.data,
|
2024-08-24 04:24:44 +03:00
|
|
|
|
}).pipe(
|
|
|
|
|
catchError(error => {
|
2024-12-26 10:40:27 +03:00
|
|
|
|
if (request.needAuth && !secondTry && error.status === 401)
|
2024-12-18 06:57:27 +03:00
|
|
|
|
return this.handle401Error(error).pipe(
|
2024-10-09 03:10:11 +03:00
|
|
|
|
switchMap(() => this.sendHttpRequest<Type>(method, request, true))
|
|
|
|
|
);
|
|
|
|
|
else {
|
|
|
|
|
if (!request.silenceMode)
|
|
|
|
|
this.handleError(error);
|
|
|
|
|
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|
2024-08-24 04:24:44 +03:00
|
|
|
|
|
2024-10-09 03:10:11 +03:00
|
|
|
|
private refreshToken(): Observable<AuthRoles> {
|
|
|
|
|
return this.http.get<AuthRoles>(ApiService.combineUrls(this.apiUrl, AvailableVersion[AvailableVersion.v1], 'Auth', 'ReLogin'), {
|
|
|
|
|
withCredentials: true
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-18 06:57:27 +03:00
|
|
|
|
private handle401Error(error: any): Observable<any> {
|
2024-10-09 03:10:11 +03:00
|
|
|
|
if (ApiService.isRefreshingToken.value)
|
|
|
|
|
return ApiService.refreshTokenSubject.asObservable();
|
|
|
|
|
|
|
|
|
|
ApiService.isRefreshingToken.next(true);
|
|
|
|
|
return this.refreshToken().pipe(
|
|
|
|
|
switchMap(_ => {
|
|
|
|
|
ApiService.isRefreshingToken.next(false);
|
|
|
|
|
ApiService.refreshTokenSubject.next(null);
|
|
|
|
|
return of(null);
|
|
|
|
|
}),
|
|
|
|
|
catchError(err => {
|
|
|
|
|
ApiService.isRefreshingToken.next(false);
|
|
|
|
|
ApiService.refreshTokenSubject.error(err);
|
|
|
|
|
ApiService.refreshTokenSubject = new ReplaySubject(1);
|
2024-12-18 06:57:27 +03:00
|
|
|
|
throw error;
|
2024-08-24 04:24:44 +03:00
|
|
|
|
})
|
2024-06-05 21:43:36 +03:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-24 04:24:44 +03:00
|
|
|
|
private makeHttpRequest<Type>(method: 'get' | 'post' | 'delete' | 'put', request: RequestData): Observable<Type> {
|
2024-10-09 03:10:11 +03:00
|
|
|
|
if (request.needAuth) {
|
|
|
|
|
return ApiService.isRefreshingToken.pipe(
|
2024-08-26 01:24:57 +03:00
|
|
|
|
distinctUntilChanged(),
|
2024-08-24 04:24:44 +03:00
|
|
|
|
filter(isRefreshing => !isRefreshing),
|
2024-08-26 01:24:57 +03:00
|
|
|
|
first(),
|
2024-10-09 03:10:11 +03:00
|
|
|
|
switchMap(() => this.sendHttpRequest<Type>(method, request))
|
2024-08-24 04:24:44 +03:00
|
|
|
|
);
|
2024-10-09 03:10:11 +03:00
|
|
|
|
} else {
|
|
|
|
|
return this.sendHttpRequest<Type>(method, request);
|
|
|
|
|
}
|
2024-06-05 21:43:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
2024-08-24 04:24:44 +03:00
|
|
|
|
private getRequest(request: RequestData | string | null): RequestData {
|
|
|
|
|
if (request === null)
|
|
|
|
|
return this.createRequestBuilder().build;
|
|
|
|
|
if (typeof request === 'string')
|
|
|
|
|
return this.createRequestBuilder().setEndpoint(request as string).build;
|
|
|
|
|
|
|
|
|
|
return request as RequestData;
|
2024-06-27 01:50:58 +03:00
|
|
|
|
}
|
|
|
|
|
|
2024-08-24 04:24:44 +03:00
|
|
|
|
public createRequestBuilder() {
|
|
|
|
|
return new RequestBuilder();
|
2024-06-27 01:50:58 +03:00
|
|
|
|
}
|
|
|
|
|
|
2024-08-24 04:24:44 +03:00
|
|
|
|
public get<Type>(request: RequestData | string | null = null): Observable<Type> {
|
|
|
|
|
return this.makeHttpRequest<Type>('get', this.getRequest(request));
|
2024-06-05 21:43:36 +03:00
|
|
|
|
}
|
|
|
|
|
|
2024-08-24 04:24:44 +03:00
|
|
|
|
public post<Type>(request: RequestData | string | null = null): Observable<Type> {
|
|
|
|
|
return this.makeHttpRequest<Type>('post', this.getRequest(request));
|
|
|
|
|
}
|
2024-07-02 00:54:21 +03:00
|
|
|
|
|
2024-08-24 04:24:44 +03:00
|
|
|
|
public put<Type>(request: RequestData | string | null = null): Observable<Type> {
|
|
|
|
|
return this.makeHttpRequest<Type>('put', this.getRequest(request));
|
|
|
|
|
}
|
2024-07-02 00:54:21 +03:00
|
|
|
|
|
2024-08-24 04:24:44 +03:00
|
|
|
|
public delete<Type>(request: RequestData | string | null = null): Observable<Type> {
|
|
|
|
|
return this.makeHttpRequest<Type>('delete', this.getRequest(request));
|
|
|
|
|
}
|
2024-07-02 00:54:21 +03:00
|
|
|
|
|
2024-08-24 04:24:44 +03:00
|
|
|
|
public addAuth(request: RequestData) {
|
|
|
|
|
request.needAuth = true;
|
2024-10-09 03:10:11 +03:00
|
|
|
|
request.withCredentials = true;
|
2024-07-02 00:54:21 +03:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-05 21:43:36 +03:00
|
|
|
|
private handleError(error: HttpErrorResponse): void {
|
|
|
|
|
// todo: change to Retry-After condition
|
2024-12-22 07:17:21 +03:00
|
|
|
|
if (error.error && error.error.detail.includes("setup")) {
|
2024-06-27 01:50:58 +03:00
|
|
|
|
this.router.navigate(['/setup/']).then();
|
2024-06-05 21:43:36 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-07 03:25:13 +03:00
|
|
|
|
let title: string;
|
|
|
|
|
let message: string | undefined = undefined;
|
2024-06-05 21:43:36 +03:00
|
|
|
|
if (error.error instanceof ErrorEvent) {
|
2024-10-07 03:25:13 +03:00
|
|
|
|
title = `Произошла ошибка: ${error.error.message}`;
|
2024-06-05 21:43:36 +03:00
|
|
|
|
} else {
|
2024-12-22 07:17:21 +03:00
|
|
|
|
if (error.error && error.error.type && error.error.title) {
|
|
|
|
|
title = error.error.title || `Ошибка с кодом ${error.status}`;
|
|
|
|
|
message = error.error.detail || 'Неизвестная ошибка';
|
|
|
|
|
} else {
|
|
|
|
|
switch (error.status) {
|
|
|
|
|
case 0:
|
|
|
|
|
title = 'Неизвестная ошибка. Пожалуйста, попробуйте позже.';
|
|
|
|
|
break;
|
|
|
|
|
case 400:
|
|
|
|
|
title = 'Ошибка запроса. Пожалуйста, проверьте отправленные данные.';
|
|
|
|
|
break;
|
|
|
|
|
case 401:
|
|
|
|
|
this.router.navigate(['/login/']).then();
|
|
|
|
|
title = 'Ошибка авторизации. Пожалуйста, выполните вход с правильными учетными данными.';
|
|
|
|
|
break;
|
|
|
|
|
case 403:
|
|
|
|
|
title = 'Отказано в доступе. У вас нет разрешения на выполнение этого действия.';
|
|
|
|
|
break;
|
|
|
|
|
case 404:
|
|
|
|
|
title = 'Запрашиваемый ресурс не найден.';
|
|
|
|
|
break;
|
|
|
|
|
case 500:
|
|
|
|
|
title = 'Внутренняя ошибка сервера. Пожалуйста, попробуйте позже.';
|
|
|
|
|
break;
|
|
|
|
|
case 503:
|
|
|
|
|
title = 'Сервер на обслуживании. Пожалуйста, попробуйте позже.';
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
title = `Сервер вернул код ошибки: ${error.status}`;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-06-05 21:43:36 +03:00
|
|
|
|
}
|
2024-12-22 07:17:21 +03:00
|
|
|
|
|
|
|
|
|
if (!message)
|
2024-10-09 03:10:11 +03:00
|
|
|
|
message = error.error.statusMessage;
|
2024-06-05 21:43:36 +03:00
|
|
|
|
}
|
2024-10-07 03:25:13 +03:00
|
|
|
|
this.notify.error(message == '' ? undefined : message, title);
|
2024-06-05 21:43:36 +03:00
|
|
|
|
}
|
|
|
|
|
}
|