fix: delete saving states

Parallel use of the same Api Service instance is likely to replace requestData
This commit is contained in:
Polianin Nikita 2024-08-24 04:24:44 +03:00
parent 2871505591
commit fd5a1cb14f
9 changed files with 169 additions and 152 deletions

View File

@ -1,9 +1,5 @@
import {HttpHeaders} from "@angular/common/http"; import {HttpHeaders} from "@angular/common/http";
export interface SetRequestBuilderAfterBuild {
setRequestBuilder(request: RequestData): void;
}
export interface RequestData { export interface RequestData {
endpoint: string; endpoint: string;
queryParams: Record<string, string | number | boolean | Array<any> | null> | null; queryParams: Record<string, string | number | boolean | Array<any> | null> | null;
@ -11,6 +7,7 @@ export interface RequestData {
data: any; data: any;
silenceMode: boolean; silenceMode: boolean;
withCredentials: boolean; withCredentials: boolean;
needAuth: boolean;
} }
export class RequestBuilder { export class RequestBuilder {
@ -20,10 +17,8 @@ export class RequestBuilder {
private data: any = null; private data: any = null;
private silenceMode: boolean = false; private silenceMode: boolean = false;
private withCredentials: boolean = false; private withCredentials: boolean = false;
private readonly object: any;
constructor(obj: any) { constructor() {
this.object = obj;
} }
public setEndpoint(endpoint: string): this { public setEndpoint(endpoint: string): this {
@ -58,16 +53,16 @@ export class RequestBuilder {
return this; return this;
} }
public build<Type>(): Type { public get build(): RequestData {
(this.object as SetRequestBuilderAfterBuild).setRequestBuilder({ return {
endpoint: this.endpoint, endpoint: this.endpoint,
queryParams: this.queryParams, queryParams: this.queryParams,
httpHeaders: this.httpHeaders, httpHeaders: this.httpHeaders,
data: this.data, data: this.data,
silenceMode: this.silenceMode, silenceMode: this.silenceMode,
withCredentials: this.withCredentials withCredentials: this.withCredentials,
}); needAuth: false
return this.object as Type; };
} }
public getEndpoint(): string { public getEndpoint(): string {
@ -97,16 +92,8 @@ export class RequestBuilder {
httpHeaders: new HttpHeaders(), httpHeaders: new HttpHeaders(),
data: null, data: null,
silenceMode: false, silenceMode: false,
withCredentials: false withCredentials: false,
needAuth: false
}; };
} }
public reset(): void {
this.endpoint = '';
this.queryParams = null;
this.httpHeaders = new HttpHeaders();
this.data = null;
this.silenceMode = false;
this.withCredentials = false;
}
} }

View File

@ -1,10 +1,10 @@
import {catchError, filter, mergeMap, Observable, retryWhen, switchMap, tap, timer} from "rxjs"; import {catchError, filter, mergeMap, Observable, retryWhen, switchMap, timer} from "rxjs";
import {HttpClient, HttpErrorResponse} from "@angular/common/http"; import {HttpClient, HttpErrorResponse} from "@angular/common/http";
import {NotifyColor, OpenNotifyService} from "@service/open-notify.service"; import {NotifyColor, OpenNotifyService} from "@service/open-notify.service";
import {environment} from "@environment"; import {environment} from "@environment";
import {Router} from "@angular/router"; import {Router} from "@angular/router";
import {Injectable} from "@angular/core"; import {Injectable} from "@angular/core";
import {RequestBuilder, RequestData, SetRequestBuilderAfterBuild} from "@api/RequestBuilder"; import {RequestBuilder, RequestData} from "@api/RequestBuilder";
import {TokenRefreshService} from "@service/token-refresh.service"; import {TokenRefreshService} from "@service/token-refresh.service";
import {AuthToken} from "@service/auth.service"; import {AuthToken} from "@service/auth.service";
@ -36,19 +36,16 @@ export enum AvailableVersion {
} }
@Injectable() @Injectable()
export default abstract class ApiService implements SetRequestBuilderAfterBuild { export default abstract class ApiService {
constructor(private http: HttpClient, private notify: OpenNotifyService, private router: Router, protected tokenRefreshService: TokenRefreshService) { constructor(private http: HttpClient, private notify: OpenNotifyService, private router: Router, protected tokenRefreshService: TokenRefreshService) {
} }
private apiUrl = environment.apiUrl; private apiUrl = environment.apiUrl;
protected abstract basePath: string; protected abstract basePath: string;
protected abstract version: AvailableVersion; protected abstract version: AvailableVersion;
private request: RequestData = RequestBuilder.getStandardRequestData();
public static readonly tokenKey = 'auth_token';
public setRequestBuilder(request: RequestData): void { public static readonly tokenKey = 'auth_token';
this.request = request;
}
private static addQuery(endpoint: string, queryParams?: Record<string, string | number | boolean | Array<any> | null> | null): string { private static addQuery(endpoint: string, queryParams?: Record<string, string | number | boolean | Array<any> | null> | null): string {
const url = new URL(endpoint); const url = new URL(endpoint);
@ -72,73 +69,81 @@ export default abstract class ApiService implements SetRequestBuilderAfterBuild
return parts.map(part => part.replace(/(^\/+|\/+$)/g, '')).join('/'); return parts.map(part => part.replace(/(^\/+|\/+$)/g, '')).join('/');
} }
protected get combinedUrl() { protected combinedUrl(request: RequestData) {
return ApiService.addQuery(ApiService.combineUrls(this.apiUrl, AvailableVersion[this.version], this.basePath, this.request.endpoint), this.request.queryParams); return ApiService.addQuery(ApiService.combineUrls(this.apiUrl, AvailableVersion[this.version], this.basePath, request.endpoint), request.queryParams);
} }
private makeHttpRequest<Type>(method: 'get' | 'post' | 'delete' | 'put'): Observable<Type> { private sendHttpRequest<Type>(method: 'get' | 'post' | 'delete' | 'put', request: RequestData): Observable<Type> {
const doneEndpoint = this.combinedUrl; const doneEndpoint = this.combinedUrl(request);
return this.tokenRefreshService.getTokenRefreshing$().pipe( return this.http.request<Type>(method, doneEndpoint, {
filter(isRefreshing => !isRefreshing), withCredentials: request.withCredentials,
switchMap(() => headers: request.httpHeaders,
this.http.request<Type>(method, doneEndpoint, { body: request.data
withCredentials: this.request.withCredentials, }).pipe(
headers: this.request.httpHeaders, retryWithInterval<Type>(),
body: this.request.data catchError(error => {
}).pipe( if (!request.silenceMode)
tap(_ => this.request = RequestBuilder.getStandardRequestData()), this.handleError(error);
retryWithInterval<Type>(),
catchError(error => {
if (!this.request.silenceMode)
this.handleError(error);
this.request = RequestBuilder.getStandardRequestData(); throw error;
throw error; })
})
)
)
); );
} }
private makeHttpRequest<Type>(method: 'get' | 'post' | 'delete' | 'put', request: RequestData): Observable<Type> {
console.log(request);
if (request.needAuth)
return this.tokenRefreshService.getTokenRefreshing$().pipe(
filter(isRefreshing => !isRefreshing),
switchMap(() => {
console.log("I'm here!");
const token = localStorage.getItem(ApiService.tokenKey);
if (token) {
const authToken = AuthToken.httpHeader((JSON.parse(token) as AuthToken));
authToken.keys().forEach(key => request.httpHeaders = request.httpHeaders.append(key, authToken.get(key) ?? ''));
}
return this.sendHttpRequest<Type>(method, request);
})
);
return this.sendHttpRequest<Type>(method, request);
}
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;
}
public createRequestBuilder() { public createRequestBuilder() {
this.request = RequestBuilder.getStandardRequestData(); return new RequestBuilder();
return new RequestBuilder(this);
} }
public get<Type>(endpoint: string = ''): Observable<Type> { public get<Type>(request: RequestData | string | null = null): Observable<Type> {
if (endpoint) return this.makeHttpRequest<Type>('get', this.getRequest(request));
this.request.endpoint = endpoint;
return this.makeHttpRequest<Type>('get');
} }
public post<Type>(endpoint: string = ''): Observable<Type> { public post<Type>(request: RequestData | string | null = null): Observable<Type> {
if (endpoint) return this.makeHttpRequest<Type>('post', this.getRequest(request));
this.request.endpoint = endpoint;
return this.makeHttpRequest<Type>('post');
} }
public put<Type>(endpoint: string = ''): Observable<Type> { public put<Type>(request: RequestData | string | null = null): Observable<Type> {
if (endpoint) return this.makeHttpRequest<Type>('put', this.getRequest(request));
this.request.endpoint = endpoint;
return this.makeHttpRequest<Type>('put');
} }
public delete<Type>(endpoint: string = ''): Observable<Type> { public delete<Type>(request: RequestData | string | null = null): Observable<Type> {
if (endpoint) return this.makeHttpRequest<Type>('delete', this.getRequest(request));
this.request.endpoint = endpoint;
return this.makeHttpRequest<Type>('delete');
} }
public addAuth() { public addAuth(request: RequestData) {
const token = localStorage.getItem(ApiService.tokenKey); request.needAuth = true;
if (!token)
return this;
const authToken = AuthToken.httpHeader((JSON.parse(token) as AuthToken));
authToken.keys().forEach(key => this.request.httpHeaders = this.request.httpHeaders.append(key, authToken.get(key) ?? ''));
return this; return this;
} }

View File

@ -12,26 +12,29 @@ export default class AuthApiService extends ApiService {
public readonly version = AvailableVersion.v1; public readonly version = AvailableVersion.v1;
public login(login: LoginRequest) { public login(login: LoginRequest) {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setEndpoint('Login') .setEndpoint('Login')
.setData(login) .setData(login)
.build<ApiService>() .setWithCredentials()
.post<TokenResponse>() .build;
return this.post<TokenResponse>(request)
.pipe( .pipe(
tap(response => { tap(response => {
AuthService.setToken(response, AvailableAuthenticationProvider.Bearer, this.createRequestBuilder().setEndpoint('ReLogin').build<AuthApiService>().combinedUrl); AuthService.setToken(response, AvailableAuthenticationProvider.Bearer, this.combinedUrl(this.createRequestBuilder().setEndpoint('ReLogin').build));
this.tokenRefreshService.startTokenRefresh(response.expiresIn); this.tokenRefreshService.setRefreshTokenExpireMs(response.expiresIn);
}) })
); );
} }
public logout() { public logout() {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setWithCredentials() .setWithCredentials()
.setEndpoint('Logout') .setEndpoint('Logout')
.build<ApiService>() .build;
.addAuth()
.get() return this.addAuth(request)
.get(request)
.pipe( .pipe(
tap(_ => { tap(_ => {
localStorage.removeItem(ApiService.tokenKey); localStorage.removeItem(ApiService.tokenKey);
@ -40,11 +43,13 @@ export default class AuthApiService extends ApiService {
} }
public getRole(isSilence: boolean = true) { public getRole(isSilence: boolean = true) {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setSilenceMode(isSilence) .setSilenceMode(isSilence)
.build<ApiService>() .setEndpoint('GetRole')
.addAuth() .build;
.get<AuthRoles>('GetRole')
return this.addAuth(request)
.get<AuthRoles>(request)
.pipe( .pipe(
catchError(_ => { catchError(_ => {
return of(null); return of(null);

View File

@ -8,10 +8,11 @@ export class DisciplineService extends ApiService {
public readonly version = AvailableVersion.v1; public readonly version = AvailableVersion.v1;
public getDisciplines(page: number | null = null, pageSize: number | null = null) { public getDisciplines(page: number | null = null, pageSize: number | null = null) {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setQueryParams({page: page, pageSize: pageSize}) .setQueryParams({page: page, pageSize: pageSize})
.build<ApiService>() .build;
.get<DisciplineResponse[]>();
return this.get<DisciplineResponse[]>(request);
} }
public getById(id: number) { public getById(id: number) {

View File

@ -9,10 +9,11 @@ export class FacultyService extends ApiService {
public readonly version = AvailableVersion.v1; public readonly version = AvailableVersion.v1;
public getFaculties(page: number | null = null, pageSize: number | null = null) { public getFaculties(page: number | null = null, pageSize: number | null = null) {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setQueryParams({page: page, pageSize: pageSize}) .setQueryParams({page: page, pageSize: pageSize})
.build<ApiService>() .build;
.get<FacultyResponse[]>();
return this.get<FacultyResponse[]>(request);
} }
public getById(id: number) { public getById(id: number) {

View File

@ -9,10 +9,11 @@ export class GroupService extends ApiService {
public readonly version = AvailableVersion.v1; public readonly version = AvailableVersion.v1;
public getGroups(page: number | null = null, pageSize: number | null = null) { public getGroups(page: number | null = null, pageSize: number | null = null) {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setQueryParams({page: page, pageSize: pageSize}) .setQueryParams({page: page, pageSize: pageSize})
.build<ApiService>() .build;
.get<GroupResponse[]>();
return this.get<GroupResponse[]>(request);
} }
public getById(id: number) { public getById(id: number) {

View File

@ -8,10 +8,11 @@ export class ProfessorService extends ApiService {
public readonly version = AvailableVersion.v1; public readonly version = AvailableVersion.v1;
public getProfessors(page: number | null = null, pageSize: number | null = null) { public getProfessors(page: number | null = null, pageSize: number | null = null) {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setQueryParams({page: page, pageSize: pageSize}) .setQueryParams({page: page, pageSize: pageSize})
.build<ApiService>() .build;
.get<ProfessorResponse[]>();
return this.get<ProfessorResponse[]>(request);
} }
public getById(id: number) { public getById(id: number) {

View File

@ -20,41 +20,46 @@ export class ScheduleService extends ApiService {
} }
public postSchedule(data: ScheduleRequest) { public postSchedule(data: ScheduleRequest) {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setData(data) .setData(data)
.build<ApiService>() .build;
.post<ScheduleResponse[]>();
return this.post<ScheduleResponse[]>(request);
} }
public getByGroup(id: number, isEven: boolean | null = null, disciplines: Array<number> | null = null, professors: Array<number> | null = null, lectureHalls: Array<number> | null = null) { public getByGroup(id: number, isEven: boolean | null = null, disciplines: Array<number> | null = null, professors: Array<number> | null = null, lectureHalls: Array<number> | null = null) {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setEndpoint('GetByGroup/' + id.toString()) .setEndpoint('GetByGroup/' + id.toString())
.setQueryParams({isEven: isEven, disciplines: disciplines, professors: professors, lectureHalls: lectureHalls}) .setQueryParams({isEven: isEven, disciplines: disciplines, professors: professors, lectureHalls: lectureHalls})
.build<ApiService>() .build;
.get<ScheduleResponse[]>();
return this.get<ScheduleResponse[]>(request);
} }
public getByProfessor(id: number, isEven: boolean | null = null, disciplines: Array<number> | null = null, groups: Array<number> | null = null, lectureHalls: Array<number> | null = null) { public getByProfessor(id: number, isEven: boolean | null = null, disciplines: Array<number> | null = null, groups: Array<number> | null = null, lectureHalls: Array<number> | null = null) {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setEndpoint('GetByProfessor/' + id.toString()) .setEndpoint('GetByProfessor/' + id.toString())
.setQueryParams({isEven: isEven, disciplines: disciplines, groups: groups, lectureHalls: lectureHalls}) .setQueryParams({isEven: isEven, disciplines: disciplines, groups: groups, lectureHalls: lectureHalls})
.build<ApiService>() .build;
.get<ScheduleResponse[]>();
return this.get<ScheduleResponse[]>(request);
} }
public getByLectureHall(id: number, isEven: boolean | null = null, disciplines: Array<number> | null = null, groups: Array<number> | null = null, professors: Array<number> | null = null) { public getByLectureHall(id: number, isEven: boolean | null = null, disciplines: Array<number> | null = null, groups: Array<number> | null = null, professors: Array<number> | null = null) {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setEndpoint('GetByLectureHall/' + id.toString()) .setEndpoint('GetByLectureHall/' + id.toString())
.setQueryParams({isEven: isEven, disciplines: disciplines, groups: groups, professors: professors}) .setQueryParams({isEven: isEven, disciplines: disciplines, groups: groups, professors: professors})
.build<ApiService>() .build;
.get<ScheduleResponse[]>();
return this.get<ScheduleResponse[]>(request);
} }
public getByDiscipline(id: number, isEven: boolean | null = null, groups: Array<number> | null = null, professors: Array<number> | null = null, lectureHalls: Array<number> | null = null) { public getByDiscipline(id: number, isEven: boolean | null = null, groups: Array<number> | null = null, professors: Array<number> | null = null, lectureHalls: Array<number> | null = null) {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setEndpoint('GetByDiscipline/' + id.toString()) .setEndpoint('GetByDiscipline/' + id.toString())
.setQueryParams({isEven: isEven, groups: groups, professors: professors, lectureHalls: lectureHalls}) .setQueryParams({isEven: isEven, groups: groups, professors: professors, lectureHalls: lectureHalls})
.build<ApiService>() .build;
.get<ScheduleResponse[]>();
return this.get<ScheduleResponse[]>(request);
} }
} }

View File

@ -14,101 +14,112 @@ export default class SetupService extends ApiService {
public readonly version = AvailableVersion.v1; public readonly version = AvailableVersion.v1;
public checkToken(token: string) { public checkToken(token: string) {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setEndpoint('CheckToken') .setEndpoint('CheckToken')
.setQueryParams({token: token}) .setQueryParams({token: token})
.build<ApiService>() .build;
.get<boolean>();
return this.get<boolean>(request);
} }
public setPsql(data: DatabaseRequest) { public setPsql(data: DatabaseRequest) {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setEndpoint('SetPsql') .setEndpoint('SetPsql')
.setData(data) .setData(data)
.setWithCredentials() .setWithCredentials()
.build<ApiService>() .build;
.post<boolean>();
return this.post<boolean>(request);
} }
public setMysql(data: DatabaseRequest) { public setMysql(data: DatabaseRequest) {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setEndpoint('SetMysql') .setEndpoint('SetMysql')
.setData(data) .setData(data)
.setWithCredentials() .setWithCredentials()
.build<ApiService>() .build;
.post<boolean>();
return this.post<boolean>(request);
} }
public setSqlite(path: string | null = null) { public setSqlite(path: string | null = null) {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setEndpoint('SetSqlite') .setEndpoint('SetSqlite')
.setQueryParams({path: path}) .setQueryParams({path: path})
.setWithCredentials() .setWithCredentials()
.build<ApiService>() .build;
.get<boolean>();
return this.get<boolean>(request);
} }
public setRedis(data: CacheRequest) { public setRedis(data: CacheRequest) {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setEndpoint('SetRedis') .setEndpoint('SetRedis')
.setData(data) .setData(data)
.setWithCredentials() .setWithCredentials()
.build<ApiService>() .build;
.post<boolean>();
return this.post<boolean>(request);
} }
public setMemcached() { public setMemcached() {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setEndpoint('SetMemcached') .setEndpoint('SetMemcached')
.setWithCredentials() .setWithCredentials()
.build<ApiService>() .build;
.post<boolean>();
return this.post<boolean>(request);
} }
public createAdmin(data: CreateUserRequest) { public createAdmin(data: CreateUserRequest) {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setEndpoint('CreateAdmin') .setEndpoint('CreateAdmin')
.setData(data) .setData(data)
.setWithCredentials() .setWithCredentials()
.build<ApiService>() .build;
.post<boolean>();
return this.post<boolean>(request);
} }
public setLogging(data: LoggingRequest | null = null) { public setLogging(data: LoggingRequest | null = null) {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setEndpoint('SetLogging') .setEndpoint('SetLogging')
.setData(data) .setData(data)
.setWithCredentials() .setWithCredentials()
.build<ApiService>() .build;
.post<boolean>();
return this.post<boolean>(request);
} }
public setEmail(data: EmailRequest | null = null) { public setEmail(data: EmailRequest | null = null) {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setEndpoint('SetEmail') .setEndpoint('SetEmail')
.setData(data) .setData(data)
.setWithCredentials() .setWithCredentials()
.build<ApiService>() .build;
.post<boolean>();
return this.post<boolean>(request);
} }
public setSchedule(data: ScheduleConfigurationRequest) { public setSchedule(data: ScheduleConfigurationRequest) {
data.startTerm = new DateOnly(data.startTerm).toString(); data.startTerm = new DateOnly(data.startTerm).toString();
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setEndpoint('SetSchedule') .setEndpoint('SetSchedule')
.setData(data) .setData(data)
.setWithCredentials() .setWithCredentials()
.build<ApiService>() .build;
.post<boolean>();
return this.post<boolean>(request);
} }
public submit() { public submit() {
return this.createRequestBuilder() let request = this.createRequestBuilder()
.setEndpoint('Submit') .setEndpoint('Submit')
.setWithCredentials() .setWithCredentials()
.build<ApiService>() .build;
.post<boolean>();
return this.post<boolean>(request);
} }
public isConfigured() { public isConfigured() {