diff --git a/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.spec.ts b/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.spec.ts index 3eb7c862d..47d211d29 100644 --- a/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.spec.ts +++ b/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.spec.ts @@ -36,6 +36,7 @@ import { LibraryEffects } from '../../store/effects'; import { NodeEntry } from '@alfresco/js-api'; import { getTitleElementText } from '../../testing/test-utils'; import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { SiteEntry } from '@alfresco/js-api/typings'; describe('FavoriteLibrariesComponent', () => { let fixture: ComponentFixture; @@ -126,7 +127,13 @@ describe('FavoriteLibrariesComponent', () => { it('does not navigate when id is not passed', () => { spyOn(router, 'navigate').and.stub(); - component.navigateTo({ entry: { guid: 'guid' } } as any); + component.navigateTo({ + entry: { + guid: 'test-guid', + visibility: 'PUBLIC', + role: 'SiteConsumer' + } + } as SiteEntry); expect(router.navigate).toHaveBeenCalledWith(['favorite/libraries', 'libraryId']); }); diff --git a/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.ts b/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.ts index 92d1638cb..250547a2c 100644 --- a/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.ts +++ b/projects/aca-content/src/lib/components/favorite-libraries/favorite-libraries.component.ts @@ -104,7 +104,7 @@ export class FavoriteLibrariesComponent extends PageComponent implements OnInit navigateTo(node: SiteEntry) { if (node?.entry?.guid) { - this.store.dispatch(new NavigateLibraryAction(node.entry.guid, 'favorite/libraries')); + this.store.dispatch(new NavigateLibraryAction(node.entry, 'favorite/libraries')); } } diff --git a/projects/aca-content/src/lib/components/libraries/libraries.component.ts b/projects/aca-content/src/lib/components/libraries/libraries.component.ts index 36339b4b2..eb9bde8e5 100644 --- a/projects/aca-content/src/lib/components/libraries/libraries.component.ts +++ b/projects/aca-content/src/lib/components/libraries/libraries.component.ts @@ -89,7 +89,7 @@ export class LibrariesComponent extends PageComponent implements OnInit { navigateTo(node: SiteEntry) { if (node?.entry?.guid) { - this.store.dispatch(new NavigateLibraryAction(node.entry.guid)); + this.store.dispatch(new NavigateLibraryAction(node.entry)); } } diff --git a/projects/aca-content/src/lib/components/search/search-libraries-results/search-libraries-results.component.ts b/projects/aca-content/src/lib/components/search/search-libraries-results/search-libraries-results.component.ts index 729e141d2..381a4c0c3 100644 --- a/projects/aca-content/src/lib/components/search/search-libraries-results/search-libraries-results.component.ts +++ b/projects/aca-content/src/lib/components/search/search-libraries-results/search-libraries-results.component.ts @@ -168,7 +168,7 @@ export class SearchLibrariesResultsComponent extends PageComponent implements On navigateTo(node: SiteEntry) { if (node?.entry?.guid) { - this.store.dispatch(new NavigateLibraryAction(node.entry.guid)); + this.store.dispatch(new NavigateLibraryAction(node.entry)); } } diff --git a/projects/aca-content/src/lib/store/effects/library.effects.spec.ts b/projects/aca-content/src/lib/store/effects/library.effects.spec.ts index c8c6c278c..f1e358b50 100644 --- a/projects/aca-content/src/lib/store/effects/library.effects.spec.ts +++ b/projects/aca-content/src/lib/store/effects/library.effects.spec.ts @@ -32,7 +32,7 @@ import { NotificationService } from '@alfresco/adf-core'; import { provideEffects } from '@ngrx/effects'; import { LibraryEffects } from './library.effects'; import { AppTestingModule } from '../../testing/app-testing.module'; -import { NodeEntry } from '@alfresco/js-api'; +import { NodeEntry, Site } from '@alfresco/js-api'; describe('LibraryEffects', () => { let store: Store; @@ -48,31 +48,106 @@ describe('LibraryEffects', () => { describe('navigateLibrary$', () => { let notificationService: NotificationService; let node$: Subject; + let admin$: Subject; + let site: Site; beforeEach(() => { node$ = new Subject(); + admin$ = new Subject(); + site = { guid: 'site-guid', visibility: 'PUBLIC', role: 'SiteConsumer', id: 'site-id', title: 'Title' }; spyOn(TestBed.inject(ContentApiService), 'getNode').and.returnValue(node$); notificationService = TestBed.inject(NotificationService); spyOn(notificationService, 'showError'); + spyOn(store, 'dispatch').and.callThrough(); + spyOn(store, 'select').and.returnValue(admin$); }); it('should display library no permission warning if user does not have permission', () => { spyOn(notificationService, 'showWarning'); - store.dispatch(new NavigateLibraryAction('libraryId')); + store.dispatch(new NavigateLibraryAction(site)); + admin$.next(false); node$.error(new HttpErrorResponse({ status: 403 })); expect(notificationService.showWarning).toHaveBeenCalledWith('APP.BROWSE.LIBRARIES.LIBRARY_NO_PERMISSIONS_WARNING'); }); it('should display library not found error if library does not exist', () => { - store.dispatch(new NavigateLibraryAction('libraryId')); + store.dispatch(new NavigateLibraryAction(site)); + admin$.next(false); node$.error(new HttpErrorResponse({ status: 404 })); expect(notificationService.showError).toHaveBeenCalledWith('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NOT_FOUND'); }); it('should display generic library loading error if there is different problem than missing permissions or absence of library', () => { - store.dispatch(new NavigateLibraryAction('libraryId')); + store.dispatch(new NavigateLibraryAction(site)); + admin$.next(false); node$.error(new HttpErrorResponse({ status: 500 })); expect(notificationService.showError).toHaveBeenCalledWith('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_LOADING_ERROR'); }); + + it('should show error when non-admin user without site role tries to access private site', () => { + store.dispatch(new NavigateLibraryAction({ ...site, visibility: 'PRIVATE', role: null })); + admin$.next(false); + + expect(store.dispatch).not.toHaveBeenCalledWith( + jasmine.objectContaining({ + type: 'NAVIGATE_ROUTE' + }) + ); + expect(notificationService.showError).toHaveBeenCalledWith('APP.BROWSE.LIBRARIES.LIBRARY_NO_PERMISSIONS_WARNING'); + }); + + it('should allow admin user to navigate to private site without role', () => { + store.dispatch(new NavigateLibraryAction({ ...site, visibility: 'PRIVATE', role: null })); + admin$.next(true); + node$.next({ entry: { id: 'private-doc-lib-id' } } as NodeEntry); + + expect(store.dispatch).toHaveBeenCalledWith( + jasmine.objectContaining({ + type: 'NAVIGATE_ROUTE', + payload: ['libraries', 'private-doc-lib-id'] + }) + ); + expect(notificationService.showError).not.toHaveBeenCalled(); + }); + + it('should allow navigation to public site for non-admin user without a role', () => { + store.dispatch(new NavigateLibraryAction({ ...site, role: null })); + admin$.next(false); + node$.next({ entry: { id: 'public-doc-lib-id' } } as NodeEntry); + + expect(store.dispatch).toHaveBeenCalledWith( + jasmine.objectContaining({ + type: 'NAVIGATE_ROUTE', + payload: ['libraries', 'public-doc-lib-id'] + }) + ); + expect(notificationService.showError).not.toHaveBeenCalled(); + }); + + it('should allow navigation to private site for non-admin user with a role', () => { + store.dispatch(new NavigateLibraryAction({ ...site, visibility: 'PRIVATE' })); + admin$.next(false); + node$.next({ entry: { id: 'role-doc-lib-id' } } as NodeEntry); + + expect(store.dispatch).toHaveBeenCalledWith( + jasmine.objectContaining({ + type: 'NAVIGATE_ROUTE', + payload: ['libraries', 'role-doc-lib-id'] + }) + ); + }); + + it('should use custom route if provided', () => { + store.dispatch(new NavigateLibraryAction(site, 'custom-route')); + admin$.next(false); + node$.next({ entry: { id: 'doc-lib-id' } } as NodeEntry); + + expect(store.dispatch).toHaveBeenCalledWith( + jasmine.objectContaining({ + type: 'NAVIGATE_ROUTE', + payload: ['custom-route', 'doc-lib-id'] + }) + ); + }); }); }); diff --git a/projects/aca-content/src/lib/store/effects/library.effects.ts b/projects/aca-content/src/lib/store/effects/library.effects.ts index b6c6eacb7..845b10fb1 100644 --- a/projects/aca-content/src/lib/store/effects/library.effects.ts +++ b/projects/aca-content/src/lib/store/effects/library.effects.ts @@ -31,12 +31,13 @@ import { LibraryActionTypes, NavigateLibraryAction, NavigateRouteAction, - UpdateLibraryAction + UpdateLibraryAction, + isAdmin } from '@alfresco/aca-shared/store'; import { inject, Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; -import { map, mergeMap, take } from 'rxjs/operators'; +import { map, mergeMap, take, tap } from 'rxjs/operators'; import { ContentApiService } from '@alfresco/aca-shared'; import { ContentManagementService } from '../../services/content-management.service'; import { NotificationService } from '@alfresco/adf-core'; @@ -99,45 +100,60 @@ export class LibraryEffects { this.actions$.pipe( ofType(LibraryActionTypes.Create), mergeMap(() => this.content.createLibrary()), - map((libraryId) => new NavigateLibraryAction(libraryId)) + tap((libraryId) => this.navigateToLibraryById(libraryId)) ), - { dispatch: true } + { dispatch: false } ); navigateLibrary$ = createEffect( () => this.actions$.pipe( ofType(LibraryActionTypes.Navigate), - map((action) => { - const libraryId = action.payload; - if (libraryId) { - this.contentApi - .getNode(libraryId, { relativePath: '/documentLibrary' }) - .pipe(map((node) => node.entry.id)) - .subscribe( - (id) => { - const route = action.route ? action.route : 'libraries'; - this.store.dispatch(new NavigateRouteAction([route, id])); - }, - (error: HttpErrorResponse) => { - switch (error.status) { - case 403: - this.notificationService.showWarning('APP.BROWSE.LIBRARIES.LIBRARY_NO_PERMISSIONS_WARNING'); - break; - case 404: - this.notificationService.showError('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NOT_FOUND'); - break; - default: - this.notificationService.showError('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_LOADING_ERROR'); - } + tap((action) => { + const payload = action.payload; + if (payload && 'guid' in payload) { + this.store + .select(isAdmin) + .pipe(take(1)) + .subscribe((isUserAdmin) => { + if (!isUserAdmin && payload.visibility !== 'PUBLIC' && !payload.role) { + this.notificationService.showError('APP.BROWSE.LIBRARIES.LIBRARY_NO_PERMISSIONS_WARNING'); + } else { + this.navigateToLibraryById(payload.guid, action.route); } - ); + }); } }) ), { dispatch: false } ); + private navigateToLibraryById(libraryId: string, route = 'libraries'): void { + this.contentApi + .getNode(libraryId, { relativePath: '/documentLibrary' }) + .pipe( + map((node) => node.entry.id), + take(1) + ) + .subscribe({ + next: (id) => { + this.store.dispatch(new NavigateRouteAction([route, id])); + }, + error: (error: HttpErrorResponse) => { + switch (error.status) { + case 403: + this.notificationService.showWarning('APP.BROWSE.LIBRARIES.LIBRARY_NO_PERMISSIONS_WARNING'); + break; + case 404: + this.notificationService.showError('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NOT_FOUND'); + break; + default: + this.notificationService.showError('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_LOADING_ERROR'); + } + } + }); + } + updateLibrary$ = createEffect( () => this.actions$.pipe( diff --git a/projects/aca-shared/store/src/actions/library.actions.ts b/projects/aca-shared/store/src/actions/library.actions.ts index f55d86a5e..a0ea36537 100644 --- a/projects/aca-shared/store/src/actions/library.actions.ts +++ b/projects/aca-shared/store/src/actions/library.actions.ts @@ -23,7 +23,7 @@ */ import { Action } from '@ngrx/store'; -import { SiteBodyCreate } from '@alfresco/js-api'; +import { Site, SiteBodyCreate } from '@alfresco/js-api'; import { ModalConfiguration } from '../models/modal-configuration'; export enum LibraryActionTypes { @@ -48,7 +48,7 @@ export class NavigateLibraryAction implements Action { readonly type = LibraryActionTypes.Navigate; constructor( - public payload?: string, + public payload?: Site, public route?: string ) {} }