/* * Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den * Ministerpräsidenten des Landes Schleswig-Holstein * Staatskanzlei * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung * * Lizenziert unter der EUPL, Version 1.2 oder - sobald * diese von der Europäischen Kommission genehmigt wurden - * Folgeversionen der EUPL ("Lizenz"); * Sie dürfen dieses Werk ausschließlich gemäß * dieser Lizenz nutzen. * Eine Kopie der Lizenz finden Sie hier: * * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * * Sofern nicht durch anwendbare Rechtsvorschriften * gefordert oder in schriftlicher Form vereinbart, wird * die unter der Lizenz verbreitete Software "so wie sie * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - * ausdrücklich oder stillschweigend - verbreitet. * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { createErrorStateResource, createStateResource, isUnprocessableEntity, StateResource, } from '@alfa-client/tech-shared'; import { SnackBarService } from '@alfa-client/ui'; import { HttpErrorResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { Resource } from '@ngxp/rest'; import { Observable, of, Subject, throwError } from 'rxjs'; import { catchError, map, mergeMap, tap } from 'rxjs/operators'; import { CommandEffects } from './+state/command.effects'; import { COMMAND_ERROR_MESSAGES } from './command.message'; import { CommandListResource, CommandResource, CreateCommand, CreateCommandProps, } from './command.model'; import { CommandRepository } from './command.repository'; import { isConcurrentModification, isPending } from './command.util'; import * as Actions from './+state/command.actions'; import * as Selectors from './+state/command.selectors'; @Injectable({ providedIn: 'root' }) export class CommandService { intervalTimer: number = CommandEffects.POLL_DELAY; constructor( private repository: CommandRepository, private snackBarService: SnackBarService, private store: Store, ) {} /** * @deprecated Use createCommandByProps(createCommandProps: CreateCommandProps) instead. */ public createCommand( resource: Resource, linkRel: string, command: CreateCommand, ): Observable<StateResource<CommandResource>> { return this.handleCommandResponse(this.repository.createCommand(resource, linkRel, command)); } public revokeCommand(resource: CommandResource): Observable<StateResource<CommandResource>> { return this.handleCommandResponse(this.repository.revokeCommand(resource)); } private handleCommandResponse( command$: Observable<CommandResource>, ): Observable<StateResource<CommandResource>> { return command$.pipe( mergeMap((command) => this.handleCommand(command)), catchError((errorResponse) => this.handleHttpError(errorResponse)), ); } handleHttpError(errorResponse: HttpErrorResponse): Observable<StateResource<CommandResource>> { return of(this.handleErrorByStatus(errorResponse)); } handleErrorByStatus(error: HttpErrorResponse): StateResource<CommandResource> { if (isUnprocessableEntity(error.status)) { return createErrorStateResource(error.error); } throwError({ error }); } handleCommand(command: CommandResource): Observable<StateResource<CommandResource>> { return this.startPolling(command); } handleCommandError(command: CommandResource): Observable<StateResource<CommandResource>> { if (isConcurrentModification(command.errorMessage)) { this.snackBarService.showError(COMMAND_ERROR_MESSAGES[command.errorMessage]); this.store.dispatch(Actions.publishConcurrentModificationAction()); } return of(createStateResource(command)); } startPolling(commandResource: CommandResource): Observable<StateResource<CommandResource>> { return isPending(commandResource) ? this.pollCommand(commandResource) : of(createStateResource(commandResource)); } public pollCommand(commandResource: CommandResource): Observable<StateResource<CommandResource>> { const interval: IntervallHandleWithTickObservable = startInterval(this.intervalTimer); return interval.tickObservable.pipe( mergeMap(() => this.getAndUpdate(commandResource)), tap((stateResource) => this.handleInterval(stateResource, interval)), ); } handleInterval( stateResource: StateResource<CommandResource>, interval: IntervallHandleWithTickObservable, ): void { if (!stateResource.loading) this.clearInterval(interval.handle); } getAndUpdate(commandResource: CommandResource): Observable<StateResource<CommandResource>> { return this.getCommand(commandResource).pipe( map((res) => createStateResource(res, isPending(res))), ); } getCommand(resource: CommandResource): Observable<CommandResource> { return this.repository.getCommand(resource); } clearInterval(handler: number): void { window.clearInterval(handler); } public getPendingCommands(resource: Resource, linkRel: string): Observable<CommandListResource> { return this.repository.getPendingCommands(resource, linkRel); } public getEffectedResource<T>(command: CommandResource): Observable<T> { return this.repository.getEffectedResource(command); } public createCommandByProps( createCommandProps: CreateCommandProps, ): Observable<StateResource<CommandResource>> { this.store.dispatch(Actions.createCommand(createCommandProps)); return this.store.select(Selectors.commandByOrder(createCommandProps.command.order)); } } export interface IntervallHandleWithTickObservable { handle: number; tickObservable: Observable<any>; } export function startInterval(interval: number): IntervallHandleWithTickObservable { const subj: Subject<any> = new Subject(); // Workaround: cast return value of setInterval() to number because since TS 4.8 it thinks it must be "Timer" const handle = setInterval(() => subj.next('tick'), interval) as unknown as number; return { handle, tickObservable: subj.asObservable() }; }