Skip to content
Snippets Groups Projects
command.service.ts 6.08 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * 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';
    
    OZGCloud's avatar
    OZGCloud committed
    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' })
    
      intervalTimer: number = CommandEffects.POLL_DELAY;
    
      constructor(
        private repository: CommandRepository,
        private snackBarService: SnackBarService,
        private store: Store,
      ) {}
    
    
    OZGCloud's avatar
    OZGCloud committed
      /**
       * @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() };