import { startWith } from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { uniq } from 'lodash-es'; import { forkJoin, map, Observable, of, switchMap, take, tap } from 'rxjs'; import { UserAccessAction } from '../models/user-access-action'; import { MwUserApiService } from './user-api.service'; import * as userReducers from '../@store/user.reducer'; import * as userActions from '../@store/user.actions'; @Injectable() export class MwUserAccessService { private readonly USER_ACCESS_ACTION_TTL = 60; // Seconds constructor( private readonly store: Store, private readonly userApiService: MwUserApiService ) {} hasAccessBatch( requestAccessActions: string[] ): Observable> { if (requestAccessActions.length === 0) { return of({}); } const result = this.getAllAccessActions(requestAccessActions).pipe( map((allAccessActions) => { const allAccessActionsEntries = Object.entries(allAccessActions); return requestAccessActions.reduce((prev, key) => { const keyUppercase = key.toUpperCase(); const item = allAccessActionsEntries.find( (t) => t[0].toUpperCase() === keyUppercase ); return { ...prev, [key]: item && item[1] === true, }; }, {}); }) ); return result; } hasAccess( requestAccessActions: string[], atLeastOne: boolean = false ): Observable { const result = this.hasAccessBatch(requestAccessActions).pipe( map((items) => { const values = Object.values(items); return atLeastOne ? values.some((t) => t === true) : values.every((t) => t === true); }) ); return result; } private convertUserAccessActionsToRecords( userAccessActions: UserAccessAction[] ): Record { return userAccessActions.reduce( (prev, curr) => ({ ...prev, [curr.action]: curr.allow }), {} ); } private getAccessActionsFromServer( accessActions: string[] ): Observable> { return accessActions.length > 0 ? this.userApiService .hasAccess(accessActions) .pipe( tap((accessActions) => this.store.dispatch( userActions.setUserAccessActions({ accessActions }) ) ) ) : of({}); } private getCachedAccessActions(): Observable { const threshold = this.USER_ACCESS_ACTION_TTL * 1000; return this.store.select(userReducers.userAccessActions).pipe( startWith([]), map((existingActions) => existingActions.filter((t) => Date.now() - +t.timestamp < threshold) ), take(1) ); } private getAllAccessActions( requestAccessActions: string[] ): Observable> { const uniqueActions = uniq(requestAccessActions).filter((t) => !!t); return this.getCachedAccessActions().pipe( switchMap((existingAccessActions) => { const missingActions = uniqueActions.filter( (t) => existingAccessActions.findIndex((x) => x.action) === -1 ); const existingAccessActionsRecords = this.convertUserAccessActionsToRecords(existingAccessActions); const accessActionsFromServer$ = this.getAccessActionsFromServer(missingActions); return forkJoin([ of(existingAccessActionsRecords), accessActionsFromServer$, ]); }), map(([existingAccessActionsRecords, accessActionsFromServer]) => ({ ...existingAccessActionsRecords, ...accessActionsFromServer, })) ); } }