mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge branch 'main' into DURACOM-248-ANGULAR-17
# Conflicts: # yarn.lock
This commit is contained in:
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
#CHROME_VERSION: "90.0.4430.212-1"
|
||||
# Bump Node heap size (OOM in CI after upgrading to Angular 15)
|
||||
NODE_OPTIONS: '--max-old-space-size=4096'
|
||||
# Project name to use when running docker-compose prior to e2e tests
|
||||
# Project name to use when running "docker compose" prior to e2e tests
|
||||
COMPOSE_PROJECT_NAME: 'ci'
|
||||
strategy:
|
||||
# Create a matrix of Node versions to test against (in parallel)
|
||||
@@ -108,12 +108,12 @@ jobs:
|
||||
path: 'coverage/dspace-angular/lcov.info'
|
||||
retention-days: 14
|
||||
|
||||
# Using docker-compose start backend using CI configuration
|
||||
# Using "docker compose" start backend using CI configuration
|
||||
# and load assetstore from a cached copy
|
||||
- name: Start DSpace REST Backend via Docker (for e2e tests)
|
||||
run: |
|
||||
docker-compose -f ./docker/docker-compose-ci.yml up -d
|
||||
docker-compose -f ./docker/cli.yml -f ./docker/cli.assetstore.yml run --rm dspace-cli
|
||||
docker compose -f ./docker/docker-compose-ci.yml up -d
|
||||
docker compose -f ./docker/cli.yml -f ./docker/cli.assetstore.yml run --rm dspace-cli
|
||||
docker container ls
|
||||
|
||||
# Run integration tests via Cypress.io
|
||||
@@ -182,7 +182,7 @@ jobs:
|
||||
run: kill -9 $(lsof -t -i:4000)
|
||||
|
||||
- name: Shutdown Docker containers
|
||||
run: docker-compose -f ./docker/docker-compose-ci.yml down
|
||||
run: docker compose -f ./docker/docker-compose-ci.yml down
|
||||
|
||||
# Codecov upload is a separate job in order to allow us to restart this separate from the entire build/test
|
||||
# job above. This is necessary because Codecov uploads seem to randomly fail at times.
|
||||
|
@@ -94,7 +94,7 @@
|
||||
"date-fns-tz": "^1.3.7",
|
||||
"deepmerge": "^4.3.1",
|
||||
"ejs": "^3.1.9",
|
||||
"express": "^4.18.2",
|
||||
"express": "^4.19.2",
|
||||
"express-rate-limit": "^5.1.3",
|
||||
"fast-json-patch": "^3.1.1",
|
||||
"filesize": "^6.1.0",
|
||||
|
@@ -1,11 +1,14 @@
|
||||
import { AbstractControl } from '@angular/forms';
|
||||
import { Route } from '@angular/router';
|
||||
import {
|
||||
mapToCanActivate,
|
||||
Route,
|
||||
} from '@angular/router';
|
||||
import {
|
||||
DYNAMIC_ERROR_MESSAGES_MATCHER,
|
||||
DynamicErrorMessagesMatcher,
|
||||
} from '@ng-dynamic-forms/core';
|
||||
|
||||
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { GroupAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/group-administrator.guard';
|
||||
import { SiteAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
|
||||
import {
|
||||
@@ -39,76 +42,76 @@ export const ROUTES: Route[] = [
|
||||
path: EPERSON_PATH,
|
||||
component: EPeopleRegistryComponent,
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
breadcrumb: i18nBreadcrumbResolver,
|
||||
},
|
||||
providers,
|
||||
data: { title: 'admin.access-control.epeople.title', breadcrumbKey: 'admin.access-control.epeople' },
|
||||
canActivate: [SiteAdministratorGuard],
|
||||
canActivate: mapToCanActivate([SiteAdministratorGuard]),
|
||||
},
|
||||
{
|
||||
path: `${EPERSON_PATH}/create`,
|
||||
component: EPersonFormComponent,
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
breadcrumb: i18nBreadcrumbResolver,
|
||||
},
|
||||
providers,
|
||||
data: { title: 'admin.access-control.epeople.add.title', breadcrumbKey: 'admin.access-control.epeople.add' },
|
||||
canActivate: [SiteAdministratorGuard],
|
||||
canActivate: mapToCanActivate([SiteAdministratorGuard]),
|
||||
},
|
||||
{
|
||||
path: `${EPERSON_PATH}/:id/edit`,
|
||||
component: EPersonFormComponent,
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
breadcrumb: i18nBreadcrumbResolver,
|
||||
ePerson: EPersonResolver,
|
||||
},
|
||||
providers,
|
||||
data: { title: 'admin.access-control.epeople.edit.title', breadcrumbKey: 'admin.access-control.epeople.edit' },
|
||||
canActivate: [SiteAdministratorGuard],
|
||||
canActivate: mapToCanActivate([SiteAdministratorGuard]),
|
||||
},
|
||||
{
|
||||
path: GROUP_PATH,
|
||||
component: GroupsRegistryComponent,
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
breadcrumb: i18nBreadcrumbResolver,
|
||||
},
|
||||
providers,
|
||||
data: { title: 'admin.access-control.groups.title', breadcrumbKey: 'admin.access-control.groups' },
|
||||
canActivate: [GroupAdministratorGuard],
|
||||
canActivate: mapToCanActivate([GroupAdministratorGuard]),
|
||||
},
|
||||
{
|
||||
path: `${GROUP_PATH}/create`,
|
||||
component: GroupFormComponent,
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
breadcrumb: i18nBreadcrumbResolver,
|
||||
},
|
||||
providers,
|
||||
data: {
|
||||
title: 'admin.access-control.groups.title.addGroup',
|
||||
breadcrumbKey: 'admin.access-control.groups.addGroup',
|
||||
},
|
||||
canActivate: [GroupAdministratorGuard],
|
||||
canActivate: mapToCanActivate([GroupAdministratorGuard]),
|
||||
},
|
||||
{
|
||||
path: `${GROUP_PATH}/:groupId/edit`,
|
||||
component: GroupFormComponent,
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
breadcrumb: i18nBreadcrumbResolver,
|
||||
},
|
||||
providers,
|
||||
data: {
|
||||
title: 'admin.access-control.groups.title.singleGroup',
|
||||
breadcrumbKey: 'admin.access-control.groups.singleGroup',
|
||||
},
|
||||
canActivate: [GroupPageGuard],
|
||||
canActivate: mapToCanActivate([GroupPageGuard]),
|
||||
},
|
||||
{
|
||||
path: 'bulk-access',
|
||||
component: BulkAccessComponent,
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
breadcrumb: i18nBreadcrumbResolver,
|
||||
},
|
||||
data: { title: 'admin.access-control.bulk-access.title', breadcrumbKey: 'admin.access-control.bulk-access' },
|
||||
canActivate: [SiteAdministratorGuard],
|
||||
canActivate: mapToCanActivate([SiteAdministratorGuard]),
|
||||
},
|
||||
];
|
||||
|
@@ -53,6 +53,7 @@ import { HALEndpointService } from '../../../core/shared/hal-endpoint.service';
|
||||
import { NoContent } from '../../../core/shared/NoContent.model';
|
||||
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||
import { UUIDService } from '../../../core/shared/uuid.service';
|
||||
import { XSRFService } from '../../../core/xsrf/xsrf.service';
|
||||
import { AlertComponent } from '../../../shared/alert/alert.component';
|
||||
import { ContextHelpDirective } from '../../../shared/context-help.directive';
|
||||
import { FormBuilderService } from '../../../shared/form/builder/form-builder.service';
|
||||
@@ -244,6 +245,7 @@ describe('GroupFormComponent', () => {
|
||||
{ provide: HttpClient, useValue: {} },
|
||||
{ provide: ObjectCacheService, useValue: {} },
|
||||
{ provide: UUIDService, useValue: {} },
|
||||
{ provide: XSRFService, useValue: {} },
|
||||
{ provide: Store, useValue: {} },
|
||||
{ provide: RemoteDataBuildService, useValue: {} },
|
||||
{ provide: HALEndpointService, useValue: {} },
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { NavigationBreadcrumbResolver } from '../../core/breadcrumbs/navigation-breadcrumb.resolver';
|
||||
import { i18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { navigationBreadcrumbResolver } from '../../core/breadcrumbs/navigation-breadcrumb.resolver';
|
||||
import { LdnServiceFormComponent } from './ldn-service-form/ldn-service-form.component';
|
||||
import { LdnServicesOverviewComponent } from './ldn-services-directory/ldn-services-directory.component';
|
||||
|
||||
@@ -10,18 +10,18 @@ const moduleRoutes: Routes = [
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
component: LdnServicesOverviewComponent,
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
||||
data: { title: 'ldn-registered-services.title', breadcrumbKey: 'ldn-registered-services.new' },
|
||||
},
|
||||
{
|
||||
path: 'new',
|
||||
resolve: { breadcrumb: NavigationBreadcrumbResolver },
|
||||
resolve: { breadcrumb: navigationBreadcrumbResolver },
|
||||
component: LdnServiceFormComponent,
|
||||
data: { title: 'ldn-register-new-service.title', breadcrumbKey: 'ldn-register-new-service' },
|
||||
},
|
||||
{
|
||||
path: 'edit/:serviceId',
|
||||
resolve: { breadcrumb: NavigationBreadcrumbResolver },
|
||||
resolve: { breadcrumb: navigationBreadcrumbResolver },
|
||||
component: LdnServiceFormComponent,
|
||||
data: { title: 'ldn-edit-service.title', breadcrumbKey: 'ldn-edit-service' },
|
||||
},
|
||||
|
@@ -1,12 +1,12 @@
|
||||
import { Route } from '@angular/router';
|
||||
|
||||
import { AuthenticatedGuard } from '../../core/auth/authenticated.guard';
|
||||
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { QualityAssuranceBreadcrumbResolver } from '../../core/breadcrumbs/quality-assurance-breadcrumb.resolver';
|
||||
import { authenticatedGuard } from '../../core/auth/authenticated.guard';
|
||||
import { i18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { qualityAssuranceBreadcrumbResolver } from '../../core/breadcrumbs/quality-assurance-breadcrumb.resolver';
|
||||
import { AdminNotificationsPublicationClaimPageResolver } from '../../quality-assurance-notifications-pages/notifications-suggestion-targets-page/notifications-suggestion-targets-page-resolver.service';
|
||||
import { QualityAssuranceEventsPageComponent } from '../../quality-assurance-notifications-pages/quality-assurance-events-page/quality-assurance-events-page.component';
|
||||
import { QualityAssuranceEventsPageResolver } from '../../quality-assurance-notifications-pages/quality-assurance-events-page/quality-assurance-events-page.resolver';
|
||||
import { SourceDataResolver } from '../../quality-assurance-notifications-pages/quality-assurance-source-page-component/quality-assurance-source-data.resolver';
|
||||
import { qualityAssuranceEventsPageResolver } from '../../quality-assurance-notifications-pages/quality-assurance-events-page/quality-assurance-events-page.resolver';
|
||||
import { qualityAssuranceSourceDataResolver } from '../../quality-assurance-notifications-pages/quality-assurance-source-page-component/quality-assurance-source-data.resolver';
|
||||
import { QualityAssuranceSourcePageComponent } from '../../quality-assurance-notifications-pages/quality-assurance-source-page-component/quality-assurance-source-page.component';
|
||||
import { QualityAssuranceSourcePageResolver } from '../../quality-assurance-notifications-pages/quality-assurance-source-page-component/quality-assurance-source-page-resolver.service';
|
||||
import { QualityAssuranceTopicsPageComponent } from '../../quality-assurance-notifications-pages/quality-assurance-topics-page/quality-assurance-topics-page.component';
|
||||
@@ -19,12 +19,12 @@ import {
|
||||
|
||||
export const ROUTES: Route[] = [
|
||||
{
|
||||
canActivate: [ AuthenticatedGuard ],
|
||||
canActivate: [ authenticatedGuard ],
|
||||
path: `${PUBLICATION_CLAIMS_PATH}`,
|
||||
component: AdminNotificationsPublicationClaimPageComponent,
|
||||
pathMatch: 'full',
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
breadcrumb: i18nBreadcrumbResolver,
|
||||
suggestionTargetParams: AdminNotificationsPublicationClaimPageResolver,
|
||||
},
|
||||
data: {
|
||||
@@ -34,12 +34,12 @@ export const ROUTES: Route[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
canActivate: [AuthenticatedGuard],
|
||||
canActivate: [authenticatedGuard],
|
||||
path: `${QUALITY_ASSURANCE_EDIT_PATH}/:sourceId`,
|
||||
component: QualityAssuranceTopicsPageComponent,
|
||||
pathMatch: 'full',
|
||||
resolve: {
|
||||
breadcrumb: QualityAssuranceBreadcrumbResolver,
|
||||
breadcrumb: qualityAssuranceBreadcrumbResolver,
|
||||
openaireQualityAssuranceTopicsParams: QualityAssuranceTopicsPageResolver,
|
||||
},
|
||||
data: {
|
||||
@@ -49,12 +49,12 @@ export const ROUTES: Route[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
canActivate: [ AuthenticatedGuard ],
|
||||
canActivate: [ authenticatedGuard ],
|
||||
path: `${QUALITY_ASSURANCE_EDIT_PATH}/:sourceId/target/:targetId`,
|
||||
component: QualityAssuranceTopicsPageComponent,
|
||||
pathMatch: 'full',
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
breadcrumb: i18nBreadcrumbResolver,
|
||||
openaireQualityAssuranceTopicsParams: QualityAssuranceTopicsPageResolver,
|
||||
},
|
||||
data: {
|
||||
@@ -64,14 +64,14 @@ export const ROUTES: Route[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
canActivate: [AuthenticatedGuard],
|
||||
canActivate: [authenticatedGuard],
|
||||
path: `${QUALITY_ASSURANCE_EDIT_PATH}`,
|
||||
component: QualityAssuranceSourcePageComponent,
|
||||
pathMatch: 'full',
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
breadcrumb: i18nBreadcrumbResolver,
|
||||
openaireQualityAssuranceSourceParams: QualityAssuranceSourcePageResolver,
|
||||
sourceData: SourceDataResolver,
|
||||
sourceData: qualityAssuranceSourceDataResolver,
|
||||
},
|
||||
data: {
|
||||
title: 'admin.notifications.source.breadcrumbs',
|
||||
@@ -80,13 +80,13 @@ export const ROUTES: Route[] = [
|
||||
},
|
||||
},
|
||||
{
|
||||
canActivate: [AuthenticatedGuard],
|
||||
canActivate: [authenticatedGuard],
|
||||
path: `${QUALITY_ASSURANCE_EDIT_PATH}/:sourceId/:topicId`,
|
||||
component: QualityAssuranceEventsPageComponent,
|
||||
pathMatch: 'full',
|
||||
resolve: {
|
||||
breadcrumb: QualityAssuranceBreadcrumbResolver,
|
||||
openaireQualityAssuranceEventsParams: QualityAssuranceEventsPageResolver,
|
||||
breadcrumb: qualityAssuranceBreadcrumbResolver,
|
||||
openaireQualityAssuranceEventsParams: qualityAssuranceEventsPageResolver,
|
||||
},
|
||||
data: {
|
||||
title: 'admin.notifications.event.page.title',
|
||||
|
@@ -1,7 +1,10 @@
|
||||
import { Route } from '@angular/router';
|
||||
import {
|
||||
mapToCanActivate,
|
||||
Route,
|
||||
} from '@angular/router';
|
||||
|
||||
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { NotifyInfoGuard } from '../../core/coar-notify/notify-info/notify-info.guard';
|
||||
import { i18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { notifyInfoGuard } from '../../core/coar-notify/notify-info/notify-info.guard';
|
||||
import { SiteAdministratorGuard } from '../../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
|
||||
import { AdminNotifyDashboardComponent } from './admin-notify-dashboard.component';
|
||||
import { AdminNotifyIncomingComponent } from './admin-notify-logs/admin-notify-incoming/admin-notify-incoming.component';
|
||||
@@ -9,10 +12,10 @@ import { AdminNotifyOutgoingComponent } from './admin-notify-logs/admin-notify-o
|
||||
|
||||
export const ROUTES: Route[] = [
|
||||
{
|
||||
canActivate: [SiteAdministratorGuard, NotifyInfoGuard],
|
||||
canActivate: [...mapToCanActivate([SiteAdministratorGuard]), notifyInfoGuard],
|
||||
path: '',
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
breadcrumb: i18nBreadcrumbResolver,
|
||||
},
|
||||
component: AdminNotifyDashboardComponent,
|
||||
pathMatch: 'full',
|
||||
@@ -24,10 +27,10 @@ export const ROUTES: Route[] = [
|
||||
{
|
||||
path: 'inbound',
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
breadcrumb: i18nBreadcrumbResolver,
|
||||
},
|
||||
component: AdminNotifyIncomingComponent,
|
||||
canActivate: [SiteAdministratorGuard, NotifyInfoGuard],
|
||||
canActivate: [...mapToCanActivate([SiteAdministratorGuard]), notifyInfoGuard],
|
||||
data: {
|
||||
title: 'admin.notify.dashboard.page.title',
|
||||
breadcrumbKey: 'admin.notify.dashboard',
|
||||
@@ -36,10 +39,10 @@ export const ROUTES: Route[] = [
|
||||
{
|
||||
path: 'outbound',
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
breadcrumb: i18nBreadcrumbResolver,
|
||||
},
|
||||
component: AdminNotifyOutgoingComponent,
|
||||
canActivate: [SiteAdministratorGuard, NotifyInfoGuard],
|
||||
canActivate: [...mapToCanActivate([SiteAdministratorGuard]), notifyInfoGuard],
|
||||
data: {
|
||||
title: 'admin.notify.dashboard.page.title',
|
||||
breadcrumbKey: 'admin.notify.dashboard',
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Route } from '@angular/router';
|
||||
|
||||
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { i18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { BITSTREAMFORMATS_MODULE_PATH } from './admin-registries-routing-paths';
|
||||
import { MetadataRegistryComponent } from './metadata-registry/metadata-registry.component';
|
||||
import { MetadataSchemaComponent } from './metadata-schema/metadata-schema.component';
|
||||
@@ -8,7 +8,7 @@ import { MetadataSchemaComponent } from './metadata-schema/metadata-schema.compo
|
||||
export const ROUTES: Route[] = [
|
||||
{
|
||||
path: 'metadata',
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
||||
data: { title: 'admin.registries.metadata.title', breadcrumbKey: 'admin.registries.metadata' },
|
||||
children: [
|
||||
{
|
||||
@@ -17,7 +17,7 @@ export const ROUTES: Route[] = [
|
||||
},
|
||||
{
|
||||
path: ':schemaName',
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
||||
component: MetadataSchemaComponent,
|
||||
data: { title: 'admin.registries.schema.title', breadcrumbKey: 'admin.registries.schema' },
|
||||
},
|
||||
@@ -25,7 +25,7 @@ export const ROUTES: Route[] = [
|
||||
},
|
||||
{
|
||||
path: BITSTREAMFORMATS_MODULE_PATH,
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
||||
loadChildren: () => import('./bitstream-formats/bitstream-formats-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
data: { title: 'admin.registries.bitstream-formats.title', breadcrumbKey: 'admin.registries.bitstream-formats' },
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import { Route } from '@angular/router';
|
||||
|
||||
import { I18nBreadcrumbResolver } from '../../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { i18nBreadcrumbResolver } from '../../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { AddBitstreamFormatComponent } from './add-bitstream-format/add-bitstream-format.component';
|
||||
import { BitstreamFormatsComponent } from './bitstream-formats.component';
|
||||
import { BitstreamFormatsResolver } from './bitstream-formats.resolver';
|
||||
import { bitstreamFormatsResolver } from './bitstream-formats.resolver';
|
||||
import { EditBitstreamFormatComponent } from './edit-bitstream-format/edit-bitstream-format.component';
|
||||
|
||||
const BITSTREAMFORMAT_EDIT_PATH = ':id/edit';
|
||||
@@ -19,7 +19,7 @@ export const ROUTES: Route[] = [
|
||||
},
|
||||
{
|
||||
path: BITSTREAMFORMAT_ADD_PATH,
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
||||
providers,
|
||||
component: AddBitstreamFormatComponent,
|
||||
data: { breadcrumbKey: 'admin.registries.bitstream-formats.create' },
|
||||
@@ -29,8 +29,8 @@ export const ROUTES: Route[] = [
|
||||
providers,
|
||||
component: EditBitstreamFormatComponent,
|
||||
resolve: {
|
||||
bitstreamFormat: BitstreamFormatsResolver,
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
bitstreamFormat: bitstreamFormatsResolver,
|
||||
breadcrumb: i18nBreadcrumbResolver,
|
||||
},
|
||||
data: { breadcrumbKey: 'admin.registries.bitstream-formats.edit' },
|
||||
},
|
||||
|
@@ -31,6 +31,7 @@ import { GroupDataService } from '../../../core/eperson/group-data.service';
|
||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
|
||||
import { BitstreamFormatSupportLevel } from '../../../core/shared/bitstream-format-support-level';
|
||||
import { XSRFService } from '../../../core/xsrf/xsrf.service';
|
||||
import { HostWindowService } from '../../../shared/host-window.service';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { PaginationComponent } from '../../../shared/pagination/pagination.component';
|
||||
@@ -143,6 +144,7 @@ describe('BitstreamFormatsComponent', () => {
|
||||
{ provide: PaginationService, useValue: paginationService },
|
||||
{ provide: GroupDataService, useValue: groupDataService },
|
||||
{ provide: ConfigurationDataService, useValue: configurationDataService },
|
||||
{ provide: XSRFService, useValue: {} },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
}).compileComponents();
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -11,24 +12,20 @@ import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
|
||||
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific bitstreamFormat before the route is activated
|
||||
* Method for resolving an bitstreamFormat based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state
|
||||
* @param {BitstreamFormatDataService} bitstreamFormatDataService The BitstreamFormatDataService
|
||||
* @returns Observable<<RemoteData<BitstreamFormat>> Emits the found bitstreamFormat based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class BitstreamFormatsResolver {
|
||||
constructor(private bitstreamFormatDataService: BitstreamFormatDataService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving an bitstreamFormat based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns Observable<<RemoteData<BitstreamFormat>> Emits the found bitstreamFormat based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<BitstreamFormat>> {
|
||||
return this.bitstreamFormatDataService.findById(route.params.id)
|
||||
.pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
}
|
||||
}
|
||||
export const bitstreamFormatsResolver: ResolveFn<RemoteData<BitstreamFormat>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
bitstreamFormatDataService: BitstreamFormatDataService = inject(BitstreamFormatDataService),
|
||||
): Observable<RemoteData<BitstreamFormat>> => {
|
||||
return bitstreamFormatDataService.findById(route.params.id)
|
||||
.pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
};
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import { Route } from '@angular/router';
|
||||
|
||||
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { i18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { FilteredCollectionsComponent } from './filtered-collections/filtered-collections.component';
|
||||
import { FilteredItemsComponent } from './filtered-items/filtered-items.component';
|
||||
|
||||
export const ROUTES: Route[] = [
|
||||
{
|
||||
path: 'collections',
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
||||
data: { title: 'admin.reports.collections.title', breadcrumbKey: 'admin.reports.collections' },
|
||||
children: [
|
||||
{
|
||||
@@ -18,7 +18,7 @@ export const ROUTES: Route[] = [
|
||||
},
|
||||
{
|
||||
path: 'queries',
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
||||
data: { title: 'admin.reports.items.title', breadcrumbKey: 'admin.reports.items' },
|
||||
children: [
|
||||
{
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Route } from '@angular/router';
|
||||
|
||||
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { AdminCurationTasksComponent } from './admin-curation-tasks/admin-curation-tasks.component';
|
||||
import { BatchImportPageComponent } from './admin-import-batch-page/batch-import-page.component';
|
||||
import { MetadataImportPageComponent } from './admin-import-metadata-page/metadata-import-page.component';
|
||||
@@ -27,37 +27,37 @@ export const ROUTES: Route[] = [
|
||||
},
|
||||
{
|
||||
path: 'search',
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
||||
component: AdminSearchPageComponent,
|
||||
data: { title: 'admin.search.title', breadcrumbKey: 'admin.search' },
|
||||
},
|
||||
{
|
||||
path: 'workflow',
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
||||
component: AdminWorkflowPageComponent,
|
||||
data: { title: 'admin.workflow.title', breadcrumbKey: 'admin.workflow' },
|
||||
},
|
||||
{
|
||||
path: 'curation-tasks',
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
||||
component: AdminCurationTasksComponent,
|
||||
data: { title: 'admin.curation-tasks.title', breadcrumbKey: 'admin.curation-tasks' },
|
||||
},
|
||||
{
|
||||
path: 'metadata-import',
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
||||
component: MetadataImportPageComponent,
|
||||
data: { title: 'admin.metadata-import.title', breadcrumbKey: 'admin.metadata-import' },
|
||||
},
|
||||
{
|
||||
path: 'batch-import',
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
||||
component: BatchImportPageComponent,
|
||||
data: { title: 'admin.batch-import.title', breadcrumbKey: 'admin.batch-import' },
|
||||
},
|
||||
{
|
||||
path: 'system-wide-alert',
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
||||
loadChildren: () => import('../system-wide-alert/system-wide-alert-routes').then((m) => m.ROUTES),
|
||||
data: { title: 'admin.system-wide-alert.title', breadcrumbKey: 'admin.system-wide-alert' },
|
||||
},
|
||||
|
@@ -17,6 +17,7 @@ import { AuthorizationDataService } from '../../../../../core/data/feature-autho
|
||||
import { Item } from '../../../../../core/shared/item.model';
|
||||
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
||||
import { WorkflowItem } from '../../../../../core/submission/models/workflowitem.model';
|
||||
import { XSRFService } from '../../../../../core/xsrf/xsrf.service';
|
||||
import { AuthServiceMock } from '../../../../../shared/mocks/auth.service.mock';
|
||||
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
|
||||
import { getMockLinkService } from '../../../../../shared/mocks/link-service.mock';
|
||||
@@ -67,6 +68,7 @@ describe('WorkflowItemSearchResultAdminWorkflowListElementComponent', () => {
|
||||
{ provide: ThemeService, useValue: getMockThemeService() },
|
||||
{ provide: AuthService, useValue: new AuthServiceMock() },
|
||||
{ provide: AuthorizationDataService, useValue: {} },
|
||||
{ provide: XSRFService, useValue: {} },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
})
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
InMemoryScrollingOptions,
|
||||
mapToCanActivate,
|
||||
Route,
|
||||
RouterConfigOptions,
|
||||
} from '@angular/router';
|
||||
@@ -23,18 +24,18 @@ import {
|
||||
} from './app-routing-paths';
|
||||
import { COLLECTION_MODULE_PATH } from './collection-page/collection-page-routing-paths';
|
||||
import { COMMUNITY_MODULE_PATH } from './community-page/community-page-routing-paths';
|
||||
import { AuthBlockingGuard } from './core/auth/auth-blocking.guard';
|
||||
import { AuthenticatedGuard } from './core/auth/authenticated.guard';
|
||||
import { authBlockingGuard } from './core/auth/auth-blocking.guard';
|
||||
import { authenticatedGuard } from './core/auth/authenticated.guard';
|
||||
import { GroupAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/group-administrator.guard';
|
||||
import { SiteAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
|
||||
import { SiteRegisterGuard } from './core/data/feature-authorization/feature-authorization-guard/site-register.guard';
|
||||
import { EndUserAgreementCurrentUserGuard } from './core/end-user-agreement/end-user-agreement-current-user.guard';
|
||||
import { ReloadGuard } from './core/reload/reload.guard';
|
||||
import { reloadGuard } from './core/reload/reload.guard';
|
||||
import { ForgotPasswordCheckGuard } from './core/rest-property/forgot-password-check-guard.guard';
|
||||
import { ServerCheckGuard } from './core/server-check/server-check.guard';
|
||||
import { ThemedForbiddenComponent } from './forbidden/themed-forbidden.component';
|
||||
import { ITEM_MODULE_PATH } from './item-page/item-page-routing-paths';
|
||||
import { MenuResolver } from './menu.resolver';
|
||||
import { menuResolver } from './menuResolver';
|
||||
import { provideSuggestionNotificationsState } from './notifications/provide-suggestion-notifications-state';
|
||||
import { ThemedPageErrorComponent } from './page-error/themed-page-error.component';
|
||||
import { ThemedPageInternalServerErrorComponent } from './page-internal-server-error/themed-page-internal-server-error.component';
|
||||
@@ -48,16 +49,16 @@ export const APP_ROUTES: Route[] = [
|
||||
{ path: ERROR_PAGE, component: ThemedPageErrorComponent },
|
||||
{
|
||||
path: '',
|
||||
canActivate: [AuthBlockingGuard],
|
||||
canActivate: [authBlockingGuard],
|
||||
canActivateChild: [ServerCheckGuard],
|
||||
resolve: [MenuResolver],
|
||||
resolve: [menuResolver],
|
||||
children: [
|
||||
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
||||
{
|
||||
path: 'reload/:rnd',
|
||||
component: ThemedPageNotFoundComponent,
|
||||
pathMatch: 'full',
|
||||
canActivate: [ReloadGuard],
|
||||
canActivate: [reloadGuard],
|
||||
},
|
||||
{
|
||||
path: 'home',
|
||||
@@ -65,105 +66,105 @@ export const APP_ROUTES: Route[] = [
|
||||
.then((m) => m.ROUTES),
|
||||
data: { showBreadcrumbs: false },
|
||||
providers: [provideSuggestionNotificationsState()],
|
||||
canActivate: [EndUserAgreementCurrentUserGuard],
|
||||
canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]),
|
||||
},
|
||||
{
|
||||
path: 'community-list',
|
||||
loadChildren: () => import('./community-list-page/community-list-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
canActivate: [EndUserAgreementCurrentUserGuard],
|
||||
canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]),
|
||||
},
|
||||
{
|
||||
path: 'id',
|
||||
loadChildren: () => import('./lookup-by-id/lookup-by-id-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
canActivate: [EndUserAgreementCurrentUserGuard],
|
||||
canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]),
|
||||
},
|
||||
{
|
||||
path: 'handle',
|
||||
loadChildren: () => import('./lookup-by-id/lookup-by-id-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
canActivate: [EndUserAgreementCurrentUserGuard],
|
||||
canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]),
|
||||
},
|
||||
{
|
||||
path: REGISTER_PATH,
|
||||
loadChildren: () => import('./register-page/register-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
canActivate: [SiteRegisterGuard],
|
||||
canActivate: mapToCanActivate([SiteRegisterGuard]),
|
||||
},
|
||||
{
|
||||
path: FORGOT_PASSWORD_PATH,
|
||||
loadChildren: () => import('./forgot-password/forgot-password-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
canActivate: [EndUserAgreementCurrentUserGuard, ForgotPasswordCheckGuard],
|
||||
canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard, ForgotPasswordCheckGuard]),
|
||||
},
|
||||
{
|
||||
path: COMMUNITY_MODULE_PATH,
|
||||
loadChildren: () => import('./community-page/community-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
canActivate: [EndUserAgreementCurrentUserGuard],
|
||||
canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]),
|
||||
},
|
||||
{
|
||||
path: COLLECTION_MODULE_PATH,
|
||||
loadChildren: () => import('./collection-page/collection-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
canActivate: [EndUserAgreementCurrentUserGuard],
|
||||
canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]),
|
||||
},
|
||||
{
|
||||
path: ITEM_MODULE_PATH,
|
||||
loadChildren: () => import('./item-page/item-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
canActivate: [EndUserAgreementCurrentUserGuard],
|
||||
canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]),
|
||||
},
|
||||
{
|
||||
path: 'entities/:entity-type',
|
||||
loadChildren: () => import('./item-page/item-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
canActivate: [EndUserAgreementCurrentUserGuard],
|
||||
canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]),
|
||||
},
|
||||
{
|
||||
path: LEGACY_BITSTREAM_MODULE_PATH,
|
||||
loadChildren: () => import('./bitstream-page/bitstream-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
canActivate: [EndUserAgreementCurrentUserGuard],
|
||||
canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]),
|
||||
},
|
||||
{
|
||||
path: BITSTREAM_MODULE_PATH,
|
||||
loadChildren: () => import('./bitstream-page/bitstream-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
canActivate: [EndUserAgreementCurrentUserGuard],
|
||||
canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]),
|
||||
},
|
||||
{
|
||||
path: 'mydspace',
|
||||
loadChildren: () => import('./my-dspace-page/my-dspace-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
providers: [provideSuggestionNotificationsState()],
|
||||
canActivate: [AuthenticatedGuard, EndUserAgreementCurrentUserGuard],
|
||||
canActivate: [authenticatedGuard, ...mapToCanActivate([EndUserAgreementCurrentUserGuard])],
|
||||
},
|
||||
{
|
||||
path: 'search',
|
||||
loadChildren: () => import('./search-page/search-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
canActivate: [EndUserAgreementCurrentUserGuard],
|
||||
canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]),
|
||||
},
|
||||
{
|
||||
path: 'browse',
|
||||
loadChildren: () => import('./browse-by/browse-by-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
canActivate: [EndUserAgreementCurrentUserGuard],
|
||||
canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]),
|
||||
},
|
||||
{
|
||||
path: ADMIN_MODULE_PATH,
|
||||
loadChildren: () => import('./admin/admin-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
canActivate: [SiteAdministratorGuard, EndUserAgreementCurrentUserGuard],
|
||||
canActivate: mapToCanActivate([SiteAdministratorGuard, EndUserAgreementCurrentUserGuard]),
|
||||
},
|
||||
{
|
||||
path: NOTIFICATIONS_MODULE_PATH,
|
||||
loadChildren: () => import('./quality-assurance-notifications-pages/notifications-pages-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
providers: [provideSuggestionNotificationsState()],
|
||||
canActivate: [AuthenticatedGuard, EndUserAgreementCurrentUserGuard],
|
||||
canActivate: [authenticatedGuard, ...mapToCanActivate([EndUserAgreementCurrentUserGuard])],
|
||||
},
|
||||
{
|
||||
path: 'login',
|
||||
@@ -180,47 +181,47 @@ export const APP_ROUTES: Route[] = [
|
||||
loadChildren: () => import('./submit-page/submit-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
providers: [provideSubmissionState()],
|
||||
canActivate: [EndUserAgreementCurrentUserGuard],
|
||||
canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]),
|
||||
},
|
||||
{
|
||||
path: 'import-external',
|
||||
loadChildren: () => import('./import-external-page/import-external-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
canActivate: [EndUserAgreementCurrentUserGuard],
|
||||
canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]),
|
||||
},
|
||||
{
|
||||
path: 'workspaceitems',
|
||||
loadChildren: () => import('./workspaceitems-edit-page/workspaceitems-edit-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
providers: [provideSubmissionState()],
|
||||
canActivate: [EndUserAgreementCurrentUserGuard],
|
||||
canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]),
|
||||
},
|
||||
{
|
||||
path: WORKFLOW_ITEM_MODULE_PATH,
|
||||
providers: [provideSubmissionState()],
|
||||
loadChildren: () => import('./workflowitems-edit-page/workflowitems-edit-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
canActivate: [EndUserAgreementCurrentUserGuard],
|
||||
canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]),
|
||||
},
|
||||
{
|
||||
path: PROFILE_MODULE_PATH,
|
||||
loadChildren: () => import('./profile-page/profile-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
providers: [provideSuggestionNotificationsState()],
|
||||
canActivate: [AuthenticatedGuard, EndUserAgreementCurrentUserGuard],
|
||||
canActivate: [authenticatedGuard, ...mapToCanActivate([EndUserAgreementCurrentUserGuard])],
|
||||
},
|
||||
{
|
||||
path: PROCESS_MODULE_PATH,
|
||||
loadChildren: () => import('./process-page/process-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
canActivate: [AuthenticatedGuard, EndUserAgreementCurrentUserGuard],
|
||||
canActivate: [authenticatedGuard, ...mapToCanActivate([EndUserAgreementCurrentUserGuard])],
|
||||
},
|
||||
{
|
||||
path: SUGGESTION_MODULE_PATH,
|
||||
loadChildren: () => import('./suggestions-page/suggestions-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
providers: [provideSuggestionNotificationsState()],
|
||||
canActivate: [AuthenticatedGuard, EndUserAgreementCurrentUserGuard],
|
||||
canActivate: [authenticatedGuard, ...mapToCanActivate([EndUserAgreementCurrentUserGuard])],
|
||||
},
|
||||
{
|
||||
path: INFO_MODULE_PATH,
|
||||
@@ -229,7 +230,7 @@ export const APP_ROUTES: Route[] = [
|
||||
{
|
||||
path: REQUEST_COPY_MODULE_PATH,
|
||||
loadChildren: () => import('./request-copy/request-copy-routes').then((m) => m.ROUTES),
|
||||
canActivate: [EndUserAgreementCurrentUserGuard],
|
||||
canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]),
|
||||
},
|
||||
{
|
||||
path: FORBIDDEN_PATH,
|
||||
@@ -239,7 +240,7 @@ export const APP_ROUTES: Route[] = [
|
||||
path: 'statistics',
|
||||
loadChildren: () => import('./statistics-page/statistics-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
canActivate: [EndUserAgreementCurrentUserGuard],
|
||||
canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]),
|
||||
},
|
||||
{
|
||||
path: HEALTH_PAGE_PATH,
|
||||
@@ -249,13 +250,13 @@ export const APP_ROUTES: Route[] = [
|
||||
{
|
||||
path: ACCESS_CONTROL_MODULE_PATH,
|
||||
loadChildren: () => import('./access-control/access-control-routes').then((m) => m.ROUTES),
|
||||
canActivate: [GroupAdministratorGuard, EndUserAgreementCurrentUserGuard],
|
||||
canActivate: mapToCanActivate([GroupAdministratorGuard, EndUserAgreementCurrentUserGuard]),
|
||||
},
|
||||
{
|
||||
path: 'subscriptions',
|
||||
loadChildren: () => import('./subscriptions-page/subscriptions-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
canActivate: [AuthenticatedGuard],
|
||||
canActivate: [authenticatedGuard],
|
||||
},
|
||||
{ path: '**', pathMatch: 'full', component: ThemedPageNotFoundComponent },
|
||||
],
|
||||
|
@@ -36,7 +36,7 @@ export function getBitstreamRequestACopyRoute(item, bitstream): { routerLink: st
|
||||
}
|
||||
export const COAR_NOTIFY_SUPPORT = 'coar-notify-support';
|
||||
|
||||
export const HOME_PAGE_PATH = 'admin';
|
||||
export const HOME_PAGE_PATH = 'home';
|
||||
|
||||
export function getHomePageRoute() {
|
||||
return `/${HOME_PAGE_PATH}`;
|
||||
|
@@ -1,17 +1,17 @@
|
||||
import { Route } from '@angular/router';
|
||||
|
||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { BitstreamBreadcrumbResolver } from '../core/breadcrumbs/bitstream-breadcrumb.resolver';
|
||||
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { authenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { bitstreamBreadcrumbResolver } from '../core/breadcrumbs/bitstream-breadcrumb.resolver';
|
||||
import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { ResourcePolicyCreateComponent } from '../shared/resource-policies/create/resource-policy-create.component';
|
||||
import { ResourcePolicyEditComponent } from '../shared/resource-policies/edit/resource-policy-edit.component';
|
||||
import { ResourcePolicyResolver } from '../shared/resource-policies/resolvers/resource-policy.resolver';
|
||||
import { ResourcePolicyTargetResolver } from '../shared/resource-policies/resolvers/resource-policy-target.resolver';
|
||||
import { resourcePolicyResolver } from '../shared/resource-policies/resolvers/resource-policy.resolver';
|
||||
import { resourcePolicyTargetResolver } from '../shared/resource-policies/resolvers/resource-policy-target.resolver';
|
||||
import { BitstreamAuthorizationsComponent } from './bitstream-authorizations/bitstream-authorizations.component';
|
||||
import { BitstreamDownloadPageComponent } from './bitstream-download-page/bitstream-download-page.component';
|
||||
import { BitstreamPageResolver } from './bitstream-page.resolver';
|
||||
import { bitstreamPageResolver } from './bitstream-page.resolver';
|
||||
import { ThemedEditBitstreamPageComponent } from './edit-bitstream-page/themed-edit-bitstream-page.component';
|
||||
import { LegacyBitstreamUrlResolver } from './legacy-bitstream-url.resolver';
|
||||
import { legacyBitstreamUrlResolver } from './legacy-bitstream-url.resolver';
|
||||
|
||||
const EDIT_BITSTREAM_PATH = ':id/edit';
|
||||
const EDIT_BITSTREAM_AUTHORIZATIONS_PATH = ':id/authorizations';
|
||||
@@ -25,7 +25,7 @@ export const ROUTES: Route[] = [
|
||||
path: 'handle/:prefix/:suffix/:filename',
|
||||
component: BitstreamDownloadPageComponent,
|
||||
resolve: {
|
||||
bitstream: LegacyBitstreamUrlResolver,
|
||||
bitstream: legacyBitstreamUrlResolver,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -33,7 +33,7 @@ export const ROUTES: Route[] = [
|
||||
path: ':prefix/:suffix/:sequence_id/:filename',
|
||||
component: BitstreamDownloadPageComponent,
|
||||
resolve: {
|
||||
bitstream: LegacyBitstreamUrlResolver,
|
||||
bitstream: legacyBitstreamUrlResolver,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -41,17 +41,17 @@ export const ROUTES: Route[] = [
|
||||
path: ':id/download',
|
||||
component: BitstreamDownloadPageComponent,
|
||||
resolve: {
|
||||
bitstream: BitstreamPageResolver,
|
||||
bitstream: bitstreamPageResolver,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: EDIT_BITSTREAM_PATH,
|
||||
component: ThemedEditBitstreamPageComponent,
|
||||
resolve: {
|
||||
bitstream: BitstreamPageResolver,
|
||||
breadcrumb: BitstreamBreadcrumbResolver,
|
||||
bitstream: bitstreamPageResolver,
|
||||
breadcrumb: bitstreamBreadcrumbResolver,
|
||||
},
|
||||
canActivate: [AuthenticatedGuard],
|
||||
canActivate: [authenticatedGuard],
|
||||
},
|
||||
{
|
||||
path: EDIT_BITSTREAM_AUTHORIZATIONS_PATH,
|
||||
@@ -59,7 +59,7 @@ export const ROUTES: Route[] = [
|
||||
{
|
||||
path: 'create',
|
||||
resolve: {
|
||||
resourcePolicyTarget: ResourcePolicyTargetResolver,
|
||||
resourcePolicyTarget: resourcePolicyTargetResolver,
|
||||
},
|
||||
component: ResourcePolicyCreateComponent,
|
||||
data: { title: 'resource-policies.create.page.title', showBreadcrumbs: true },
|
||||
@@ -67,8 +67,8 @@ export const ROUTES: Route[] = [
|
||||
{
|
||||
path: 'edit',
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
resourcePolicy: ResourcePolicyResolver,
|
||||
breadcrumb: i18nBreadcrumbResolver,
|
||||
resourcePolicy: resourcePolicyResolver,
|
||||
},
|
||||
component: ResourcePolicyEditComponent,
|
||||
data: { breadcrumbKey: 'item.edit', title: 'resource-policies.edit.page.title', showBreadcrumbs: true },
|
||||
@@ -76,8 +76,8 @@ export const ROUTES: Route[] = [
|
||||
{
|
||||
path: '',
|
||||
resolve: {
|
||||
bitstream: BitstreamPageResolver,
|
||||
breadcrumb: BitstreamBreadcrumbResolver,
|
||||
bitstream: bitstreamPageResolver,
|
||||
breadcrumb: bitstreamBreadcrumbResolver,
|
||||
},
|
||||
component: BitstreamAuthorizationsComponent,
|
||||
data: { title: 'bitstream.edit.authorizations.title', showBreadcrumbs: true },
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -24,32 +25,20 @@ export const BITSTREAM_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Bitstream>[] = [
|
||||
];
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific bitstream before the route is activated
|
||||
* Method for resolving a bitstream based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {BitstreamDataService} bitstreamService
|
||||
* @returns Observable<<RemoteData<Item>> Emits the found bitstream based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class BitstreamPageResolver {
|
||||
constructor(private bitstreamService: BitstreamDataService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving a bitstream based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns Observable<<RemoteData<Item>> Emits the found bitstream based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Bitstream>> {
|
||||
return this.bitstreamService.findById(route.params.id, true, false, ...this.followLinks)
|
||||
.pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Method that returns the follow links to already resolve
|
||||
* The self links defined in this list are expected to be requested somewhere in the near future
|
||||
* Requesting them as embeds will limit the number of requests
|
||||
*/
|
||||
get followLinks(): FollowLinkConfig<Bitstream>[] {
|
||||
return BITSTREAM_PAGE_LINKS_TO_FOLLOW;
|
||||
}
|
||||
}
|
||||
export const bitstreamPageResolver: ResolveFn<RemoteData<Bitstream>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
bitstreamService: BitstreamDataService = inject(BitstreamDataService),
|
||||
): Observable<RemoteData<Bitstream>> => {
|
||||
return bitstreamService.findById(route.params.id, true, false, ...BITSTREAM_PAGE_LINKS_TO_FOLLOW)
|
||||
.pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
};
|
||||
|
@@ -4,10 +4,10 @@ import { TestScheduler } from 'rxjs/testing';
|
||||
import { BitstreamDataService } from '../core/data/bitstream-data.service';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { RequestEntryState } from '../core/data/request-entry-state.model';
|
||||
import { LegacyBitstreamUrlResolver } from './legacy-bitstream-url.resolver';
|
||||
import { legacyBitstreamUrlResolver } from './legacy-bitstream-url.resolver';
|
||||
|
||||
describe(`LegacyBitstreamUrlResolver`, () => {
|
||||
let resolver: LegacyBitstreamUrlResolver;
|
||||
describe(`legacyBitstreamUrlResolver`, () => {
|
||||
let resolver: any;
|
||||
let bitstreamDataService: BitstreamDataService;
|
||||
let testScheduler;
|
||||
let remoteDataMocks;
|
||||
@@ -33,7 +33,7 @@ describe(`LegacyBitstreamUrlResolver`, () => {
|
||||
bitstreamDataService = {
|
||||
findByItemHandle: () => undefined,
|
||||
} as any;
|
||||
resolver = new LegacyBitstreamUrlResolver(bitstreamDataService);
|
||||
resolver = legacyBitstreamUrlResolver;
|
||||
});
|
||||
|
||||
describe(`resolve`, () => {
|
||||
@@ -51,7 +51,7 @@ describe(`LegacyBitstreamUrlResolver`, () => {
|
||||
});
|
||||
it(`should call findByItemHandle with the handle, sequence id, and filename from the route`, () => {
|
||||
testScheduler.run(() => {
|
||||
resolver.resolve(route, state);
|
||||
resolver(route, state, bitstreamDataService);
|
||||
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalledWith(
|
||||
`${route.params.prefix}/${route.params.suffix}`,
|
||||
route.params.sequence_id,
|
||||
@@ -78,7 +78,7 @@ describe(`LegacyBitstreamUrlResolver`, () => {
|
||||
});
|
||||
it(`should call findByItemHandle with the handle and filename from the route, and the sequence ID from the queryParams`, () => {
|
||||
testScheduler.run(() => {
|
||||
resolver.resolve(route, state);
|
||||
resolver(route, state, bitstreamDataService);
|
||||
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalledWith(
|
||||
`${route.params.prefix}/${route.params.suffix}`,
|
||||
route.queryParams.sequenceId,
|
||||
@@ -100,7 +100,7 @@ describe(`LegacyBitstreamUrlResolver`, () => {
|
||||
});
|
||||
it(`should call findByItemHandle with the handle, and filename from the route`, () => {
|
||||
testScheduler.run(() => {
|
||||
resolver.resolve(route, state);
|
||||
resolver(route, state, bitstreamDataService);
|
||||
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalledWith(
|
||||
`${route.params.prefix}/${route.params.suffix}`,
|
||||
undefined,
|
||||
@@ -123,7 +123,7 @@ describe(`LegacyBitstreamUrlResolver`, () => {
|
||||
c: remoteDataMocks.Error,
|
||||
};
|
||||
|
||||
expectObservable(resolver.resolve(route, state)).toBe(expected, values);
|
||||
expectObservable(resolver(route, state, bitstreamDataService)).toBe(expected, values);
|
||||
});
|
||||
});
|
||||
it(`...succeeded`, () => {
|
||||
@@ -138,7 +138,7 @@ describe(`LegacyBitstreamUrlResolver`, () => {
|
||||
c: remoteDataMocks.Success,
|
||||
};
|
||||
|
||||
expectObservable(resolver.resolve(route, state)).toBe(expected, values);
|
||||
expectObservable(resolver(route, state, bitstreamDataService)).toBe(expected, values);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -12,41 +13,34 @@ import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||
import { hasNoValue } from '../shared/empty.util';
|
||||
|
||||
/**
|
||||
* This class resolves a bitstream based on the DSpace 6 XMLUI or JSPUI bitstream download URLs
|
||||
* Resolve a bitstream based on the handle of the item, and the sequence id or the filename of the
|
||||
* bitstream
|
||||
*
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {BitstreamDataService} bitstreamDataService
|
||||
* @returns Observable<<RemoteData<Item>> Emits the found bitstream based on the parameters in
|
||||
* current route, or an error if something went wrong
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class LegacyBitstreamUrlResolver {
|
||||
constructor(protected bitstreamDataService: BitstreamDataService) {
|
||||
export const legacyBitstreamUrlResolver: ResolveFn<RemoteData<Bitstream>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
bitstreamDataService: BitstreamDataService = inject(BitstreamDataService),
|
||||
): Observable<RemoteData<Bitstream>> => {
|
||||
const prefix = route.params.prefix;
|
||||
const suffix = route.params.suffix;
|
||||
const filename = route.params.filename;
|
||||
|
||||
let sequenceId = route.params.sequence_id;
|
||||
if (hasNoValue(sequenceId)) {
|
||||
sequenceId = route.queryParams.sequenceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a bitstream based on the handle of the item, and the sequence id or the filename of the
|
||||
* bitstream
|
||||
*
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns Observable<<RemoteData<Item>> Emits the found bitstream based on the parameters in
|
||||
* current route, or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
|
||||
Observable<RemoteData<Bitstream>> {
|
||||
const prefix = route.params.prefix;
|
||||
const suffix = route.params.suffix;
|
||||
const filename = route.params.filename;
|
||||
|
||||
let sequenceId = route.params.sequence_id;
|
||||
if (hasNoValue(sequenceId)) {
|
||||
sequenceId = route.queryParams.sequenceId;
|
||||
}
|
||||
|
||||
return this.bitstreamDataService.findByItemHandle(
|
||||
`${prefix}/${suffix}`,
|
||||
sequenceId,
|
||||
filename,
|
||||
).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
}
|
||||
}
|
||||
return bitstreamDataService.findByItemHandle(
|
||||
`${prefix}/${suffix}`,
|
||||
sequenceId,
|
||||
filename,
|
||||
).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
};
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -19,30 +20,28 @@ import {
|
||||
import { hasValue } from '../shared/empty.util';
|
||||
|
||||
/**
|
||||
* The class that resolves the BreadcrumbConfig object for a DSpaceObject on a browse by page
|
||||
* Method for resolving a breadcrumb config object
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {DSOBreadcrumbsService} breadcrumbService
|
||||
* @param {DSpaceObjectDataService} dataService
|
||||
* @returns BreadcrumbConfig object
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class BrowseByDSOBreadcrumbResolver {
|
||||
constructor(protected breadcrumbService: DSOBreadcrumbsService, protected dataService: DSpaceObjectDataService) {
|
||||
export const browseByDSOBreadcrumbResolver: ResolveFn<BreadcrumbConfig<Community | Collection>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
breadcrumbService: DSOBreadcrumbsService = inject(DSOBreadcrumbsService),
|
||||
dataService: DSpaceObjectDataService = inject(DSpaceObjectDataService),
|
||||
): Observable<BreadcrumbConfig<Community | Collection>> => {
|
||||
const uuid = route.queryParams.scope;
|
||||
if (hasValue(uuid)) {
|
||||
return dataService.findById(uuid).pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
map((object: Community | Collection) => {
|
||||
return { provider: breadcrumbService, key: object, url: getDSORoute(object) };
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving a breadcrumb config object
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns BreadcrumbConfig object
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<BreadcrumbConfig<Community | Collection>> {
|
||||
const uuid = route.queryParams.scope;
|
||||
if (hasValue(uuid)) {
|
||||
return this.dataService.findById(uuid).pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
map((object: Community | Collection) => {
|
||||
return { provider: this.breadcrumbService, key: object, url: getDSORoute(object) };
|
||||
}),
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
@@ -6,12 +6,12 @@ import {
|
||||
createSuccessfulRemoteDataObject$,
|
||||
} from '../shared/remote-data.utils';
|
||||
import { RouterStub } from '../shared/testing/router.stub';
|
||||
import { BrowseByGuard } from './browse-by-guard';
|
||||
import { browseByGuard } from './browse-by-guard';
|
||||
import { BrowseByDataType } from './browse-by-switcher/browse-by-data-type';
|
||||
|
||||
describe('BrowseByGuard', () => {
|
||||
describe('browseByGuard', () => {
|
||||
describe('canActivate', () => {
|
||||
let guard: BrowseByGuard;
|
||||
let guard: any;
|
||||
let translateService: any;
|
||||
let browseDefinitionService: any;
|
||||
let router: any;
|
||||
@@ -35,7 +35,7 @@ describe('BrowseByGuard', () => {
|
||||
|
||||
router = new RouterStub() as any;
|
||||
|
||||
guard = new BrowseByGuard(translateService, browseDefinitionService, router);
|
||||
guard = browseByGuard;
|
||||
});
|
||||
|
||||
it('should return true, and sets up the data correctly, with a scope and value', () => {
|
||||
@@ -53,7 +53,7 @@ describe('BrowseByGuard', () => {
|
||||
value,
|
||||
},
|
||||
};
|
||||
guard.canActivate(scopedRoute as any, undefined)
|
||||
guard(scopedRoute as any, undefined, browseDefinitionService, router, translateService)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(canActivate) => {
|
||||
@@ -86,7 +86,7 @@ describe('BrowseByGuard', () => {
|
||||
},
|
||||
};
|
||||
|
||||
guard.canActivate(scopedNoValueRoute as any, undefined)
|
||||
guard(scopedNoValueRoute, undefined, browseDefinitionService, router, translateService)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(canActivate) => {
|
||||
@@ -123,7 +123,7 @@ describe('BrowseByGuard', () => {
|
||||
},
|
||||
};
|
||||
|
||||
guard.canActivate(scopedNoValueRoute as any, undefined).pipe(
|
||||
guard(scopedNoValueRoute as any, undefined, browseDefinitionService, router, translateService).pipe(
|
||||
first(),
|
||||
).subscribe((canActivate) => {
|
||||
const result = {
|
||||
@@ -154,7 +154,8 @@ describe('BrowseByGuard', () => {
|
||||
value,
|
||||
},
|
||||
};
|
||||
guard.canActivate(route as any, undefined)
|
||||
|
||||
guard(route as any, undefined, browseDefinitionService, router, translateService)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(canActivate) => {
|
||||
@@ -189,7 +190,8 @@ describe('BrowseByGuard', () => {
|
||||
value,
|
||||
},
|
||||
};
|
||||
guard.canActivate(scopedRoute as any, undefined)
|
||||
|
||||
guard(scopedRoute as any, undefined, browseDefinitionService, router, translateService)
|
||||
.pipe(first())
|
||||
.subscribe((canActivate) => {
|
||||
expect(router.navigate).toHaveBeenCalled();
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
CanActivateFn,
|
||||
Data,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
@@ -25,55 +26,47 @@ import {
|
||||
hasValue,
|
||||
} from '../shared/empty.util';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
/**
|
||||
* A guard taking care of the correct route.data being set for the Browse-By components
|
||||
*/
|
||||
export class BrowseByGuard {
|
||||
|
||||
constructor(
|
||||
protected translate: TranslateService,
|
||||
protected browseDefinitionService: BrowseDefinitionDataService,
|
||||
protected router: Router,
|
||||
) {
|
||||
}
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
|
||||
const title = route.data.title;
|
||||
const id = route.params.id || route.queryParams.id || route.data.id;
|
||||
let browseDefinition$: Observable<BrowseDefinition | undefined>;
|
||||
if (hasNoValue(route.data.browseDefinition) && hasValue(id)) {
|
||||
browseDefinition$ = this.browseDefinitionService.findById(id).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
map((browseDefinitionRD: RemoteData<BrowseDefinition>) => browseDefinitionRD.payload),
|
||||
);
|
||||
} else {
|
||||
browseDefinition$ = observableOf(route.data.browseDefinition);
|
||||
}
|
||||
const scope = route.queryParams.scope ?? route.parent?.params.id;
|
||||
const value = route.queryParams.value;
|
||||
const metadataTranslated = this.translate.instant(`browse.metadata.${id}`);
|
||||
return browseDefinition$.pipe(
|
||||
switchMap((browseDefinition: BrowseDefinition | undefined) => {
|
||||
if (hasValue(browseDefinition)) {
|
||||
route.data = this.createData(title, id, browseDefinition, metadataTranslated, value, route, scope);
|
||||
return observableOf(true);
|
||||
} else {
|
||||
void this.router.navigate([PAGE_NOT_FOUND_PATH]);
|
||||
return observableOf(false);
|
||||
}
|
||||
}),
|
||||
export const browseByGuard: CanActivateFn = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
browseDefinitionService: BrowseDefinitionDataService = inject(BrowseDefinitionDataService),
|
||||
router: Router = inject(Router),
|
||||
translate: TranslateService = inject(TranslateService),
|
||||
): Observable<boolean> => {
|
||||
const title = route.data.title;
|
||||
const id = route.params.id || route.queryParams.id || route.data.id;
|
||||
let browseDefinition$: Observable<BrowseDefinition | undefined>;
|
||||
if (hasNoValue(route.data.browseDefinition) && hasValue(id)) {
|
||||
browseDefinition$ = browseDefinitionService.findById(id).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
map((browseDefinitionRD: RemoteData<BrowseDefinition>) => browseDefinitionRD.payload),
|
||||
);
|
||||
} else {
|
||||
browseDefinition$ = observableOf(route.data.browseDefinition);
|
||||
}
|
||||
const scope = route.queryParams.scope ?? route.parent?.params.id;
|
||||
const value = route.queryParams.value;
|
||||
const metadataTranslated = translate.instant(`browse.metadata.${id}`);
|
||||
return browseDefinition$.pipe(
|
||||
switchMap((browseDefinition: BrowseDefinition | undefined) => {
|
||||
if (hasValue(browseDefinition)) {
|
||||
route.data = createData(title, id, browseDefinition, metadataTranslated, value, route, scope);
|
||||
return observableOf(true);
|
||||
} else {
|
||||
void router.navigate([PAGE_NOT_FOUND_PATH]);
|
||||
return observableOf(false);
|
||||
}
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
private createData(title: string, id: string, browseDefinition: BrowseDefinition, field: string, value: string, route: ActivatedRouteSnapshot, scope: string): Data {
|
||||
return Object.assign({}, route.data, {
|
||||
title: title,
|
||||
id: id,
|
||||
browseDefinition: browseDefinition,
|
||||
field: field,
|
||||
value: hasValue(value) ? `"${value}"` : '',
|
||||
scope: scope,
|
||||
});
|
||||
}
|
||||
function createData(title: string, id: string, browseDefinition: BrowseDefinition, field: string, value: string, route: ActivatedRouteSnapshot, scope: string): Data {
|
||||
return Object.assign({}, route.data, {
|
||||
title: title,
|
||||
id: id,
|
||||
browseDefinition: browseDefinition,
|
||||
field: field,
|
||||
value: hasValue(value) ? `"${value}"` : '',
|
||||
scope: scope,
|
||||
});
|
||||
}
|
||||
|
@@ -1,32 +1,23 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
|
||||
import { BreadcrumbConfig } from '../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
||||
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service';
|
||||
import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
|
||||
/**
|
||||
* This class resolves a BreadcrumbConfig object with an i18n key string for a route
|
||||
* It adds the metadata field of the current browse-by page
|
||||
* Method for resolving a browse-by i18n breadcrumb configuration object
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns BreadcrumbConfig object for a browse-by page
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class BrowseByI18nBreadcrumbResolver extends I18nBreadcrumbResolver {
|
||||
constructor(protected breadcrumbService: I18nBreadcrumbsService) {
|
||||
super(breadcrumbService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving a browse-by i18n breadcrumb configuration object
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns BreadcrumbConfig object for a browse-by page
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): BreadcrumbConfig<string> {
|
||||
const extendedBreadcrumbKey = route.data.breadcrumbKey + '.' + route.params.id;
|
||||
route.data = Object.assign({}, route.data, { breadcrumbKey: extendedBreadcrumbKey });
|
||||
return super.resolve(route, state);
|
||||
}
|
||||
}
|
||||
export const browseByI18nBreadcrumbResolver: ResolveFn<BreadcrumbConfig<string>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
): BreadcrumbConfig<string> => {
|
||||
const extendedBreadcrumbKey = route.data.breadcrumbKey + '.' + route.params.id;
|
||||
route.data = Object.assign({}, route.data, { breadcrumbKey: extendedBreadcrumbKey });
|
||||
return i18nBreadcrumbResolver(route, state) as BreadcrumbConfig<string>;
|
||||
};
|
||||
|
@@ -1,24 +1,24 @@
|
||||
import { Route } from '@angular/router';
|
||||
|
||||
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||
import { BrowseByDSOBreadcrumbResolver } from './browse-by-dso-breadcrumb.resolver';
|
||||
import { BrowseByGuard } from './browse-by-guard';
|
||||
import { BrowseByI18nBreadcrumbResolver } from './browse-by-i18n-breadcrumb.resolver';
|
||||
import { dsoEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||
import { browseByDSOBreadcrumbResolver } from './browse-by-dso-breadcrumb.resolver';
|
||||
import { browseByGuard } from './browse-by-guard';
|
||||
import { browseByI18nBreadcrumbResolver } from './browse-by-i18n-breadcrumb.resolver';
|
||||
import { BrowseByPageComponent } from './browse-by-page/browse-by-page.component';
|
||||
|
||||
export const ROUTES: Route[] = [
|
||||
{
|
||||
path: '',
|
||||
resolve: {
|
||||
breadcrumb: BrowseByDSOBreadcrumbResolver,
|
||||
menu: DSOEditMenuResolver,
|
||||
breadcrumb: browseByDSOBreadcrumbResolver,
|
||||
menu: dsoEditMenuResolver,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: ':id',
|
||||
component: BrowseByPageComponent,
|
||||
canActivate: [BrowseByGuard],
|
||||
resolve: { breadcrumb: BrowseByI18nBreadcrumbResolver },
|
||||
canActivate: [browseByGuard],
|
||||
resolve: { breadcrumb: browseByI18nBreadcrumbResolver },
|
||||
data: { title: 'browse.title.page', breadcrumbKey: 'browse.metadata' },
|
||||
},
|
||||
],
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
@@ -13,8 +14,9 @@ import { AuthService } from '../core/auth/auth.service';
|
||||
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
|
||||
import { DsoPageSingleFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard';
|
||||
import { FeatureID } from '../core/data/feature-authorization/feature-id';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { Collection } from '../core/shared/collection.model';
|
||||
import { CollectionPageResolver } from './collection-page.resolver';
|
||||
import { collectionPageResolver } from './collection-page.resolver';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -23,11 +25,13 @@ import { CollectionPageResolver } from './collection-page.resolver';
|
||||
* Guard for preventing unauthorized access to certain {@link Collection} pages requiring administrator rights
|
||||
*/
|
||||
export class CollectionPageAdministratorGuard extends DsoPageSingleFeatureGuard<Collection> {
|
||||
constructor(protected resolver: CollectionPageResolver,
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
|
||||
protected resolver: ResolveFn<RemoteData<Collection>> = collectionPageResolver;
|
||||
|
||||
constructor(protected authorizationService: AuthorizationDataService,
|
||||
protected router: Router,
|
||||
protected authService: AuthService) {
|
||||
super(resolver, authorizationService, router, authService);
|
||||
super(authorizationService, router, authService);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,16 +1,19 @@
|
||||
import { Route } from '@angular/router';
|
||||
import {
|
||||
mapToCanActivate,
|
||||
Route,
|
||||
} from '@angular/router';
|
||||
|
||||
import { BrowseByGuard } from '../browse-by/browse-by-guard';
|
||||
import { BrowseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver';
|
||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { CollectionBreadcrumbResolver } from '../core/breadcrumbs/collection-breadcrumb.resolver';
|
||||
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { browseByGuard } from '../browse-by/browse-by-guard';
|
||||
import { browseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver';
|
||||
import { authenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { collectionBreadcrumbResolver } from '../core/breadcrumbs/collection-breadcrumb.resolver';
|
||||
import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component';
|
||||
import { ComcolSearchSectionComponent } from '../shared/comcol/sections/comcol-search-section/comcol-search-section.component';
|
||||
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||
import { dsoEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
||||
import { CollectionPageResolver } from './collection-page.resolver';
|
||||
import { collectionPageResolver } from './collection-page.resolver';
|
||||
import { CollectionPageAdministratorGuard } from './collection-page-administrator.guard';
|
||||
import {
|
||||
COLLECTION_CREATE_PATH,
|
||||
@@ -18,9 +21,9 @@ import {
|
||||
ITEMTEMPLATE_PATH,
|
||||
} from './collection-page-routing-paths';
|
||||
import { CreateCollectionPageComponent } from './create-collection-page/create-collection-page.component';
|
||||
import { CreateCollectionPageGuard } from './create-collection-page/create-collection-page.guard';
|
||||
import { createCollectionPageGuard } from './create-collection-page/create-collection-page.guard';
|
||||
import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component';
|
||||
import { ItemTemplatePageResolver } from './edit-item-template-page/item-template-page.resolver';
|
||||
import { itemTemplatePageResolver } from './edit-item-template-page/item-template-page.resolver';
|
||||
import { ThemedEditItemTemplatePageComponent } from './edit-item-template-page/themed-edit-item-template-page.component';
|
||||
import { ThemedCollectionPageComponent } from './themed-collection-page.component';
|
||||
|
||||
@@ -29,14 +32,14 @@ export const ROUTES: Route[] = [
|
||||
{
|
||||
path: COLLECTION_CREATE_PATH,
|
||||
component: CreateCollectionPageComponent,
|
||||
canActivate: [AuthenticatedGuard, CreateCollectionPageGuard],
|
||||
canActivate: [authenticatedGuard, createCollectionPageGuard],
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
resolve: {
|
||||
dso: CollectionPageResolver,
|
||||
breadcrumb: CollectionBreadcrumbResolver,
|
||||
menu: DSOEditMenuResolver,
|
||||
dso: collectionPageResolver,
|
||||
breadcrumb: collectionBreadcrumbResolver,
|
||||
menu: dsoEditMenuResolver,
|
||||
},
|
||||
runGuardsAndResolvers: 'always',
|
||||
children: [
|
||||
@@ -44,21 +47,21 @@ export const ROUTES: Route[] = [
|
||||
path: COLLECTION_EDIT_PATH,
|
||||
loadChildren: () => import('./edit-collection-page/edit-collection-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
canActivate: [CollectionPageAdministratorGuard],
|
||||
canActivate: mapToCanActivate([CollectionPageAdministratorGuard]),
|
||||
},
|
||||
{
|
||||
path: 'delete',
|
||||
pathMatch: 'full',
|
||||
component: DeleteCollectionPageComponent,
|
||||
canActivate: [AuthenticatedGuard],
|
||||
canActivate: [authenticatedGuard],
|
||||
},
|
||||
{
|
||||
path: ITEMTEMPLATE_PATH,
|
||||
component: ThemedEditItemTemplatePageComponent,
|
||||
canActivate: [AuthenticatedGuard],
|
||||
canActivate: [authenticatedGuard],
|
||||
resolve: {
|
||||
item: ItemTemplatePageResolver,
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
item: itemTemplatePageResolver,
|
||||
breadcrumb: i18nBreadcrumbResolver,
|
||||
},
|
||||
data: { title: 'collection.edit.template.title', breadcrumbKey: 'collection.edit.template' },
|
||||
},
|
||||
@@ -75,9 +78,9 @@ export const ROUTES: Route[] = [
|
||||
path: 'browse/:id',
|
||||
pathMatch: 'full',
|
||||
component: ComcolBrowseByComponent,
|
||||
canActivate: [BrowseByGuard],
|
||||
canActivate: [browseByGuard],
|
||||
resolve: {
|
||||
breadcrumb: BrowseByI18nBreadcrumbResolver,
|
||||
breadcrumb: browseByI18nBreadcrumbResolver,
|
||||
},
|
||||
data: { breadcrumbKey: 'browse.metadata' },
|
||||
},
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||
import { CollectionPageResolver } from './collection-page.resolver';
|
||||
import { collectionPageResolver } from './collection-page.resolver';
|
||||
|
||||
describe('CollectionPageResolver', () => {
|
||||
describe('collectionPageResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: CollectionPageResolver;
|
||||
let resolver: any;
|
||||
let collectionService: any;
|
||||
let store: any;
|
||||
const uuid = '1234-65487-12354-1235';
|
||||
@@ -17,12 +18,11 @@ describe('CollectionPageResolver', () => {
|
||||
store = jasmine.createSpyObj('store', {
|
||||
dispatch: {},
|
||||
});
|
||||
resolver = new CollectionPageResolver(collectionService, store);
|
||||
resolver = collectionPageResolver;
|
||||
});
|
||||
|
||||
it('should resolve a collection with the correct id', (done) => {
|
||||
resolver.resolve({ params: { id: uuid } } as any, { url: 'current-url' } as any)
|
||||
.pipe(first())
|
||||
(resolver({ params: { id: uuid } } as any, { url: 'current-url' } as any, collectionService, store) as Observable<any>).pipe(first())
|
||||
.subscribe(
|
||||
(resolved) => {
|
||||
expect(resolved.payload.id).toEqual(uuid);
|
||||
|
@@ -1,11 +1,13 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { AppState } from '../app.reducer';
|
||||
import { CollectionDataService } from '../core/data/collection-data.service';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { ResolvedAction } from '../core/resolving/resolver.actions';
|
||||
@@ -28,37 +30,32 @@ export const COLLECTION_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Collection>[] = [
|
||||
];
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific collection before the route is activated
|
||||
* Method for resolving a collection based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param collectionService
|
||||
* @param store
|
||||
* @returns Observable<<RemoteData<Collection>> Emits the found collection based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CollectionPageResolver {
|
||||
constructor(
|
||||
private collectionService: CollectionDataService,
|
||||
private store: Store<any>,
|
||||
) {
|
||||
}
|
||||
export const collectionPageResolver: ResolveFn<RemoteData<Collection>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
collectionService: CollectionDataService = inject(CollectionDataService),
|
||||
store: Store<AppState> = inject(Store<AppState>),
|
||||
): Observable<RemoteData<Collection>> => {
|
||||
const collectionRD$ = collectionService.findById(
|
||||
route.params.id,
|
||||
true,
|
||||
false,
|
||||
...COLLECTION_PAGE_LINKS_TO_FOLLOW,
|
||||
).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
|
||||
/**
|
||||
* Method for resolving a collection based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns Observable<<RemoteData<Collection>> Emits the found collection based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Collection>> {
|
||||
const collectionRD$ = this.collectionService.findById(
|
||||
route.params.id,
|
||||
true,
|
||||
false,
|
||||
...COLLECTION_PAGE_LINKS_TO_FOLLOW,
|
||||
).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
collectionRD$.subscribe((collectionRD: RemoteData<Collection>) => {
|
||||
store.dispatch(new ResolvedAction(state.url, collectionRD.payload));
|
||||
});
|
||||
|
||||
collectionRD$.subscribe((collectionRD: RemoteData<Collection>) => {
|
||||
this.store.dispatch(new ResolvedAction(state.url, collectionRD.payload));
|
||||
});
|
||||
|
||||
return collectionRD$;
|
||||
}
|
||||
}
|
||||
return collectionRD$;
|
||||
};
|
||||
|
@@ -6,11 +6,11 @@ import {
|
||||
createFailedRemoteDataObject$,
|
||||
createSuccessfulRemoteDataObject$,
|
||||
} from '../../shared/remote-data.utils';
|
||||
import { CreateCollectionPageGuard } from './create-collection-page.guard';
|
||||
import { createCollectionPageGuard } from './create-collection-page.guard';
|
||||
|
||||
describe('CreateCollectionPageGuard', () => {
|
||||
describe('createCollectionPageGuard', () => {
|
||||
describe('canActivate', () => {
|
||||
let guard: CreateCollectionPageGuard;
|
||||
let guard: any;
|
||||
let router;
|
||||
let communityDataServiceStub: any;
|
||||
|
||||
@@ -28,11 +28,11 @@ describe('CreateCollectionPageGuard', () => {
|
||||
};
|
||||
router = new RouterMock();
|
||||
|
||||
guard = new CreateCollectionPageGuard(router, communityDataServiceStub);
|
||||
guard = createCollectionPageGuard;
|
||||
});
|
||||
|
||||
it('should return true when the parent ID resolves to a community', () => {
|
||||
guard.canActivate({ queryParams: { parent: 'valid-id' } } as any, undefined)
|
||||
guard({ queryParams: { parent: 'valid-id' } } as any, undefined, communityDataServiceStub, router)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(canActivate) =>
|
||||
@@ -41,7 +41,7 @@ describe('CreateCollectionPageGuard', () => {
|
||||
});
|
||||
|
||||
it('should return false when no parent ID has been provided', () => {
|
||||
guard.canActivate({ queryParams: { } } as any, undefined)
|
||||
guard({ queryParams: { } } as any, undefined, communityDataServiceStub, router)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(canActivate) =>
|
||||
@@ -50,7 +50,7 @@ describe('CreateCollectionPageGuard', () => {
|
||||
});
|
||||
|
||||
it('should return false when the parent ID does not resolve to a community', () => {
|
||||
guard.canActivate({ queryParams: { parent: 'invalid-id' } } as any, undefined)
|
||||
guard({ queryParams: { parent: 'invalid-id' } } as any, undefined, communityDataServiceStub, router)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(canActivate) =>
|
||||
@@ -59,7 +59,7 @@ describe('CreateCollectionPageGuard', () => {
|
||||
});
|
||||
|
||||
it('should return false when the parent ID resolves to an error response', () => {
|
||||
guard.canActivate({ queryParams: { parent: 'error-id' } } as any, undefined)
|
||||
guard({ queryParams: { parent: 'error-id' } } as any, undefined, communityDataServiceStub, router)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(canActivate) =>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
CanActivate,
|
||||
CanActivateFn,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
@@ -24,34 +24,29 @@ import {
|
||||
} from '../../shared/empty.util';
|
||||
|
||||
/**
|
||||
* Prevent creation of a collection without a parent community provided
|
||||
* @class CreateCollectionPageGuard
|
||||
* True when either a parent ID query parameter has been provided and the parent ID resolves to a valid parent community
|
||||
* Reroutes to a 404 page when the page cannot be activated
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CreateCollectionPageGuard implements CanActivate {
|
||||
public constructor(private router: Router, private communityService: CommunityDataService) {
|
||||
export const createCollectionPageGuard: CanActivateFn = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
communityService: CommunityDataService = inject(CommunityDataService),
|
||||
router: Router = inject(Router),
|
||||
): Observable<boolean> => {
|
||||
const parentID = route.queryParams.parent;
|
||||
if (hasNoValue(parentID)) {
|
||||
router.navigate(['/404']);
|
||||
return observableOf(false);
|
||||
}
|
||||
return communityService.findById(parentID)
|
||||
.pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
map((communityRD: RemoteData<Community>) => hasValue(communityRD) && communityRD.hasSucceeded && hasValue(communityRD.payload)),
|
||||
tap((isValid: boolean) => {
|
||||
if (!isValid) {
|
||||
router.navigate(['/404']);
|
||||
}
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* True when either a parent ID query parameter has been provided and the parent ID resolves to a valid parent community
|
||||
* Reroutes to a 404 page when the page cannot be activated
|
||||
* @method canActivate
|
||||
*/
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
|
||||
const parentID = route.queryParams.parent;
|
||||
if (hasNoValue(parentID)) {
|
||||
this.router.navigate(['/404']);
|
||||
return observableOf(false);
|
||||
}
|
||||
return this.communityService.findById(parentID)
|
||||
.pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
map((communityRD: RemoteData<Community>) => hasValue(communityRD) && communityRD.hasSucceeded && hasValue(communityRD.payload)),
|
||||
tap((isValid: boolean) => {
|
||||
if (!isValid) {
|
||||
this.router.navigate(['/404']);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +1,14 @@
|
||||
import { Route } from '@angular/router';
|
||||
import {
|
||||
mapToCanActivate,
|
||||
Route,
|
||||
} from '@angular/router';
|
||||
|
||||
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { i18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { CollectionAdministratorGuard } from '../../core/data/feature-authorization/feature-authorization-guard/collection-administrator.guard';
|
||||
import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component';
|
||||
import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component';
|
||||
import { ResourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver';
|
||||
import { ResourcePolicyTargetResolver } from '../../shared/resource-policies/resolvers/resource-policy-target.resolver';
|
||||
import { resourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver';
|
||||
import { resourcePolicyTargetResolver } from '../../shared/resource-policies/resolvers/resource-policy-target.resolver';
|
||||
import { CollectionItemMapperComponent } from '../collection-item-mapper/collection-item-mapper.component';
|
||||
import { CollectionAccessControlComponent } from './collection-access-control/collection-access-control.component';
|
||||
import { CollectionAuthorizationsComponent } from './collection-authorizations/collection-authorizations.component';
|
||||
@@ -23,11 +26,11 @@ export const ROUTES: Route[] = [
|
||||
{
|
||||
path: '',
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
breadcrumb: i18nBreadcrumbResolver,
|
||||
},
|
||||
data: { breadcrumbKey: 'collection.edit' },
|
||||
component: EditCollectionPageComponent,
|
||||
canActivate: [CollectionAdministratorGuard],
|
||||
canActivate: mapToCanActivate([CollectionAdministratorGuard]),
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
@@ -70,7 +73,7 @@ export const ROUTES: Route[] = [
|
||||
{
|
||||
path: 'create',
|
||||
resolve: {
|
||||
resourcePolicyTarget: ResourcePolicyTargetResolver,
|
||||
resourcePolicyTarget: resourcePolicyTargetResolver,
|
||||
},
|
||||
component: ResourcePolicyCreateComponent,
|
||||
data: { title: 'resource-policies.create.page.title' },
|
||||
@@ -78,7 +81,7 @@ export const ROUTES: Route[] = [
|
||||
{
|
||||
path: 'edit',
|
||||
resolve: {
|
||||
resourcePolicy: ResourcePolicyResolver,
|
||||
resourcePolicy: resourcePolicyResolver,
|
||||
},
|
||||
component: ResourcePolicyEditComponent,
|
||||
data: { title: 'resource-policies.edit.page.title' },
|
||||
|
@@ -1,27 +1,24 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||
import { DSONameServiceMock } from '../../shared/mocks/dso-name.service.mock';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||
import { ItemTemplatePageResolver } from './item-template-page.resolver';
|
||||
import { itemTemplatePageResolver } from './item-template-page.resolver';
|
||||
|
||||
describe('ItemTemplatePageResolver', () => {
|
||||
describe('itemTemplatePageResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: ItemTemplatePageResolver;
|
||||
let resolver: any;
|
||||
let itemTemplateService: any;
|
||||
let dsoNameService: DSONameServiceMock;
|
||||
const uuid = '1234-65487-12354-1235';
|
||||
|
||||
beforeEach(() => {
|
||||
itemTemplateService = {
|
||||
findByCollectionID: (id: string) => createSuccessfulRemoteDataObject$({ id }),
|
||||
};
|
||||
dsoNameService = new DSONameServiceMock();
|
||||
resolver = new ItemTemplatePageResolver(dsoNameService as DSONameService, itemTemplateService);
|
||||
resolver = itemTemplatePageResolver;
|
||||
});
|
||||
|
||||
it('should resolve an item template with the correct id', (done) => {
|
||||
resolver.resolve({ params: { id: uuid } } as any, undefined)
|
||||
(resolver({ params: { id: uuid } } as any, undefined, itemTemplateService) as Observable<any>)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(resolved) => {
|
||||
|
@@ -1,38 +1,23 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||
import { ItemTemplateDataService } from '../../core/data/item-template-data.service';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
|
||||
import { followLink } from '../../shared/utils/follow-link-config.model';
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific collection's item template before the route is activated
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ItemTemplatePageResolver {
|
||||
constructor(
|
||||
public dsoNameService: DSONameService,
|
||||
private itemTemplateService: ItemTemplateDataService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving a collection's item template based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns Observable<<RemoteData<Collection>> Emits the found item template based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
|
||||
return this.itemTemplateService.findByCollectionID(route.params.id, true, false, followLink('templateItemOf')).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
}
|
||||
}
|
||||
export const itemTemplatePageResolver: ResolveFn<RemoteData<Item>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
itemTemplateService: ItemTemplateDataService = inject(ItemTemplateDataService),
|
||||
): Observable<RemoteData<Item>> => {
|
||||
return itemTemplateService.findByCollectionID(route.params.id, true, false, followLink('templateItemOf')).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
};
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Route } from '@angular/router';
|
||||
|
||||
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { ThemedCommunityListPageComponent } from './themed-community-list-page.component';
|
||||
|
||||
/**
|
||||
@@ -12,7 +12,7 @@ export const ROUTES: Route[] = [
|
||||
component: ThemedCommunityListPageComponent,
|
||||
pathMatch: 'full',
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
breadcrumb: i18nBreadcrumbResolver,
|
||||
},
|
||||
data: { title: 'communityList.tabTitle', breadcrumbKey: 'communityList' },
|
||||
},
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
@@ -13,8 +14,9 @@ import { AuthService } from '../core/auth/auth.service';
|
||||
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
|
||||
import { DsoPageSingleFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard';
|
||||
import { FeatureID } from '../core/data/feature-authorization/feature-id';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { Community } from '../core/shared/community.model';
|
||||
import { CommunityPageResolver } from './community-page.resolver';
|
||||
import { communityPageResolver } from './community-page.resolver';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -23,11 +25,13 @@ import { CommunityPageResolver } from './community-page.resolver';
|
||||
* Guard for preventing unauthorized access to certain {@link Community} pages requiring administrator rights
|
||||
*/
|
||||
export class CommunityPageAdministratorGuard extends DsoPageSingleFeatureGuard<Community> {
|
||||
constructor(protected resolver: CommunityPageResolver,
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
|
||||
protected resolver: ResolveFn<RemoteData<Community>> = communityPageResolver;
|
||||
|
||||
constructor(protected authorizationService: AuthorizationDataService,
|
||||
protected router: Router,
|
||||
protected authService: AuthService) {
|
||||
super(resolver, authorizationService, router, authService);
|
||||
super(authorizationService, router, authService);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,23 +1,26 @@
|
||||
import { Route } from '@angular/router';
|
||||
import {
|
||||
mapToCanActivate,
|
||||
Route,
|
||||
} from '@angular/router';
|
||||
|
||||
import { BrowseByGuard } from '../browse-by/browse-by-guard';
|
||||
import { BrowseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver';
|
||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { CommunityBreadcrumbResolver } from '../core/breadcrumbs/community-breadcrumb.resolver';
|
||||
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { browseByGuard } from '../browse-by/browse-by-guard';
|
||||
import { browseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver';
|
||||
import { authenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { communityBreadcrumbResolver } from '../core/breadcrumbs/community-breadcrumb.resolver';
|
||||
import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component';
|
||||
import { ComcolSearchSectionComponent } from '../shared/comcol/sections/comcol-search-section/comcol-search-section.component';
|
||||
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||
import { dsoEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
||||
import { CommunityPageResolver } from './community-page.resolver';
|
||||
import { communityPageResolver } from './community-page.resolver';
|
||||
import { CommunityPageAdministratorGuard } from './community-page-administrator.guard';
|
||||
import {
|
||||
COMMUNITY_CREATE_PATH,
|
||||
COMMUNITY_EDIT_PATH,
|
||||
} from './community-page-routing-paths';
|
||||
import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component';
|
||||
import { CreateCommunityPageGuard } from './create-community-page/create-community-page.guard';
|
||||
import { createCommunityPageGuard } from './create-community-page/create-community-page.guard';
|
||||
import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component';
|
||||
import { SubComColSectionComponent } from './sections/sub-com-col-section/sub-com-col-section.component';
|
||||
import { ThemedCommunityPageComponent } from './themed-community-page.component';
|
||||
@@ -26,14 +29,14 @@ export const ROUTES: Route[] = [
|
||||
{
|
||||
path: COMMUNITY_CREATE_PATH,
|
||||
component: CreateCommunityPageComponent,
|
||||
canActivate: [AuthenticatedGuard, CreateCommunityPageGuard],
|
||||
canActivate: [authenticatedGuard, createCommunityPageGuard],
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
resolve: {
|
||||
dso: CommunityPageResolver,
|
||||
breadcrumb: CommunityBreadcrumbResolver,
|
||||
menu: DSOEditMenuResolver,
|
||||
dso: communityPageResolver,
|
||||
breadcrumb: communityBreadcrumbResolver,
|
||||
menu: dsoEditMenuResolver,
|
||||
},
|
||||
runGuardsAndResolvers: 'always',
|
||||
children: [
|
||||
@@ -41,13 +44,13 @@ export const ROUTES: Route[] = [
|
||||
path: COMMUNITY_EDIT_PATH,
|
||||
loadChildren: () => import('./edit-community-page/edit-community-page-routes')
|
||||
.then((m) => m.ROUTES),
|
||||
canActivate: [CommunityPageAdministratorGuard],
|
||||
canActivate: mapToCanActivate([CommunityPageAdministratorGuard]),
|
||||
},
|
||||
{
|
||||
path: 'delete',
|
||||
pathMatch: 'full',
|
||||
component: DeleteCommunityPageComponent,
|
||||
canActivate: [AuthenticatedGuard],
|
||||
canActivate: [authenticatedGuard],
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
@@ -63,7 +66,7 @@ export const ROUTES: Route[] = [
|
||||
pathMatch: 'full',
|
||||
component: SubComColSectionComponent,
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
breadcrumb: i18nBreadcrumbResolver,
|
||||
},
|
||||
data: { breadcrumbKey: 'community.subcoms-cols' },
|
||||
},
|
||||
@@ -71,9 +74,9 @@ export const ROUTES: Route[] = [
|
||||
path: 'browse/:id',
|
||||
pathMatch: 'full',
|
||||
component: ComcolBrowseByComponent,
|
||||
canActivate: [BrowseByGuard],
|
||||
canActivate: [browseByGuard],
|
||||
resolve: {
|
||||
breadcrumb: BrowseByI18nBreadcrumbResolver,
|
||||
breadcrumb: browseByI18nBreadcrumbResolver,
|
||||
},
|
||||
data: { breadcrumbKey: 'browse.metadata' },
|
||||
},
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||
import { CommunityPageResolver } from './community-page.resolver';
|
||||
import { communityPageResolver } from './community-page.resolver';
|
||||
|
||||
describe('CommunityPageResolver', () => {
|
||||
describe('communityPageResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: CommunityPageResolver;
|
||||
let resolver: any;
|
||||
let communityService: any;
|
||||
let store: any;
|
||||
const uuid = '1234-65487-12354-1235';
|
||||
@@ -17,11 +18,11 @@ describe('CommunityPageResolver', () => {
|
||||
store = jasmine.createSpyObj('store', {
|
||||
dispatch: {},
|
||||
});
|
||||
resolver = new CommunityPageResolver(communityService, store);
|
||||
resolver = communityPageResolver;
|
||||
});
|
||||
|
||||
it('should resolve a community with the correct id', (done) => {
|
||||
resolver.resolve({ params: { id: uuid } } as any, { url: 'current-url' } as any)
|
||||
(resolver({ params: { id: uuid } } as any, { url: 'current-url' } as any, communityService, store) as Observable<any>)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(resolved) => {
|
||||
|
@@ -1,11 +1,13 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { AppState } from '../app.reducer';
|
||||
import { CommunityDataService } from '../core/data/community-data.service';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { ResolvedAction } from '../core/resolving/resolver.actions';
|
||||
@@ -28,37 +30,32 @@ export const COMMUNITY_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Community>[] = [
|
||||
];
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific community before the route is activated
|
||||
* Method for resolving a community based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {CommunityDataService} communityService
|
||||
* @param {Store} store
|
||||
* @returns Observable<<RemoteData<Community>> Emits the found community based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CommunityPageResolver {
|
||||
constructor(
|
||||
private communityService: CommunityDataService,
|
||||
private store: Store<any>,
|
||||
) {
|
||||
}
|
||||
export const communityPageResolver: ResolveFn<RemoteData<Community>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
communityService: CommunityDataService = inject(CommunityDataService),
|
||||
store: Store<AppState> = inject(Store<AppState>),
|
||||
): Observable<RemoteData<Community>> => {
|
||||
const communityRD$ = communityService.findById(
|
||||
route.params.id,
|
||||
true,
|
||||
false,
|
||||
...COMMUNITY_PAGE_LINKS_TO_FOLLOW,
|
||||
).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
|
||||
/**
|
||||
* Method for resolving a community based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns Observable<<RemoteData<Community>> Emits the found community based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Community>> {
|
||||
const communityRD$ = this.communityService.findById(
|
||||
route.params.id,
|
||||
true,
|
||||
false,
|
||||
...COMMUNITY_PAGE_LINKS_TO_FOLLOW,
|
||||
).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
communityRD$.subscribe((communityRD: RemoteData<Community>) => {
|
||||
store.dispatch(new ResolvedAction(state.url, communityRD.payload));
|
||||
});
|
||||
|
||||
communityRD$.subscribe((communityRD: RemoteData<Community>) => {
|
||||
this.store.dispatch(new ResolvedAction(state.url, communityRD.payload));
|
||||
});
|
||||
|
||||
return communityRD$;
|
||||
}
|
||||
}
|
||||
return communityRD$;
|
||||
};
|
||||
|
@@ -6,11 +6,11 @@ import {
|
||||
createFailedRemoteDataObject$,
|
||||
createSuccessfulRemoteDataObject$,
|
||||
} from '../../shared/remote-data.utils';
|
||||
import { CreateCommunityPageGuard } from './create-community-page.guard';
|
||||
import { createCommunityPageGuard } from './create-community-page.guard';
|
||||
|
||||
describe('CreateCommunityPageGuard', () => {
|
||||
describe('createCommunityPageGuard', () => {
|
||||
describe('canActivate', () => {
|
||||
let guard: CreateCommunityPageGuard;
|
||||
let guard: any;
|
||||
let router;
|
||||
let communityDataServiceStub: any;
|
||||
|
||||
@@ -28,11 +28,11 @@ describe('CreateCommunityPageGuard', () => {
|
||||
};
|
||||
router = new RouterMock();
|
||||
|
||||
guard = new CreateCommunityPageGuard(router, communityDataServiceStub);
|
||||
guard = createCommunityPageGuard;
|
||||
});
|
||||
|
||||
it('should return true when the parent ID resolves to a community', () => {
|
||||
guard.canActivate({ queryParams: { parent: 'valid-id' } } as any, undefined)
|
||||
guard({ queryParams: { parent: 'valid-id' } } as any, undefined, communityDataServiceStub, router)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(canActivate) =>
|
||||
@@ -41,7 +41,7 @@ describe('CreateCommunityPageGuard', () => {
|
||||
});
|
||||
|
||||
it('should return true when no parent ID has been provided', () => {
|
||||
guard.canActivate({ queryParams: { } } as any, undefined)
|
||||
guard({ queryParams: { } } as any, undefined, communityDataServiceStub, router)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(canActivate) =>
|
||||
@@ -50,7 +50,7 @@ describe('CreateCommunityPageGuard', () => {
|
||||
});
|
||||
|
||||
it('should return false when the parent ID does not resolve to a community', () => {
|
||||
guard.canActivate({ queryParams: { parent: 'invalid-id' } } as any, undefined)
|
||||
guard({ queryParams: { parent: 'invalid-id' } } as any, undefined, communityDataServiceStub, router)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(canActivate) =>
|
||||
@@ -59,7 +59,7 @@ describe('CreateCommunityPageGuard', () => {
|
||||
});
|
||||
|
||||
it('should return false when the parent ID resolves to an error response', () => {
|
||||
guard.canActivate({ queryParams: { parent: 'error-id' } } as any, undefined)
|
||||
guard({ queryParams: { parent: 'error-id' } } as any, undefined, communityDataServiceStub, router)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(canActivate) =>
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
CanActivateFn,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
@@ -23,35 +24,29 @@ import {
|
||||
} from '../../shared/empty.util';
|
||||
|
||||
/**
|
||||
* Prevent creation of a community with an invalid parent community provided
|
||||
* @class CreateCommunityPageGuard
|
||||
* True when either NO parent ID query parameter has been provided, or the parent ID resolves to a valid parent community
|
||||
* Reroutes to a 404 page when the page cannot be activated
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CreateCommunityPageGuard {
|
||||
public constructor(private router: Router, private communityService: CommunityDataService) {
|
||||
export const createCommunityPageGuard: CanActivateFn = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
communityService: CommunityDataService = inject(CommunityDataService),
|
||||
router: Router = inject(Router),
|
||||
): Observable<boolean> => {
|
||||
const parentID = route.queryParams.parent;
|
||||
if (hasNoValue(parentID)) {
|
||||
return observableOf(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* True when either NO parent ID query parameter has been provided, or the parent ID resolves to a valid parent community
|
||||
* Reroutes to a 404 page when the page cannot be activated
|
||||
* @method canActivate
|
||||
*/
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
|
||||
const parentID = route.queryParams.parent;
|
||||
if (hasNoValue(parentID)) {
|
||||
return observableOf(true);
|
||||
}
|
||||
|
||||
return this.communityService.findById(parentID)
|
||||
.pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
map((communityRD: RemoteData<Community>) => hasValue(communityRD) && communityRD.hasSucceeded && hasValue(communityRD.payload)),
|
||||
tap((isValid: boolean) => {
|
||||
if (!isValid) {
|
||||
this.router.navigate(['/404']);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return communityService.findById(parentID)
|
||||
.pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
map((communityRD: RemoteData<Community>) => hasValue(communityRD) && communityRD.hasSucceeded && hasValue(communityRD.payload)),
|
||||
tap((isValid: boolean) => {
|
||||
if (!isValid) {
|
||||
router.navigate(['/404']);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
};
|
||||
|
@@ -1,11 +1,14 @@
|
||||
import { Route } from '@angular/router';
|
||||
import {
|
||||
mapToCanActivate,
|
||||
Route,
|
||||
} from '@angular/router';
|
||||
|
||||
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { i18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { CommunityAdministratorGuard } from '../../core/data/feature-authorization/feature-authorization-guard/community-administrator.guard';
|
||||
import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component';
|
||||
import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component';
|
||||
import { ResourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver';
|
||||
import { ResourcePolicyTargetResolver } from '../../shared/resource-policies/resolvers/resource-policy-target.resolver';
|
||||
import { resourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver';
|
||||
import { resourcePolicyTargetResolver } from '../../shared/resource-policies/resolvers/resource-policy-target.resolver';
|
||||
import { CommunityAccessControlComponent } from './community-access-control/community-access-control.component';
|
||||
import { CommunityAuthorizationsComponent } from './community-authorizations/community-authorizations.component';
|
||||
import { CommunityCurateComponent } from './community-curate/community-curate.component';
|
||||
@@ -21,11 +24,11 @@ export const ROUTES: Route[] = [
|
||||
{
|
||||
path: '',
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
breadcrumb: i18nBreadcrumbResolver,
|
||||
},
|
||||
data: { breadcrumbKey: 'community.edit' },
|
||||
component: EditCommunityPageComponent,
|
||||
canActivate: [CommunityAdministratorGuard],
|
||||
canActivate: mapToCanActivate([CommunityAdministratorGuard]),
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
@@ -63,7 +66,7 @@ export const ROUTES: Route[] = [
|
||||
{
|
||||
path: 'create',
|
||||
resolve: {
|
||||
resourcePolicyTarget: ResourcePolicyTargetResolver,
|
||||
resourcePolicyTarget: resourcePolicyTargetResolver,
|
||||
},
|
||||
component: ResourcePolicyCreateComponent,
|
||||
data: { title: 'resource-policies.create.page.title' },
|
||||
@@ -71,7 +74,7 @@ export const ROUTES: Route[] = [
|
||||
{
|
||||
path: 'edit',
|
||||
resolve: {
|
||||
resourcePolicy: ResourcePolicyResolver,
|
||||
resourcePolicy: resourcePolicyResolver,
|
||||
},
|
||||
component: ResourcePolicyEditComponent,
|
||||
data: { title: 'resource-policies.edit.page.title' },
|
||||
|
@@ -17,10 +17,10 @@ import {
|
||||
storeModuleConfig,
|
||||
} from '../../app.reducer';
|
||||
import { authReducer } from './auth.reducer';
|
||||
import { AuthBlockingGuard } from './auth-blocking.guard';
|
||||
import { authBlockingGuard } from './auth-blocking.guard';
|
||||
|
||||
describe('AuthBlockingGuard', () => {
|
||||
let guard: AuthBlockingGuard;
|
||||
describe('authBlockingGuard', () => {
|
||||
let guard: any;
|
||||
let initialState;
|
||||
let store: Store<AppState>;
|
||||
let mockStore: MockStore<AppState>;
|
||||
@@ -44,7 +44,7 @@ describe('AuthBlockingGuard', () => {
|
||||
],
|
||||
providers: [
|
||||
provideMockStore({ initialState }),
|
||||
{ provide: AuthBlockingGuard, useValue: guard },
|
||||
{ provide: authBlockingGuard, useValue: guard },
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
@@ -52,14 +52,14 @@ describe('AuthBlockingGuard', () => {
|
||||
beforeEach(() => {
|
||||
store = TestBed.inject(Store);
|
||||
mockStore = store as MockStore<AppState>;
|
||||
guard = new AuthBlockingGuard(store);
|
||||
guard = authBlockingGuard;
|
||||
});
|
||||
|
||||
describe(`canActivate`, () => {
|
||||
|
||||
describe(`when authState.blocking is undefined`, () => {
|
||||
it(`should not emit anything`, (done) => {
|
||||
expect(guard.canActivate()).toBeObservable(cold('-'));
|
||||
expect(guard(null, null, store)).toBeObservable(cold('-'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -77,7 +77,7 @@ describe('AuthBlockingGuard', () => {
|
||||
});
|
||||
|
||||
it(`should not emit anything`, (done) => {
|
||||
expect(guard.canActivate()).toBeObservable(cold('-'));
|
||||
expect(guard(null, null, store)).toBeObservable(cold('-'));
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -95,7 +95,7 @@ describe('AuthBlockingGuard', () => {
|
||||
});
|
||||
|
||||
it(`should succeed`, (done) => {
|
||||
expect(guard.canActivate()).toBeObservable(cold('(a|)', { a: true }));
|
||||
expect(guard(null, null, store)).toBeObservable(cold('(a|)', { a: true }));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@@ -1,4 +1,9 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
CanActivateFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import {
|
||||
select,
|
||||
Store,
|
||||
@@ -19,24 +24,16 @@ import { isAuthenticationBlocking } from './selectors';
|
||||
* route until the authentication status has loaded.
|
||||
* To ensure all rest requests get the correct auth header.
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AuthBlockingGuard {
|
||||
export const authBlockingGuard: CanActivateFn = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
store: Store<AppState> = inject(Store<AppState>),
|
||||
): Observable<boolean> => {
|
||||
return store.pipe(select(isAuthenticationBlocking)).pipe(
|
||||
map((isBlocking: boolean) => isBlocking === false),
|
||||
distinctUntilChanged(),
|
||||
filter((finished: boolean) => finished === true),
|
||||
take(1),
|
||||
);
|
||||
};
|
||||
|
||||
constructor(private store: Store<AppState>) {
|
||||
}
|
||||
|
||||
/**
|
||||
* True when the authentication isn't blocking everything
|
||||
*/
|
||||
canActivate(): Observable<boolean> {
|
||||
return this.store.pipe(select(isAuthenticationBlocking)).pipe(
|
||||
map((isBlocking: boolean) => isBlocking === false),
|
||||
distinctUntilChanged(),
|
||||
filter((finished: boolean) => finished === true),
|
||||
take(1),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,6 +1,8 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
CanActivateChildFn,
|
||||
CanActivateFn,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
UrlTree,
|
||||
@@ -16,7 +18,7 @@ import {
|
||||
switchMap,
|
||||
} from 'rxjs/operators';
|
||||
|
||||
import { CoreState } from '../core-state.model';
|
||||
import { AppState } from '../../app.reducer';
|
||||
import {
|
||||
AuthService,
|
||||
LOGIN_ROUTE,
|
||||
@@ -28,49 +30,35 @@ import {
|
||||
|
||||
/**
|
||||
* Prevent unauthorized activating and loading of routes
|
||||
* @class AuthenticatedGuard
|
||||
* True when user is authenticated
|
||||
* UrlTree with redirect to login page when user isn't authenticated
|
||||
* @method canActivate
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AuthenticatedGuard {
|
||||
export const authenticatedGuard: CanActivateFn = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
authService: AuthService = inject(AuthService),
|
||||
router: Router = inject(Router),
|
||||
store: Store<AppState> = inject(Store<AppState>),
|
||||
): Observable<boolean | UrlTree> => {
|
||||
const url = state.url;
|
||||
// redirect to sign in page if user is not authenticated
|
||||
return store.pipe(select(isAuthenticationLoading)).pipe(
|
||||
find((isLoading: boolean) => isLoading === false),
|
||||
switchMap(() => store.pipe(select(isAuthenticated))),
|
||||
map((authenticated) => {
|
||||
if (authenticated) {
|
||||
return authenticated;
|
||||
} else {
|
||||
authService.setRedirectUrl(url);
|
||||
authService.removeToken();
|
||||
return router.createUrlTree([LOGIN_ROUTE]);
|
||||
}
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
constructor(private authService: AuthService, private router: Router, private store: Store<CoreState>) {}
|
||||
|
||||
/**
|
||||
* True when user is authenticated
|
||||
* UrlTree with redirect to login page when user isn't authenticated
|
||||
* @method canActivate
|
||||
*/
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> {
|
||||
const url = state.url;
|
||||
return this.handleAuth(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* True when user is authenticated
|
||||
* UrlTree with redirect to login page when user isn't authenticated
|
||||
* @method canActivateChild
|
||||
*/
|
||||
canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> {
|
||||
return this.canActivate(route, state);
|
||||
}
|
||||
|
||||
private handleAuth(url: string): Observable<boolean | UrlTree> {
|
||||
// redirect to sign in page if user is not authenticated
|
||||
return this.store.pipe(select(isAuthenticationLoading)).pipe(
|
||||
find((isLoading: boolean) => isLoading === false),
|
||||
switchMap(() => this.store.pipe(select(isAuthenticated))),
|
||||
map((authenticated) => {
|
||||
if (authenticated) {
|
||||
return authenticated;
|
||||
} else {
|
||||
this.authService.setRedirectUrl(url);
|
||||
this.authService.removeToken();
|
||||
return this.router.createUrlTree([LOGIN_ROUTE]);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
export const AuthenticatedGuardChild: CanActivateChildFn = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
) => authenticatedGuard(route, state);
|
||||
|
@@ -1,31 +1,36 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { BITSTREAM_PAGE_LINKS_TO_FOLLOW } from '../../bitstream-page/bitstream-page.resolver';
|
||||
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { BitstreamDataService } from '../data/bitstream-data.service';
|
||||
import { Bitstream } from '../shared/bitstream.model';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import { BitstreamBreadcrumbsService } from './bitstream-breadcrumbs.service';
|
||||
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
|
||||
|
||||
/**
|
||||
* The class that resolves the BreadcrumbConfig object for an Item
|
||||
* The resolve function that resolves the BreadcrumbConfig object for an Item
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class BitstreamBreadcrumbResolver extends DSOBreadcrumbResolver<Bitstream> {
|
||||
constructor(
|
||||
protected breadcrumbService: BitstreamBreadcrumbsService, protected dataService: BitstreamDataService) {
|
||||
super(breadcrumbService, dataService);
|
||||
}
|
||||
export const bitstreamBreadcrumbResolver: ResolveFn<BreadcrumbConfig<Bitstream>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
breadcrumbService: BitstreamBreadcrumbsService = inject(BitstreamBreadcrumbsService),
|
||||
dataService: BitstreamDataService = inject(BitstreamDataService),
|
||||
): Observable<BreadcrumbConfig<Bitstream>> => {
|
||||
const linksToFollow: FollowLinkConfig<DSpaceObject>[] = BITSTREAM_PAGE_LINKS_TO_FOLLOW as FollowLinkConfig<DSpaceObject>[];
|
||||
return DSOBreadcrumbResolver(
|
||||
route,
|
||||
state,
|
||||
breadcrumbService,
|
||||
dataService,
|
||||
...linksToFollow,
|
||||
) as Observable<BreadcrumbConfig<Bitstream>>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Method that returns the follow links to already resolve
|
||||
* The self links defined in this list are expected to be requested somewhere in the near future
|
||||
* Requesting them as embeds will limit the number of requests
|
||||
*/
|
||||
get followLinks(): FollowLinkConfig<Bitstream>[] {
|
||||
return BITSTREAM_PAGE_LINKS_TO_FOLLOW;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,29 +1,36 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
||||
import { COLLECTION_PAGE_LINKS_TO_FOLLOW } from '../../collection-page/collection-page.resolver';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { CollectionDataService } from '../data/collection-data.service';
|
||||
import { Collection } from '../shared/collection.model';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
|
||||
import { DSOBreadcrumbsService } from './dso-breadcrumbs.service';
|
||||
|
||||
/**
|
||||
* The class that resolves the BreadcrumbConfig object for a Collection
|
||||
* The resolve function that resolves the BreadcrumbConfig object for a Collection
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class CollectionBreadcrumbResolver extends DSOBreadcrumbResolver<Collection> {
|
||||
constructor(protected breadcrumbService: DSOBreadcrumbsService, protected dataService: CollectionDataService) {
|
||||
super(breadcrumbService, dataService);
|
||||
}
|
||||
export const collectionBreadcrumbResolver: ResolveFn<BreadcrumbConfig<Collection>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
breadcrumbService: DSOBreadcrumbsService = inject(DSOBreadcrumbsService),
|
||||
dataService: CollectionDataService = inject(CollectionDataService),
|
||||
): Observable<BreadcrumbConfig<Collection>> => {
|
||||
const linksToFollow: FollowLinkConfig<DSpaceObject>[] = COLLECTION_PAGE_LINKS_TO_FOLLOW as FollowLinkConfig<DSpaceObject>[];
|
||||
return DSOBreadcrumbResolver(
|
||||
route,
|
||||
state,
|
||||
breadcrumbService,
|
||||
dataService,
|
||||
...linksToFollow,
|
||||
) as Observable<BreadcrumbConfig<Collection>>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Method that returns the follow links to already resolve
|
||||
* The self links defined in this list are expected to be requested somewhere in the near future
|
||||
* Requesting them as embeds will limit the number of requests
|
||||
*/
|
||||
get followLinks(): FollowLinkConfig<Collection>[] {
|
||||
return COLLECTION_PAGE_LINKS_TO_FOLLOW;
|
||||
}
|
||||
}
|
||||
|
@@ -1,29 +1,35 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
||||
import { COMMUNITY_PAGE_LINKS_TO_FOLLOW } from '../../community-page/community-page.resolver';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { CommunityDataService } from '../data/community-data.service';
|
||||
import { Community } from '../shared/community.model';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
|
||||
import { DSOBreadcrumbsService } from './dso-breadcrumbs.service';
|
||||
|
||||
/**
|
||||
* The class that resolves the BreadcrumbConfig object for a Community
|
||||
* The resolve function that resolves the BreadcrumbConfig object for a Community
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class CommunityBreadcrumbResolver extends DSOBreadcrumbResolver<Community> {
|
||||
constructor(protected breadcrumbService: DSOBreadcrumbsService, protected dataService: CommunityDataService) {
|
||||
super(breadcrumbService, dataService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that returns the follow links to already resolve
|
||||
* The self links defined in this list are expected to be requested somewhere in the near future
|
||||
* Requesting them as embeds will limit the number of requests
|
||||
*/
|
||||
get followLinks(): FollowLinkConfig<Community>[] {
|
||||
return COMMUNITY_PAGE_LINKS_TO_FOLLOW;
|
||||
}
|
||||
}
|
||||
export const communityBreadcrumbResolver: ResolveFn<BreadcrumbConfig<Community>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
breadcrumbService: DSOBreadcrumbsService = inject(DSOBreadcrumbsService),
|
||||
dataService: CommunityDataService = inject(CommunityDataService),
|
||||
): Observable<BreadcrumbConfig<Community>> => {
|
||||
const linksToFollow: FollowLinkConfig<DSpaceObject>[] = COMMUNITY_PAGE_LINKS_TO_FOLLOW as FollowLinkConfig<DSpaceObject>[];
|
||||
return DSOBreadcrumbResolver(
|
||||
route,
|
||||
state,
|
||||
breadcrumbService,
|
||||
dataService,
|
||||
...linksToFollow,
|
||||
) as Observable<BreadcrumbConfig<Community>>;
|
||||
};
|
||||
|
@@ -2,12 +2,11 @@ import { getTestScheduler } from 'jasmine-marbles';
|
||||
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||
import { Collection } from '../shared/collection.model';
|
||||
import { CollectionBreadcrumbResolver } from './collection-breadcrumb.resolver';
|
||||
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
|
||||
import { collectionBreadcrumbResolver } from './collection-breadcrumb.resolver';
|
||||
|
||||
describe('DSOBreadcrumbResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: DSOBreadcrumbResolver<Collection>;
|
||||
let resolver: any;
|
||||
let collectionService: any;
|
||||
let dsoBreadcrumbService: any;
|
||||
let testCollection: Collection;
|
||||
@@ -17,18 +16,18 @@ describe('DSOBreadcrumbResolver', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
uuid = '1234-65487-12354-1235';
|
||||
breadcrumbUrl = '/collections/' + uuid;
|
||||
currentUrl = breadcrumbUrl + '/edit';
|
||||
breadcrumbUrl = `/collections/${uuid}`;
|
||||
currentUrl = `${breadcrumbUrl}/edit`;
|
||||
testCollection = Object.assign(new Collection(), { uuid });
|
||||
dsoBreadcrumbService = {};
|
||||
collectionService = {
|
||||
findById: (id: string) => createSuccessfulRemoteDataObject$(testCollection),
|
||||
findById: () => createSuccessfulRemoteDataObject$(testCollection),
|
||||
};
|
||||
resolver = new CollectionBreadcrumbResolver(dsoBreadcrumbService, collectionService);
|
||||
resolver = collectionBreadcrumbResolver;
|
||||
});
|
||||
|
||||
it('should resolve a breadcrumb config for the correct DSO', () => {
|
||||
const resolvedConfig = resolver.resolve({ params: { id: uuid } } as any, { url: currentUrl } as any);
|
||||
const resolvedConfig = resolver({ params: { id: uuid } } as any, { url: currentUrl } as any, dsoBreadcrumbService, collectionService);
|
||||
const expectedConfig = { provider: dsoBreadcrumbService, key: testCollection, url: breadcrumbUrl };
|
||||
getTestScheduler().expectObservable(resolvedConfig).toBe('(a|)', { a: expectedConfig });
|
||||
});
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
RouterStateSnapshot,
|
||||
@@ -10,7 +9,6 @@ import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { IdentifiableDataService } from '../data/base/identifiable-data.service';
|
||||
import { ChildHALResource } from '../shared/child-hal-resource.model';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import {
|
||||
getFirstCompletedRemoteData,
|
||||
@@ -19,45 +17,33 @@ import {
|
||||
import { DSOBreadcrumbsService } from './dso-breadcrumbs.service';
|
||||
|
||||
/**
|
||||
* The class that resolves the BreadcrumbConfig object for a DSpaceObject
|
||||
* Method for resolving a breadcrumb config object
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {DSOBreadcrumbsService} breadcrumbService
|
||||
* @param {IdentifiableDataService} dataService
|
||||
* @param linksToFollow
|
||||
* @returns BreadcrumbConfig object
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export abstract class DSOBreadcrumbResolver<T extends ChildHALResource & DSpaceObject> {
|
||||
protected constructor(
|
||||
protected breadcrumbService: DSOBreadcrumbsService,
|
||||
protected dataService: IdentifiableDataService<T>,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving a breadcrumb config object
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns BreadcrumbConfig object
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<BreadcrumbConfig<T>> {
|
||||
const uuid = route.params.id;
|
||||
return this.dataService.findById(uuid, true, false, ...this.followLinks).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
map((object: T) => {
|
||||
if (hasValue(object)) {
|
||||
const fullPath = state.url;
|
||||
const url = fullPath.substr(0, fullPath.indexOf(uuid)) + uuid;
|
||||
return { provider: this.breadcrumbService, key: object, url: url };
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that returns the follow links to already resolve
|
||||
* The self links defined in this list are expected to be requested somewhere in the near future
|
||||
* Requesting them as embeds will limit the number of requests
|
||||
*/
|
||||
abstract get followLinks(): FollowLinkConfig<T>[];
|
||||
}
|
||||
export const DSOBreadcrumbResolver: (route: ActivatedRouteSnapshot, state: RouterStateSnapshot, breadcrumbService: DSOBreadcrumbsService, dataService: IdentifiableDataService<DSpaceObject>, ...linksToFollow: FollowLinkConfig<DSpaceObject>[]) => Observable<BreadcrumbConfig<DSpaceObject>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
breadcrumbService: DSOBreadcrumbsService,
|
||||
dataService: IdentifiableDataService<DSpaceObject>,
|
||||
...linksToFollow: FollowLinkConfig<DSpaceObject>[]
|
||||
): Observable<BreadcrumbConfig<DSpaceObject>> => {
|
||||
const uuid = route.params.id;
|
||||
return dataService.findById(uuid, true, false, ...linksToFollow).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
map((object: DSpaceObject) => {
|
||||
if (hasValue(object)) {
|
||||
const fullPath = state.url;
|
||||
const url = (fullPath.substring(0, fullPath.indexOf(uuid))).concat(uuid);
|
||||
return { provider: breadcrumbService, key: object, url: url };
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import { URLCombiner } from '../url-combiner/url-combiner';
|
||||
import { I18nBreadcrumbResolver } from './i18n-breadcrumb.resolver';
|
||||
import { i18nBreadcrumbResolver } from './i18n-breadcrumb.resolver';
|
||||
|
||||
describe('I18nBreadcrumbResolver', () => {
|
||||
describe('i18nBreadcrumbResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: I18nBreadcrumbResolver;
|
||||
let resolver: any;
|
||||
let i18nBreadcrumbService: any;
|
||||
let i18nKey: string;
|
||||
let route: any;
|
||||
@@ -27,18 +27,18 @@ describe('I18nBreadcrumbResolver', () => {
|
||||
};
|
||||
expectedPath = new URLCombiner(parentSegment, segment).toString();
|
||||
i18nBreadcrumbService = {};
|
||||
resolver = new I18nBreadcrumbResolver(i18nBreadcrumbService);
|
||||
resolver = i18nBreadcrumbResolver;
|
||||
});
|
||||
|
||||
it('should resolve the breadcrumb config', () => {
|
||||
const resolvedConfig = resolver.resolve(route, {} as any);
|
||||
const resolvedConfig = resolver(route, {} as any, i18nBreadcrumbService);
|
||||
const expectedConfig = { provider: i18nBreadcrumbService, key: i18nKey, url: expectedPath };
|
||||
expect(resolvedConfig).toEqual(expectedConfig);
|
||||
});
|
||||
|
||||
it('should resolve throw an error when no breadcrumbKey is defined', () => {
|
||||
expect(() => {
|
||||
resolver.resolve({ data: {} } as any, undefined);
|
||||
resolver({ data: {} } as any, undefined, i18nBreadcrumbService);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
|
||||
@@ -10,27 +11,21 @@ import { currentPathFromSnapshot } from '../../shared/utils/route.utils';
|
||||
import { I18nBreadcrumbsService } from './i18n-breadcrumbs.service';
|
||||
|
||||
/**
|
||||
* The class that resolves a BreadcrumbConfig object with an i18n key string for a route
|
||||
* Method for resolving an I18n breadcrumb configuration object
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {I18nBreadcrumbsService} breadcrumbService
|
||||
* @returns BreadcrumbConfig object
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class I18nBreadcrumbResolver {
|
||||
constructor(protected breadcrumbService: I18nBreadcrumbsService) {
|
||||
export const i18nBreadcrumbResolver: ResolveFn<BreadcrumbConfig<string>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
breadcrumbService: I18nBreadcrumbsService = inject(I18nBreadcrumbsService),
|
||||
): BreadcrumbConfig<string> => {
|
||||
const key = route.data.breadcrumbKey;
|
||||
if (hasNoValue(key)) {
|
||||
throw new Error('You provided an i18nBreadcrumbResolver for url \"' + route.url + '\" but no breadcrumbKey in the route\'s data');
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving an I18n breadcrumb configuration object
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns BreadcrumbConfig object
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): BreadcrumbConfig<string> {
|
||||
const key = route.data.breadcrumbKey;
|
||||
if (hasNoValue(key)) {
|
||||
throw new Error('You provided an i18nBreadcrumbResolver for url \"' + route.url + '\" but no breadcrumbKey in the route\'s data');
|
||||
}
|
||||
const fullPath = currentPathFromSnapshot(route);
|
||||
return { provider: this.breadcrumbService, key: key, url: fullPath };
|
||||
}
|
||||
}
|
||||
const fullPath = currentPathFromSnapshot(route);
|
||||
return { provider: breadcrumbService, key: key, url: fullPath };
|
||||
};
|
||||
|
@@ -1,29 +1,35 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
||||
import { ITEM_PAGE_LINKS_TO_FOLLOW } from '../../item-page/item.resolver';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { ItemDataService } from '../data/item-data.service';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import { Item } from '../shared/item.model';
|
||||
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
|
||||
import { DSOBreadcrumbsService } from './dso-breadcrumbs.service';
|
||||
|
||||
/**
|
||||
* The class that resolves the BreadcrumbConfig object for an Item
|
||||
* The resolve function that resolves the BreadcrumbConfig object for an Item
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ItemBreadcrumbResolver extends DSOBreadcrumbResolver<Item> {
|
||||
constructor(protected breadcrumbService: DSOBreadcrumbsService, protected dataService: ItemDataService) {
|
||||
super(breadcrumbService, dataService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that returns the follow links to already resolve
|
||||
* The self links defined in this list are expected to be requested somewhere in the near future
|
||||
* Requesting them as embeds will limit the number of requests
|
||||
*/
|
||||
get followLinks(): FollowLinkConfig<Item>[] {
|
||||
return ITEM_PAGE_LINKS_TO_FOLLOW;
|
||||
}
|
||||
}
|
||||
export const itemBreadcrumbResolver: ResolveFn<BreadcrumbConfig<Item>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
breadcrumbService: DSOBreadcrumbsService = inject(DSOBreadcrumbsService),
|
||||
dataService: ItemDataService = inject(ItemDataService),
|
||||
): Observable<BreadcrumbConfig<Item>> => {
|
||||
const linksToFollow: FollowLinkConfig<DSpaceObject>[] = ITEM_PAGE_LINKS_TO_FOLLOW as FollowLinkConfig<DSpaceObject>[];
|
||||
return DSOBreadcrumbResolver(
|
||||
route,
|
||||
state,
|
||||
breadcrumbService,
|
||||
dataService,
|
||||
...linksToFollow,
|
||||
) as Observable<BreadcrumbConfig<Item>>;
|
||||
};
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { NavigationBreadcrumbResolver } from './navigation-breadcrumb.resolver';
|
||||
import { navigationBreadcrumbResolver } from './navigation-breadcrumb.resolver';
|
||||
|
||||
describe('NavigationBreadcrumbResolver', () => {
|
||||
describe('navigationBreadcrumbResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: NavigationBreadcrumbResolver;
|
||||
let resolver: any;
|
||||
let NavigationBreadcrumbService: any;
|
||||
let i18nKey: string;
|
||||
let relatedI18nKey: string;
|
||||
@@ -40,11 +40,11 @@ describe('NavigationBreadcrumbResolver', () => {
|
||||
};
|
||||
expectedPath = '/base/example:/base';
|
||||
NavigationBreadcrumbService = {};
|
||||
resolver = new NavigationBreadcrumbResolver(NavigationBreadcrumbService);
|
||||
resolver = navigationBreadcrumbResolver;
|
||||
});
|
||||
|
||||
it('should resolve the breadcrumb config', () => {
|
||||
const resolvedConfig = resolver.resolve(route, state);
|
||||
const resolvedConfig = resolver(route, state, NavigationBreadcrumbService);
|
||||
const expectedConfig = { provider: NavigationBreadcrumbService, key: `${i18nKey}:${relatedI18nKey}`, url: expectedPath };
|
||||
expect(resolvedConfig).toEqual(expectedConfig);
|
||||
});
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
|
||||
@@ -8,49 +9,44 @@ import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config
|
||||
import { NavigationBreadcrumbsService } from './navigation-breadcrumb.service';
|
||||
|
||||
/**
|
||||
* The class that resolves a BreadcrumbConfig object with an i18n key string for a route and related parents
|
||||
* Method for resolving an I18n breadcrumb configuration object
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {NavigationBreadcrumbsService} breadcrumbService
|
||||
* @returns BreadcrumbConfig object
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class NavigationBreadcrumbResolver {
|
||||
|
||||
private parentRoutes: ActivatedRouteSnapshot[] = [];
|
||||
constructor(protected breadcrumbService: NavigationBreadcrumbsService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to collect all parent routes snapshot from current route snapshot
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
*/
|
||||
private getParentRoutes(route: ActivatedRouteSnapshot): void {
|
||||
if (route.parent) {
|
||||
this.parentRoutes.push(route.parent);
|
||||
this.getParentRoutes(route.parent);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Method for resolving an I18n breadcrumb configuration object
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns BreadcrumbConfig object
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): BreadcrumbConfig<string> {
|
||||
this.getParentRoutes(route);
|
||||
const relatedRoutes = route.data.relatedRoutes;
|
||||
const parentPaths = this.parentRoutes.map(parent => parent.routeConfig?.path);
|
||||
const relatedParentRoutes = relatedRoutes.filter(relatedRoute => parentPaths.includes(relatedRoute.path));
|
||||
const baseUrlSegmentPath = route.parent.url[route.parent.url.length - 1].path;
|
||||
const baseUrl = state.url.substring(0, state.url.lastIndexOf(baseUrlSegmentPath) + baseUrlSegmentPath.length);
|
||||
export const navigationBreadcrumbResolver: ResolveFn<BreadcrumbConfig<string>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
breadcrumbService: NavigationBreadcrumbsService = inject(NavigationBreadcrumbsService),
|
||||
): BreadcrumbConfig<string> => {
|
||||
const parentRoutes: ActivatedRouteSnapshot[] = [];
|
||||
getParentRoutes(route, parentRoutes);
|
||||
const relatedRoutes = route.data.relatedRoutes;
|
||||
const parentPaths = parentRoutes.map(parent => parent.routeConfig?.path);
|
||||
const relatedParentRoutes = relatedRoutes.filter(relatedRoute => parentPaths.includes(relatedRoute.path));
|
||||
const baseUrlSegmentPath = route.parent.url[route.parent.url.length - 1].path;
|
||||
const baseUrl = state.url.substring(0, state.url.lastIndexOf(baseUrlSegmentPath) + baseUrlSegmentPath.length);
|
||||
|
||||
|
||||
const combinedParentBreadcrumbKeys = relatedParentRoutes.reduce((previous, current) => {
|
||||
return `${previous}:${current.data.breadcrumbKey}`;
|
||||
}, route.data.breadcrumbKey);
|
||||
const combinedUrls = relatedParentRoutes.reduce((previous, current) => {
|
||||
return `${previous}:${baseUrl}${current.path}`;
|
||||
}, state.url);
|
||||
const combinedParentBreadcrumbKeys = relatedParentRoutes.reduce((previous, current) => {
|
||||
return `${previous}:${current.data.breadcrumbKey}`;
|
||||
}, route.data.breadcrumbKey);
|
||||
const combinedUrls = relatedParentRoutes.reduce((previous, current) => {
|
||||
return `${previous}:${baseUrl}${current.path}`;
|
||||
}, state.url);
|
||||
|
||||
return { provider: this.breadcrumbService, key: combinedParentBreadcrumbKeys, url: combinedUrls };
|
||||
return { provider: breadcrumbService, key: combinedParentBreadcrumbKeys, url: combinedUrls };
|
||||
};
|
||||
|
||||
/**
|
||||
* Method to collect all parent routes snapshot from current route snapshot
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {ActivatedRouteSnapshot[]} parentRoutes
|
||||
*/
|
||||
function getParentRoutes(route: ActivatedRouteSnapshot, parentRoutes: ActivatedRouteSnapshot[]): void {
|
||||
if (route.parent) {
|
||||
parentRoutes.push(route.parent);
|
||||
getParentRoutes(route.parent, parentRoutes);
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { PublicationClaimBreadcrumbResolver } from './publication-claim-breadcrumb.resolver';
|
||||
import { publicationClaimBreadcrumbResolver } from './publication-claim-breadcrumb.resolver';
|
||||
|
||||
describe('PublicationClaimBreadcrumbResolver', () => {
|
||||
describe('publicationClaimBreadcrumbResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: PublicationClaimBreadcrumbResolver;
|
||||
let resolver: any;
|
||||
let publicationClaimBreadcrumbService: any;
|
||||
const fullPath = '/test/publication-claim/openaire:6bee076d-4f2a-4555-a475-04a267769b2a';
|
||||
const expectedKey = '6bee076d-4f2a-4555-a475-04a267769b2a';
|
||||
@@ -19,11 +19,11 @@ describe('PublicationClaimBreadcrumbResolver', () => {
|
||||
},
|
||||
};
|
||||
publicationClaimBreadcrumbService = {};
|
||||
resolver = new PublicationClaimBreadcrumbResolver(publicationClaimBreadcrumbService);
|
||||
resolver = publicationClaimBreadcrumbResolver;
|
||||
});
|
||||
|
||||
it('should resolve the breadcrumb config', () => {
|
||||
const resolvedConfig = resolver.resolve(route as any, { url: fullPath } as any);
|
||||
const resolvedConfig = resolver(route as any, { url: fullPath } as any, publicationClaimBreadcrumbService);
|
||||
const expectedConfig = { provider: publicationClaimBreadcrumbService, key: expectedKey };
|
||||
expect(resolvedConfig).toEqual(expectedConfig);
|
||||
});
|
||||
|
@@ -1,28 +1,18 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
|
||||
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
||||
import { PublicationClaimBreadcrumbService } from './publication-claim-breadcrumb.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class PublicationClaimBreadcrumbResolver {
|
||||
constructor(protected breadcrumbService: PublicationClaimBreadcrumbService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that resolve Publication Claim item into a breadcrumb
|
||||
* The parameter are retrieved by the url since part of the Publication Claim route config
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns BreadcrumbConfig object
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): BreadcrumbConfig<string> {
|
||||
const targetId = route.paramMap.get('targetId').split(':')[1];
|
||||
return { provider: this.breadcrumbService, key: targetId };
|
||||
}
|
||||
}
|
||||
export const publicationClaimBreadcrumbResolver: ResolveFn<BreadcrumbConfig<string>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
breadcrumbService: PublicationClaimBreadcrumbService = inject(PublicationClaimBreadcrumbService),
|
||||
): BreadcrumbConfig<string> => {
|
||||
const targetId = route.paramMap.get('targetId').split(':')[1];
|
||||
return { provider: breadcrumbService, key: targetId };
|
||||
};
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { QualityAssuranceBreadcrumbResolver } from './quality-assurance-breadcrumb.resolver';
|
||||
import { qualityAssuranceBreadcrumbResolver } from './quality-assurance-breadcrumb.resolver';
|
||||
|
||||
describe('QualityAssuranceBreadcrumbResolver', () => {
|
||||
describe('qualityAssuranceBreadcrumbResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: QualityAssuranceBreadcrumbResolver;
|
||||
let resolver: any;
|
||||
let qualityAssuranceBreadcrumbService: any;
|
||||
let route: any;
|
||||
const fullPath = '/test/quality-assurance/';
|
||||
@@ -19,11 +19,11 @@ describe('QualityAssuranceBreadcrumbResolver', () => {
|
||||
},
|
||||
};
|
||||
qualityAssuranceBreadcrumbService = {};
|
||||
resolver = new QualityAssuranceBreadcrumbResolver(qualityAssuranceBreadcrumbService);
|
||||
resolver = qualityAssuranceBreadcrumbResolver;
|
||||
});
|
||||
|
||||
it('should resolve the breadcrumb config', () => {
|
||||
const resolvedConfig = resolver.resolve(route as any, { url: fullPath + 'testSourceId' } as any);
|
||||
const resolvedConfig = resolver(route as any, { url: fullPath + 'testSourceId' } as any, qualityAssuranceBreadcrumbService);
|
||||
const expectedConfig = { provider: qualityAssuranceBreadcrumbService, key: expectedKey, url: fullPath };
|
||||
expect(resolvedConfig).toEqual(expectedConfig);
|
||||
});
|
||||
|
@@ -1,36 +1,27 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
|
||||
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
||||
import { QualityAssuranceBreadcrumbService } from './quality-assurance-breadcrumb.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class QualityAssuranceBreadcrumbResolver {
|
||||
constructor(protected breadcrumbService: QualityAssuranceBreadcrumbService) {}
|
||||
export const qualityAssuranceBreadcrumbResolver: ResolveFn<BreadcrumbConfig<string>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
breadcrumbService: QualityAssuranceBreadcrumbService = inject(QualityAssuranceBreadcrumbService),
|
||||
): BreadcrumbConfig<string> => {
|
||||
const sourceId = route.paramMap.get('sourceId');
|
||||
const topicId = route.paramMap.get('topicId');
|
||||
let key = sourceId;
|
||||
|
||||
/**
|
||||
* Method that resolve QA item into a breadcrumb
|
||||
* The parameter are retrieved by the url since part of the QA route config
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns BreadcrumbConfig object
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): BreadcrumbConfig<string> {
|
||||
const sourceId = route.paramMap.get('sourceId');
|
||||
const topicId = route.paramMap.get('topicId');
|
||||
let key = sourceId;
|
||||
|
||||
if (topicId) {
|
||||
key += `:${topicId}`;
|
||||
}
|
||||
const fullPath = state.url;
|
||||
const url = fullPath.substr(0, fullPath.indexOf(sourceId));
|
||||
|
||||
return { provider: this.breadcrumbService, key, url };
|
||||
if (topicId) {
|
||||
key += `:${topicId}`;
|
||||
}
|
||||
}
|
||||
const fullPath = state.url;
|
||||
const url = fullPath.substring(0, fullPath.indexOf(sourceId));
|
||||
|
||||
return { provider: breadcrumbService, key, url };
|
||||
};
|
||||
|
@@ -1,36 +1,27 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { Router } from '@angular/router';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import { NotifyInfoGuard } from './notify-info.guard';
|
||||
import { NotifyInfoService } from './notify-info.service';
|
||||
import { notifyInfoGuard } from './notify-info.guard';
|
||||
|
||||
describe('NotifyInfoGuard', () => {
|
||||
let guard: NotifyInfoGuard;
|
||||
describe('notifyInfoGuard', () => {
|
||||
let guard: any;
|
||||
let notifyInfoServiceSpy: any;
|
||||
let router: any;
|
||||
|
||||
beforeEach(() => {
|
||||
notifyInfoServiceSpy = jasmine.createSpyObj('NotifyInfoService', ['isCoarConfigEnabled']);
|
||||
router = jasmine.createSpyObj('Router', ['parseUrl']);
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
NotifyInfoGuard,
|
||||
{ provide: NotifyInfoService, useValue: notifyInfoServiceSpy },
|
||||
{ provide: Router, useValue: router },
|
||||
],
|
||||
});
|
||||
guard = TestBed.inject(NotifyInfoGuard);
|
||||
guard = notifyInfoGuard;
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(guard).toBeTruthy();
|
||||
notifyInfoServiceSpy.isCoarConfigEnabled.and.returnValue(of(true));
|
||||
expect(guard(null, null, notifyInfoServiceSpy, router)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return true if COAR config is enabled', (done) => {
|
||||
notifyInfoServiceSpy.isCoarConfigEnabled.and.returnValue(of(true));
|
||||
|
||||
guard.canActivate(null, null).subscribe((result) => {
|
||||
guard(null, null, notifyInfoServiceSpy, router).subscribe((result) => {
|
||||
expect(result).toBe(true);
|
||||
done();
|
||||
});
|
||||
@@ -40,7 +31,7 @@ describe('NotifyInfoGuard', () => {
|
||||
notifyInfoServiceSpy.isCoarConfigEnabled.and.returnValue(of(false));
|
||||
router.parseUrl.and.returnValue(of('/404'));
|
||||
|
||||
guard.canActivate(null, null).subscribe(() => {
|
||||
guard(null, null, notifyInfoServiceSpy, router).subscribe(() => {
|
||||
expect(router.parseUrl).toHaveBeenCalledWith('/404');
|
||||
done();
|
||||
});
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
CanActivate,
|
||||
CanActivateFn,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
UrlTree,
|
||||
@@ -11,27 +11,13 @@ import { map } from 'rxjs/operators';
|
||||
|
||||
import { NotifyInfoService } from './notify-info.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class NotifyInfoGuard implements CanActivate {
|
||||
constructor(
|
||||
private notifyInfoService: NotifyInfoService,
|
||||
private router: Router,
|
||||
) {}
|
||||
|
||||
canActivate(
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
): Observable<boolean | UrlTree> {
|
||||
return this.notifyInfoService.isCoarConfigEnabled().pipe(
|
||||
map(coarLdnEnabled => {
|
||||
if (coarLdnEnabled) {
|
||||
return true;
|
||||
} else {
|
||||
return this.router.parseUrl('/404');
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
export const notifyInfoGuard: CanActivateFn = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
notifyInfoService: NotifyInfoService = inject(NotifyInfoService),
|
||||
router: Router = inject(Router),
|
||||
): Observable<boolean | UrlTree> => {
|
||||
return notifyInfoService.isCoarConfigEnabled().pipe(
|
||||
map(isEnabled => isEnabled ? true : router.parseUrl('/404')),
|
||||
);
|
||||
};
|
||||
|
@@ -28,7 +28,10 @@ import { Community } from '../shared/community.model';
|
||||
import { ContentSource } from '../shared/content-source.model';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { Item } from '../shared/item.model';
|
||||
import { getFirstCompletedRemoteData } from '../shared/operators';
|
||||
import {
|
||||
getAllCompletedRemoteData,
|
||||
getFirstCompletedRemoteData,
|
||||
} from '../shared/operators';
|
||||
import { BitstreamDataService } from './bitstream-data.service';
|
||||
import { ComColDataService } from './comcol-data.service';
|
||||
import { CommunityDataService } from './community-data.service';
|
||||
@@ -84,7 +87,8 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
||||
});
|
||||
|
||||
return this.searchBy(searchHref, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow).pipe(
|
||||
filter((collections: RemoteData<PaginatedList<Collection>>) => !collections.isResponsePending));
|
||||
getAllCompletedRemoteData(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,7 +118,8 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
||||
});
|
||||
|
||||
return this.searchBy(searchHref, options, true, reRequestOnStale, ...linksToFollow).pipe(
|
||||
filter((collections: RemoteData<PaginatedList<Collection>>) => !collections.isResponsePending));
|
||||
getAllCompletedRemoteData(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,7 +143,8 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
||||
});
|
||||
|
||||
return this.searchBy(searchHref, options, reRequestOnStale).pipe(
|
||||
filter((collections: RemoteData<PaginatedList<Collection>>) => !collections.isResponsePending));
|
||||
getAllCompletedRemoteData(),
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Get all collections the user is authorized to submit to, by community and has the metadata
|
||||
@@ -169,7 +175,8 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
||||
});
|
||||
|
||||
return this.searchBy(searchHref, options, true, reRequestOnStale, ...linksToFollow).pipe(
|
||||
filter((collections: RemoteData<PaginatedList<Collection>>) => !collections.isResponsePending));
|
||||
getAllCompletedRemoteData(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,9 +191,8 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
||||
options.elementsPerPage = 1;
|
||||
|
||||
return this.searchBy(searchHref, options).pipe(
|
||||
filter((collections: RemoteData<PaginatedList<Collection>>) => !collections.isResponsePending),
|
||||
take(1),
|
||||
map((collections: RemoteData<PaginatedList<Collection>>) => collections.payload.totalElements > 0),
|
||||
getFirstCompletedRemoteData(),
|
||||
map((collections: RemoteData<PaginatedList<Collection>>) => collections?.payload?.totalElements > 0),
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -91,7 +91,7 @@ export class DsoRedirectService {
|
||||
/**
|
||||
* Redirect to a DSpaceObject's path using the given identifier type and ID.
|
||||
* This is used to redirect paths like "/handle/[prefix]/[suffix]" to the object's path (e.g. /items/[uuid]).
|
||||
* See LookupGuard for more examples.
|
||||
* See lookupGuard for more examples.
|
||||
*
|
||||
* @param id the identifier of the object to retrieve
|
||||
* @param identifierType the type of the given identifier (defaults to UUID)
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import {
|
||||
filter,
|
||||
map,
|
||||
switchMap,
|
||||
take,
|
||||
@@ -14,6 +13,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { ItemType } from '../shared/item-relationships/item-type.model';
|
||||
import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
|
||||
import {
|
||||
getAllCompletedRemoteData,
|
||||
getFirstSucceededRemoteData,
|
||||
getRemoteDataPayload,
|
||||
} from '../shared/operators';
|
||||
@@ -89,8 +89,7 @@ export class EntityTypeDataService extends BaseDataService<ItemType> implements
|
||||
getAllAuthorizedRelationshipType(options: FindListOptions = {}): Observable<RemoteData<PaginatedList<ItemType>>> {
|
||||
const searchHref = 'findAllByAuthorizedCollection';
|
||||
|
||||
return this.searchBy(searchHref, options).pipe(
|
||||
filter((type: RemoteData<PaginatedList<ItemType>>) => !type.isResponsePending));
|
||||
return this.searchBy(searchHref, options).pipe(getAllCompletedRemoteData());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,8 +122,7 @@ export class EntityTypeDataService extends BaseDataService<ItemType> implements
|
||||
getAllAuthorizedRelationshipTypeImport(options: FindListOptions = {}): Observable<RemoteData<PaginatedList<ItemType>>> {
|
||||
const searchHref = 'findAllByAuthorizedExternalSource';
|
||||
|
||||
return this.searchBy(searchHref, options).pipe(
|
||||
filter((type: RemoteData<PaginatedList<ItemType>>) => !type.isResponsePending));
|
||||
return this.searchBy(searchHref, options).pipe(getAllCompletedRemoteData());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,15 +134,8 @@ export class EntityTypeDataService extends BaseDataService<ItemType> implements
|
||||
currentPage: 1,
|
||||
};
|
||||
return this.getAllAuthorizedRelationshipTypeImport(findListOptions).pipe(
|
||||
map((result: RemoteData<PaginatedList<ItemType>>) => {
|
||||
let output: boolean;
|
||||
if (result.payload) {
|
||||
output = ( result.payload.page.length > 1 );
|
||||
} else {
|
||||
output = false;
|
||||
}
|
||||
return output;
|
||||
}),
|
||||
take(1),
|
||||
map((result: RemoteData<PaginatedList<ItemType>>) => result?.payload?.totalElements > 1),
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -12,23 +12,30 @@ import {
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
|
||||
import { AuthService } from '../../../auth/auth.service';
|
||||
import { DSpaceObject } from '../../../shared/dspace-object.model';
|
||||
import { Item } from '../../../shared/item.model';
|
||||
import { RemoteData } from '../../remote-data';
|
||||
import { AuthorizationDataService } from '../authorization-data.service';
|
||||
import { FeatureID } from '../feature-id';
|
||||
import { DsoPageSingleFeatureGuard } from './dso-page-single-feature.guard';
|
||||
|
||||
const object = {
|
||||
self: 'test-selflink',
|
||||
} as DSpaceObject;
|
||||
|
||||
const testResolver: ResolveFn<RemoteData<any>> = () => createSuccessfulRemoteDataObject$(object);
|
||||
|
||||
/**
|
||||
* Test implementation of abstract class DsoPageSingleFeatureGuard
|
||||
*/
|
||||
class DsoPageSingleFeatureGuardImpl extends DsoPageSingleFeatureGuard<any> {
|
||||
constructor(protected resolver: {
|
||||
resolve: ResolveFn<RemoteData<any>>;
|
||||
},
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
|
||||
protected resolver: ResolveFn<RemoteData<Item>> = testResolver;
|
||||
|
||||
constructor(protected authorizationService: AuthorizationDataService,
|
||||
protected router: Router,
|
||||
protected authService: AuthService,
|
||||
protected featureID: FeatureID) {
|
||||
super(resolver, authorizationService, router, authService);
|
||||
super(authorizationService, router, authService);
|
||||
}
|
||||
|
||||
getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID> {
|
||||
@@ -41,27 +48,16 @@ describe('DsoPageSingleFeatureGuard', () => {
|
||||
let authorizationService: AuthorizationDataService;
|
||||
let router: Router;
|
||||
let authService: AuthService;
|
||||
let resolver: {
|
||||
resolve: ResolveFn<RemoteData<any>>;
|
||||
};
|
||||
let object: DSpaceObject;
|
||||
let route;
|
||||
let parentRoute;
|
||||
|
||||
function init() {
|
||||
object = {
|
||||
self: 'test-selflink',
|
||||
} as DSpaceObject;
|
||||
|
||||
authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||
isAuthorized: observableOf(true),
|
||||
});
|
||||
router = jasmine.createSpyObj('router', {
|
||||
parseUrl: {},
|
||||
});
|
||||
resolver = jasmine.createSpyObj('resolver', {
|
||||
resolve: createSuccessfulRemoteDataObject$(object),
|
||||
});
|
||||
authService = jasmine.createSpyObj('authService', {
|
||||
isAuthenticated: observableOf(true),
|
||||
});
|
||||
@@ -75,7 +71,7 @@ describe('DsoPageSingleFeatureGuard', () => {
|
||||
},
|
||||
parent: parentRoute,
|
||||
};
|
||||
guard = new DsoPageSingleFeatureGuardImpl(resolver, authorizationService, router, authService, undefined);
|
||||
guard = new DsoPageSingleFeatureGuardImpl(authorizationService, router, authService, undefined);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
|
@@ -12,23 +12,30 @@ import {
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
|
||||
import { AuthService } from '../../../auth/auth.service';
|
||||
import { DSpaceObject } from '../../../shared/dspace-object.model';
|
||||
import { Item } from '../../../shared/item.model';
|
||||
import { RemoteData } from '../../remote-data';
|
||||
import { AuthorizationDataService } from '../authorization-data.service';
|
||||
import { FeatureID } from '../feature-id';
|
||||
import { DsoPageSomeFeatureGuard } from './dso-page-some-feature.guard';
|
||||
|
||||
const object = {
|
||||
self: 'test-selflink',
|
||||
} as DSpaceObject;
|
||||
|
||||
const testResolver: ResolveFn<RemoteData<any>> = () => createSuccessfulRemoteDataObject$(object);
|
||||
|
||||
/**
|
||||
* Test implementation of abstract class DsoPageSomeFeatureGuard
|
||||
*/
|
||||
class DsoPageSomeFeatureGuardImpl extends DsoPageSomeFeatureGuard<any> {
|
||||
constructor(protected resolver: {
|
||||
resolve: ResolveFn<RemoteData<any>>;
|
||||
},
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
|
||||
protected resolver: ResolveFn<RemoteData<Item>> = testResolver;
|
||||
|
||||
constructor(protected authorizationService: AuthorizationDataService,
|
||||
protected router: Router,
|
||||
protected authService: AuthService,
|
||||
protected featureIDs: FeatureID[]) {
|
||||
super(resolver, authorizationService, router, authService);
|
||||
super(authorizationService, router, authService);
|
||||
}
|
||||
|
||||
getFeatureIDs(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID[]> {
|
||||
@@ -41,27 +48,17 @@ describe('DsoPageSomeFeatureGuard', () => {
|
||||
let authorizationService: AuthorizationDataService;
|
||||
let router: Router;
|
||||
let authService: AuthService;
|
||||
let resolver: {
|
||||
resolve: ResolveFn<RemoteData<any>>;
|
||||
};
|
||||
let object: DSpaceObject;
|
||||
|
||||
let route;
|
||||
let parentRoute;
|
||||
|
||||
function init() {
|
||||
object = {
|
||||
self: 'test-selflink',
|
||||
} as DSpaceObject;
|
||||
|
||||
authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||
isAuthorized: observableOf(true),
|
||||
});
|
||||
router = jasmine.createSpyObj('router', {
|
||||
parseUrl: {},
|
||||
});
|
||||
resolver = jasmine.createSpyObj('resolver', {
|
||||
resolve: createSuccessfulRemoteDataObject$(object),
|
||||
});
|
||||
authService = jasmine.createSpyObj('authService', {
|
||||
isAuthenticated: observableOf(true),
|
||||
});
|
||||
@@ -75,7 +72,7 @@ describe('DsoPageSomeFeatureGuard', () => {
|
||||
},
|
||||
parent: parentRoute,
|
||||
};
|
||||
guard = new DsoPageSomeFeatureGuardImpl(resolver, authorizationService, router, authService, []);
|
||||
guard = new DsoPageSomeFeatureGuardImpl(authorizationService, router, authService, []);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
|
@@ -23,10 +23,10 @@ import { SomeFeatureAuthorizationGuard } from './some-feature-authorization.guar
|
||||
* This guard utilizes a resolver to retrieve the relevant object to check authorizations for
|
||||
*/
|
||||
export abstract class DsoPageSomeFeatureGuard<T extends DSpaceObject> extends SomeFeatureAuthorizationGuard {
|
||||
constructor(protected resolver: {
|
||||
resolve: ResolveFn<RemoteData<T>>;
|
||||
},
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
|
||||
protected abstract resolver: ResolveFn<RemoteData<DSpaceObject>>;
|
||||
|
||||
constructor(protected authorizationService: AuthorizationDataService,
|
||||
protected router: Router,
|
||||
protected authService: AuthService) {
|
||||
super(authorizationService, router, authService);
|
||||
@@ -37,14 +37,14 @@ export abstract class DsoPageSomeFeatureGuard<T extends DSpaceObject> extends So
|
||||
*/
|
||||
getObjectUrl(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<string> {
|
||||
const routeWithObjectID = this.getRouteWithDSOId(route);
|
||||
return (this.resolver.resolve(routeWithObjectID, state) as Observable<RemoteData<T>>).pipe(
|
||||
return (this.resolver(routeWithObjectID, state) as Observable<RemoteData<T>>).pipe(
|
||||
getAllSucceededRemoteDataPayload(),
|
||||
map((dso) => dso.self),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to resolve resolve (parent) route that contains the UUID of the DSO
|
||||
* Method to resolve (parent) route that contains the UUID of the DSO
|
||||
* @param route The current route
|
||||
*/
|
||||
protected getRouteWithDSOId(route: ActivatedRouteSnapshot): ActivatedRouteSnapshot {
|
||||
|
@@ -17,6 +17,7 @@ import {
|
||||
getTestScheduler,
|
||||
} from 'jasmine-marbles';
|
||||
import {
|
||||
BehaviorSubject,
|
||||
EMPTY,
|
||||
Observable,
|
||||
of as observableOf,
|
||||
@@ -33,6 +34,7 @@ import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { coreReducers } from '../core.reducers';
|
||||
import { CoreState } from '../core-state.model';
|
||||
import { UUIDService } from '../shared/uuid.service';
|
||||
import { XSRFService } from '../xsrf/xsrf.service';
|
||||
import {
|
||||
RequestConfigureAction,
|
||||
RequestExecuteAction,
|
||||
@@ -60,6 +62,7 @@ describe('RequestService', () => {
|
||||
let uuidService: UUIDService;
|
||||
let store: Store<CoreState>;
|
||||
let mockStore: MockStore<CoreState>;
|
||||
let xsrfService: XSRFService;
|
||||
|
||||
const testUUID = '5f2a0d2a-effa-4d54-bd54-5663b960f9eb';
|
||||
const testHref = 'https://rest.api/endpoint/selfLink';
|
||||
@@ -105,10 +108,15 @@ describe('RequestService', () => {
|
||||
store = TestBed.inject(Store);
|
||||
mockStore = store as MockStore<CoreState>;
|
||||
mockStore.setState(initialState);
|
||||
xsrfService = {
|
||||
tokenInitialized$: new BehaviorSubject(false),
|
||||
} as XSRFService;
|
||||
|
||||
service = new RequestService(
|
||||
objectCache,
|
||||
uuidService,
|
||||
store,
|
||||
xsrfService,
|
||||
undefined,
|
||||
);
|
||||
serviceAsAny = service as any;
|
||||
|
@@ -43,6 +43,7 @@ import {
|
||||
requestIndexSelector,
|
||||
} from '../index/index.selectors';
|
||||
import { UUIDService } from '../shared/uuid.service';
|
||||
import { XSRFService } from '../xsrf/xsrf.service';
|
||||
import {
|
||||
RequestConfigureAction,
|
||||
RequestExecuteAction,
|
||||
@@ -169,6 +170,7 @@ export class RequestService {
|
||||
constructor(private objectCache: ObjectCacheService,
|
||||
private uuidService: UUIDService,
|
||||
private store: Store<CoreState>,
|
||||
protected xsrfService: XSRFService,
|
||||
private indexStore: Store<MetaIndexState>) {
|
||||
}
|
||||
|
||||
@@ -453,7 +455,17 @@ export class RequestService {
|
||||
private dispatchRequest(request: RestRequest) {
|
||||
asapScheduler.schedule(() => {
|
||||
this.store.dispatch(new RequestConfigureAction(request));
|
||||
this.store.dispatch(new RequestExecuteAction(request.uuid));
|
||||
// If it's a GET request, or we have an XSRF token, dispatch it immediately
|
||||
if (request.method === RestRequestMethod.GET || this.xsrfService.tokenInitialized$.getValue() === true) {
|
||||
this.store.dispatch(new RequestExecuteAction(request.uuid));
|
||||
} else {
|
||||
// Otherwise wait for the XSRF token first
|
||||
this.xsrfService.tokenInitialized$.pipe(
|
||||
find((hasInitialized: boolean) => hasInitialized === true),
|
||||
).subscribe(() => {
|
||||
this.store.dispatch(new RequestExecuteAction(request.uuid));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
CanActivateFn,
|
||||
RouterStateSnapshot,
|
||||
UrlTree,
|
||||
} from '@angular/router';
|
||||
@@ -10,16 +11,13 @@ import { AuthorizationDataService } from '../data/feature-authorization/authoriz
|
||||
import { FeatureID } from '../data/feature-authorization/feature-id';
|
||||
|
||||
/**
|
||||
* An guard for redirecting users to the feedback page if user is authorized
|
||||
* A guard for redirecting users to the feedback page if user is authorized
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class FeedbackGuard {
|
||||
export const feedbackGuard: CanActivateFn = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
authorizationService: AuthorizationDataService = inject(AuthorizationDataService),
|
||||
): Observable<boolean | UrlTree> => {
|
||||
return authorizationService.isAuthorized(FeatureID.CanSendFeedback);
|
||||
};
|
||||
|
||||
constructor(private authorizationService: AuthorizationDataService) {
|
||||
}
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> {
|
||||
return this.authorizationService.isAuthorized(FeatureID.CanSendFeedback);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,17 +2,17 @@ import { Router } from '@angular/router';
|
||||
|
||||
import { AppConfig } from '../../../config/app-config.interface';
|
||||
import { DefaultAppConfig } from '../../../config/default-app-config';
|
||||
import { ReloadGuard } from './reload.guard';
|
||||
import { reloadGuard } from './reload.guard';
|
||||
|
||||
describe('ReloadGuard', () => {
|
||||
let guard: ReloadGuard;
|
||||
describe('reloadGuard', () => {
|
||||
let guard: any;
|
||||
let router: Router;
|
||||
let appConfig: AppConfig;
|
||||
|
||||
beforeEach(() => {
|
||||
router = jasmine.createSpyObj('router', ['parseUrl', 'createUrlTree']);
|
||||
appConfig = new DefaultAppConfig();
|
||||
guard = new ReloadGuard(router, appConfig);
|
||||
guard = reloadGuard;
|
||||
});
|
||||
|
||||
describe('canActivate', () => {
|
||||
@@ -31,7 +31,7 @@ describe('ReloadGuard', () => {
|
||||
});
|
||||
|
||||
it('should create a UrlTree with the redirect URL', () => {
|
||||
guard.canActivate(route, undefined);
|
||||
guard(route, undefined, appConfig, router);
|
||||
expect(router.parseUrl).toHaveBeenCalledWith(redirectUrl.substring(1));
|
||||
});
|
||||
});
|
||||
@@ -44,7 +44,7 @@ describe('ReloadGuard', () => {
|
||||
});
|
||||
|
||||
it('should create a UrlTree to home', () => {
|
||||
guard.canActivate(route, undefined);
|
||||
guard(route, undefined, appConfig, router);
|
||||
expect(router.createUrlTree).toHaveBeenCalledWith(['home']);
|
||||
});
|
||||
});
|
||||
|
@@ -1,9 +1,7 @@
|
||||
import {
|
||||
Inject,
|
||||
Injectable,
|
||||
} from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
CanActivateFn,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
UrlTree,
|
||||
@@ -13,33 +11,25 @@ import {
|
||||
APP_CONFIG,
|
||||
AppConfig,
|
||||
} from '../../../config/app-config.interface';
|
||||
import { HOME_PAGE_PATH } from '../../app-routing-paths';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
|
||||
/**
|
||||
* A guard redirecting the user to the URL provided in the route's query params
|
||||
* When no redirect url is found, the user is redirected to the homepage
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ReloadGuard {
|
||||
constructor(
|
||||
private router: Router,
|
||||
@Inject(APP_CONFIG) private appConfig: AppConfig,
|
||||
) {
|
||||
export const reloadGuard: CanActivateFn = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
appConfig: AppConfig = inject(APP_CONFIG),
|
||||
router: Router = inject(Router),
|
||||
): UrlTree => {
|
||||
if (isNotEmpty(route.queryParams.redirect)) {
|
||||
const url = route.queryParams.redirect.startsWith(appConfig.ui.nameSpace)
|
||||
? route.queryParams.redirect.substring(appConfig.ui.nameSpace.length)
|
||||
: route.queryParams.redirect;
|
||||
return router.parseUrl(url);
|
||||
} else {
|
||||
return router.createUrlTree([HOME_PAGE_PATH]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the UrlTree of the URL to redirect to
|
||||
* @param route
|
||||
* @param state
|
||||
*/
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): UrlTree {
|
||||
if (isNotEmpty(route.queryParams.redirect)) {
|
||||
const url = route.queryParams.redirect.startsWith(this.appConfig.ui.nameSpace)
|
||||
? route.queryParams.redirect.substring(this.appConfig.ui.nameSpace.length)
|
||||
: route.queryParams.redirect;
|
||||
return this.router.parseUrl(url);
|
||||
} else {
|
||||
return this.router.createUrlTree(['home']);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -16,7 +16,7 @@ import { ServerCheckGuard } from './server-check.guard';
|
||||
import SpyObj = jasmine.SpyObj;
|
||||
|
||||
describe('ServerCheckGuard', () => {
|
||||
let guard: ServerCheckGuard;
|
||||
let guard: any;
|
||||
let router: Router;
|
||||
let eventSubject: ReplaySubject<RouterEvent>;
|
||||
let rootDataServiceStub: SpyObj<RootDataService>;
|
||||
@@ -39,7 +39,7 @@ describe('ServerCheckGuard', () => {
|
||||
navigateByUrl: jasmine.createSpy('navigateByUrl'),
|
||||
parseUrl: jasmine.createSpy('parseUrl').and.returnValue(redirectUrlTree),
|
||||
} as any;
|
||||
guard = new ServerCheckGuard(router, rootDataServiceStub);
|
||||
guard = ServerCheckGuard;
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
@@ -53,7 +53,7 @@ describe('ServerCheckGuard', () => {
|
||||
|
||||
it('should return true', () => {
|
||||
testScheduler.run(({ expectObservable }) => {
|
||||
const result$ = guard.canActivateChild({} as any, {} as any);
|
||||
const result$ = guard({} as any, {} as any, rootDataServiceStub, router);
|
||||
expectObservable(result$).toBe('(a|)', { a: true });
|
||||
});
|
||||
});
|
||||
@@ -66,14 +66,14 @@ describe('ServerCheckGuard', () => {
|
||||
|
||||
it('should return a UrlTree with the route to the 500 error page', () => {
|
||||
testScheduler.run(({ expectObservable }) => {
|
||||
const result$ = guard.canActivateChild({} as any, {} as any);
|
||||
const result$ = guard({} as any, {} as any, rootDataServiceStub, router);
|
||||
expectObservable(result$).toBe('(b|)', { b: redirectUrlTree });
|
||||
});
|
||||
expect(router.parseUrl).toHaveBeenCalledWith('/500');
|
||||
});
|
||||
});
|
||||
|
||||
describe(`listenForRouteChanges`, () => {
|
||||
xdescribe(`listenForRouteChanges`, () => {
|
||||
it(`should invalidate the root cache, when the method is first called`, () => {
|
||||
testScheduler.run(() => {
|
||||
guard.listenForRouteChanges();
|
||||
|
@@ -1,14 +1,13 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
NavigationStart,
|
||||
CanActivateChildFn,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
UrlTree,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import {
|
||||
filter,
|
||||
map,
|
||||
take,
|
||||
} from 'rxjs/operators';
|
||||
@@ -16,52 +15,18 @@ import {
|
||||
import { getPageInternalServerErrorRoute } from '../../app-routing-paths';
|
||||
import { RootDataService } from '../data/root-data.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
/**
|
||||
* A guard that checks if root api endpoint is reachable.
|
||||
* If not redirect to 500 error page
|
||||
*/
|
||||
export class ServerCheckGuard {
|
||||
constructor(private router: Router, private rootDataService: RootDataService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* True when root api endpoint is reachable.
|
||||
*/
|
||||
canActivateChild(
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
): Observable<boolean | UrlTree> {
|
||||
|
||||
return this.rootDataService.checkServerAvailability().pipe(
|
||||
take(1),
|
||||
map((isAvailable: boolean) => {
|
||||
if (!isAvailable) {
|
||||
return this.router.parseUrl(getPageInternalServerErrorRoute());
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to all router events. Every time a new navigation starts, invalidate the cache
|
||||
* for the root endpoint. That way we retrieve it once per routing operation to ensure the
|
||||
* backend is not down. But if the guard is called multiple times during the same routing
|
||||
* operation, the cached version is used.
|
||||
*/
|
||||
listenForRouteChanges(): void {
|
||||
// we'll always be too late for the first NavigationStart event with the router subscribe below,
|
||||
// so this statement is for the very first route operation.
|
||||
this.rootDataService.invalidateRootCache();
|
||||
|
||||
this.router.events.pipe(
|
||||
filter(event => event instanceof NavigationStart),
|
||||
).subscribe(() => {
|
||||
this.rootDataService.invalidateRootCache();
|
||||
});
|
||||
}
|
||||
}
|
||||
export const ServerCheckGuard: CanActivateChildFn = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
rootDataService: RootDataService = inject(RootDataService),
|
||||
router: Router = inject(Router),
|
||||
): Observable<boolean | UrlTree> => {
|
||||
return rootDataService.checkServerAvailability().pipe(
|
||||
take(1),
|
||||
map((isAvailable: boolean) => isAvailable ? true : router.parseUrl(getPageInternalServerErrorRoute())),
|
||||
);
|
||||
};
|
||||
|
@@ -1,45 +1,37 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
|
||||
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
||||
import { IdentifiableDataService } from '../../data/base/identifiable-data.service';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
import { Item } from '../../shared/item.model';
|
||||
import { getFirstCompletedRemoteData } from '../../shared/operators';
|
||||
import { SubmissionObject } from '../models/submission-object.model';
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific item before the route is activated
|
||||
* Method for resolving an item based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {IdentifiableDataService<SubmissionObject> } dataService
|
||||
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class SubmissionObjectResolver<T> {
|
||||
constructor(
|
||||
protected dataService: IdentifiableDataService<any>,
|
||||
protected store: Store<any>,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving an item based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<T>> {
|
||||
const itemRD$ = this.dataService.findById(route.params.id,
|
||||
true,
|
||||
false,
|
||||
followLink('item'),
|
||||
).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
switchMap((wfiRD: RemoteData<any>) => wfiRD.payload.item as Observable<RemoteData<T>>),
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
return itemRD$;
|
||||
}
|
||||
}
|
||||
export const SubmissionObjectResolver: (route: ActivatedRouteSnapshot, state: RouterStateSnapshot, dataService: IdentifiableDataService<SubmissionObject>) => Observable<RemoteData<Item>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
dataService: IdentifiableDataService<SubmissionObject>,
|
||||
): Observable<RemoteData<Item>> => {
|
||||
return dataService.findById(route.params.id,
|
||||
true,
|
||||
false,
|
||||
followLink('item'),
|
||||
).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
switchMap((wfiRD: RemoteData<any>) => wfiRD.payload.item as Observable<RemoteData<Item>>),
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
};
|
||||
|
58
src/app/core/xsrf/browser-xsrf.service.spec.ts
Normal file
58
src/app/core/xsrf/browser-xsrf.service.spec.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import {
|
||||
HttpClientTestingModule,
|
||||
HttpTestingController,
|
||||
} from '@angular/common/http/testing';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
||||
import { BrowserXSRFService } from './browser-xsrf.service';
|
||||
|
||||
describe(`BrowserXSRFService`, () => {
|
||||
let service: BrowserXSRFService;
|
||||
let httpClient: HttpClient;
|
||||
let httpTestingController: HttpTestingController;
|
||||
|
||||
const endpointURL = new RESTURLCombiner('/security/csrf').toString();
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [ HttpClientTestingModule ],
|
||||
providers: [ BrowserXSRFService ],
|
||||
});
|
||||
httpClient = TestBed.inject(HttpClient);
|
||||
httpTestingController = TestBed.inject(HttpTestingController);
|
||||
service = TestBed.inject(BrowserXSRFService);
|
||||
});
|
||||
|
||||
describe(`initXSRFToken`, () => {
|
||||
it(`should perform a GET to the csrf endpoint`, (done: DoneFn) => {
|
||||
service.initXSRFToken(httpClient)();
|
||||
|
||||
const req = httpTestingController.expectOne({
|
||||
url: endpointURL,
|
||||
method: 'GET',
|
||||
});
|
||||
|
||||
req.flush({});
|
||||
httpTestingController.verify();
|
||||
expect().nothing();
|
||||
done();
|
||||
});
|
||||
|
||||
describe(`when the GET succeeds`, () => {
|
||||
it(`should set tokenInitialized$ to true`, (done: DoneFn) => {
|
||||
service.initXSRFToken(httpClient)();
|
||||
|
||||
const req = httpTestingController.expectOne(endpointURL);
|
||||
|
||||
req.flush({});
|
||||
httpTestingController.verify();
|
||||
|
||||
expect(service.tokenInitialized$.getValue()).toBeTrue();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
30
src/app/core/xsrf/browser-xsrf.service.ts
Normal file
30
src/app/core/xsrf/browser-xsrf.service.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
||||
import { XSRFService } from './xsrf.service';
|
||||
|
||||
/**
|
||||
* Browser (CSR) Service to obtain a new CSRF/XSRF token when needed by our RequestService
|
||||
* to perform a modify request (e.g. POST/PUT/DELETE).
|
||||
* NOTE: This is primarily necessary before the *first* modifying request, as the CSRF
|
||||
* token may not yet be initialized.
|
||||
*/
|
||||
@Injectable()
|
||||
export class BrowserXSRFService extends XSRFService {
|
||||
initXSRFToken(httpClient: HttpClient): () => Promise<any> {
|
||||
return () => new Promise<void>((resolve) => {
|
||||
// Force a new token to be created by calling the CSRF endpoint
|
||||
httpClient.get(new RESTURLCombiner('/security/csrf').toString(), undefined).pipe(
|
||||
take(1),
|
||||
).subscribe(() => {
|
||||
// Once token is returned, set tokenInitialized to true.
|
||||
this.tokenInitialized$.next(true);
|
||||
});
|
||||
|
||||
// return immediately, the rest of the app doesn't need to wait for this to finish
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
33
src/app/core/xsrf/server-xsrf.service.spec.ts
Normal file
33
src/app/core/xsrf/server-xsrf.service.spec.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
import { ServerXSRFService } from './server-xsrf.service';
|
||||
|
||||
describe(`ServerXSRFService`, () => {
|
||||
let service: ServerXSRFService;
|
||||
let httpClient: HttpClient;
|
||||
|
||||
beforeEach(() => {
|
||||
httpClient = jasmine.createSpyObj(['post', 'get', 'request']);
|
||||
service = new ServerXSRFService();
|
||||
});
|
||||
|
||||
describe(`initXSRFToken`, () => {
|
||||
it(`shouldn't perform any requests`, (done: DoneFn) => {
|
||||
service.initXSRFToken(httpClient)().then(() => {
|
||||
for (const prop in httpClient) {
|
||||
if (httpClient.hasOwnProperty(prop)) {
|
||||
expect(httpClient[prop]).not.toHaveBeenCalled();
|
||||
}
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it(`should leave tokenInitialized$ on false`, (done: DoneFn) => {
|
||||
service.initXSRFToken(httpClient)().then(() => {
|
||||
expect(service.tokenInitialized$.getValue()).toBeFalse();
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
19
src/app/core/xsrf/server-xsrf.service.ts
Normal file
19
src/app/core/xsrf/server-xsrf.service.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { XSRFService } from './xsrf.service';
|
||||
|
||||
/**
|
||||
* Server (SSR) Service to obtain a new CSRF/XSRF token. Because SSR only triggers GET
|
||||
* requests a CSRF token is never needed.
|
||||
*/
|
||||
@Injectable()
|
||||
export class ServerXSRFService extends XSRFService {
|
||||
initXSRFToken(httpClient: HttpClient): () => Promise<any> {
|
||||
return () => new Promise<void>((resolve) => {
|
||||
// return immediately, and keep tokenInitialized$ false. The server side can make only GET
|
||||
// requests, since it can never get a valid XSRF cookie
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
21
src/app/core/xsrf/xsrf.service.spec.ts
Normal file
21
src/app/core/xsrf/xsrf.service.spec.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
import { XSRFService } from './xsrf.service';
|
||||
|
||||
class XSRFServiceImpl extends XSRFService {
|
||||
initXSRFToken(httpClient: HttpClient): () => Promise<any> {
|
||||
return () => null;
|
||||
}
|
||||
}
|
||||
|
||||
describe(`XSRFService`, () => {
|
||||
let service: XSRFService;
|
||||
|
||||
beforeEach(() => {
|
||||
service = new XSRFServiceImpl();
|
||||
});
|
||||
|
||||
it(`should start with tokenInitialized$.hasValue() === false`, () => {
|
||||
expect(service.tokenInitialized$.getValue()).toBeFalse();
|
||||
});
|
||||
});
|
15
src/app/core/xsrf/xsrf.service.ts
Normal file
15
src/app/core/xsrf/xsrf.service.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
/**
|
||||
* Abstract CSRF/XSRF Service used to track whether a CSRF token has been received
|
||||
* from the DSpace REST API. Once it is received, the "tokenInitialized$" flag will
|
||||
* be set to "true".
|
||||
*/
|
||||
@Injectable()
|
||||
export abstract class XSRFService {
|
||||
public tokenInitialized$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||
|
||||
abstract initXSRFToken(httpClient: HttpClient): () => Promise<any>;
|
||||
}
|
@@ -35,6 +35,7 @@ import { Bitstream } from '../../../../../core/shared/bitstream.model';
|
||||
import { HALEndpointService } from '../../../../../core/shared/hal-endpoint.service';
|
||||
import { Item } from '../../../../../core/shared/item.model';
|
||||
import { UUIDService } from '../../../../../core/shared/uuid.service';
|
||||
import { XSRFService } from '../../../../../core/xsrf/xsrf.service';
|
||||
import { getMockThemeService } from '../../../../../shared/mocks/theme-service.mock';
|
||||
import { CollectionElementLinkType } from '../../../../../shared/object-collection/collection-element-link.type';
|
||||
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||
@@ -138,6 +139,7 @@ describe('PersonSearchResultListElementSubmissionComponent', () => {
|
||||
{ provide: Store, useValue: {} },
|
||||
{ provide: ObjectCacheService, useValue: {} },
|
||||
{ provide: UUIDService, useValue: {} },
|
||||
{ provide: XSRFService, useValue: {} },
|
||||
{ provide: RemoteDataBuildService, useValue: {} },
|
||||
{ provide: CommunityDataService, useValue: {} },
|
||||
{ provide: HALEndpointService, useValue: {} },
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Route } from '@angular/router';
|
||||
|
||||
import { RegistrationGuard } from '../register-page/registration.guard';
|
||||
import { registrationGuard } from '../register-page/registration.guard';
|
||||
import { ThemedForgotEmailComponent } from './forgot-password-email/themed-forgot-email.component';
|
||||
import { ThemedForgotPasswordFormComponent } from './forgot-password-form/themed-forgot-password-form.component';
|
||||
|
||||
@@ -13,6 +13,6 @@ export const ROUTES: Route[] = [
|
||||
{
|
||||
path: ':token',
|
||||
component: ThemedForgotPasswordFormComponent,
|
||||
canActivate: [RegistrationGuard],
|
||||
canActivate: [registrationGuard],
|
||||
},
|
||||
];
|
||||
|
@@ -1,18 +1,21 @@
|
||||
import { Route } from '@angular/router';
|
||||
import {
|
||||
mapToCanActivate,
|
||||
Route,
|
||||
} from '@angular/router';
|
||||
|
||||
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { SiteAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
|
||||
import { HealthPageComponent } from './health-page.component';
|
||||
|
||||
export const ROUTES: Route[] = [
|
||||
{
|
||||
path: '',
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
||||
data: {
|
||||
breadcrumbKey: 'health',
|
||||
title: 'health-page.title',
|
||||
},
|
||||
canActivate: [SiteAdministratorGuard],
|
||||
canActivate: mapToCanActivate([SiteAdministratorGuard]),
|
||||
component: HealthPageComponent,
|
||||
},
|
||||
];
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<div class="container">
|
||||
<div class="d-flex flex-wrap">
|
||||
<div>
|
||||
<h1 class="display-3">DSpace 7</h1>
|
||||
<h1 class="display-3">DSpace 8</h1>
|
||||
<p class="lead">DSpace is the world leading open source repository platform that enables organisations to:</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -2,7 +2,7 @@ import { Route } from '@angular/router';
|
||||
|
||||
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
||||
import { HomePageResolver } from './home-page.resolver';
|
||||
import { homePageResolver } from './home-page.resolver';
|
||||
import { ThemedHomePageComponent } from './themed-home-page.component';
|
||||
|
||||
export const ROUTES: Route[] = [
|
||||
@@ -27,7 +27,7 @@ export const ROUTES: Route[] = [
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
site: HomePageResolver,
|
||||
site: homePageResolver,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -9,21 +10,10 @@ import { take } from 'rxjs/operators';
|
||||
import { SiteDataService } from '../core/data/site-data.service';
|
||||
import { Site } from '../core/shared/site.model';
|
||||
|
||||
/**
|
||||
* The class that resolve the Site object for a route
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class HomePageResolver {
|
||||
constructor(private siteService: SiteDataService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving a site object
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns Observable<Site> Emits the found Site object, or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Site> | Promise<Site> | Site {
|
||||
return this.siteService.find().pipe(take(1));
|
||||
}
|
||||
}
|
||||
export const homePageResolver: ResolveFn<Site> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
siteService: SiteDataService = inject(SiteDataService),
|
||||
): Observable<Site> => {
|
||||
return siteService.find().pipe(take(1));
|
||||
};
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import { Route } from '@angular/router';
|
||||
|
||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { authenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { ThemedSubmissionImportExternalComponent } from '../submission/import-external/themed-submission-import-external.component';
|
||||
|
||||
export const ROUTES: Route[] = [
|
||||
{
|
||||
canActivate: [AuthenticatedGuard],
|
||||
canActivate: [authenticatedGuard],
|
||||
path: '',
|
||||
component: ThemedSubmissionImportExternalComponent,
|
||||
pathMatch: 'full',
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { environment } from '../../environments/environment';
|
||||
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { FeedbackGuard } from '../core/feedback/feedback.guard';
|
||||
import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { feedbackGuard } from '../core/feedback/feedback.guard';
|
||||
import { ThemedEndUserAgreementComponent } from './end-user-agreement/themed-end-user-agreement.component';
|
||||
import { ThemedFeedbackComponent } from './feedback/themed-feedback.component';
|
||||
import {
|
||||
@@ -15,20 +15,20 @@ export const ROUTES = [
|
||||
{
|
||||
path: FEEDBACK_PATH,
|
||||
component: ThemedFeedbackComponent,
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
||||
data: { title: 'info.feedback.title', breadcrumbKey: 'info.feedback' },
|
||||
canActivate: [FeedbackGuard],
|
||||
canActivate: [feedbackGuard],
|
||||
},
|
||||
environment.info.enableEndUserAgreement ? {
|
||||
path: END_USER_AGREEMENT_PATH,
|
||||
component: ThemedEndUserAgreementComponent,
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
||||
data: { title: 'info.end-user-agreement.title', breadcrumbKey: 'info.end-user-agreement' },
|
||||
} : undefined,
|
||||
environment.info.enablePrivacyStatement ? {
|
||||
path: PRIVACY_PATH,
|
||||
component: ThemedPrivacyComponent,
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
resolve: { breadcrumb: i18nBreadcrumbResolver },
|
||||
data: { title: 'info.privacy.title', breadcrumbKey: 'info.privacy' },
|
||||
} : undefined,
|
||||
];
|
||||
|
@@ -146,7 +146,7 @@ export class UploadBitstreamComponent implements OnInit, OnDestroy {
|
||||
|
||||
/**
|
||||
* Initialize component properties:
|
||||
* itemRD$ Fetched from the current route data (populated by BitstreamPageResolver)
|
||||
* itemRD$ Fetched from the current route data (populated by bitstreamPageResolver)
|
||||
* selectedBundleId Starts off by checking if the route's queryParams contain a "bundle" parameter. If none is found,
|
||||
* the ID of the first bundle in the list is selected.
|
||||
* Calls setUploadUrl after setting the selected bundle
|
||||
|
@@ -1,11 +1,14 @@
|
||||
import { Route } from '@angular/router';
|
||||
import {
|
||||
mapToCanActivate,
|
||||
Route,
|
||||
} from '@angular/router';
|
||||
|
||||
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { i18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { ThemedDsoEditMetadataComponent } from '../../dso-shared/dso-edit-metadata/themed-dso-edit-metadata.component';
|
||||
import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component';
|
||||
import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component';
|
||||
import { ResourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver';
|
||||
import { ResourcePolicyTargetResolver } from '../../shared/resource-policies/resolvers/resource-policy-target.resolver';
|
||||
import { resourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver';
|
||||
import { resourcePolicyTargetResolver } from '../../shared/resource-policies/resolvers/resource-policy-target.resolver';
|
||||
import { EditItemPageComponent } from './edit-item-page.component';
|
||||
import {
|
||||
ITEM_EDIT_AUTHORIZATIONS_PATH,
|
||||
@@ -52,7 +55,7 @@ export const ROUTES: Route[] = [
|
||||
{
|
||||
path: '',
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
breadcrumb: i18nBreadcrumbResolver,
|
||||
},
|
||||
data: { breadcrumbKey: 'item.edit' },
|
||||
children: [
|
||||
@@ -69,31 +72,31 @@ export const ROUTES: Route[] = [
|
||||
path: 'status',
|
||||
component: ThemedItemStatusComponent,
|
||||
data: { title: 'item.edit.tabs.status.title', showBreadcrumbs: true },
|
||||
canActivate: [ItemPageStatusGuard],
|
||||
canActivate: mapToCanActivate([ItemPageStatusGuard]),
|
||||
},
|
||||
{
|
||||
path: 'bitstreams',
|
||||
component: ItemBitstreamsComponent,
|
||||
data: { title: 'item.edit.tabs.bitstreams.title', showBreadcrumbs: true },
|
||||
canActivate: [ItemPageBitstreamsGuard],
|
||||
canActivate: mapToCanActivate([ItemPageBitstreamsGuard]),
|
||||
},
|
||||
{
|
||||
path: 'metadata',
|
||||
component: ThemedDsoEditMetadataComponent,
|
||||
data: { title: 'item.edit.tabs.metadata.title', showBreadcrumbs: true },
|
||||
canActivate: [ItemPageMetadataGuard],
|
||||
canActivate: mapToCanActivate([ItemPageMetadataGuard]),
|
||||
},
|
||||
{
|
||||
path: 'curate',
|
||||
component: ItemCurateComponent,
|
||||
data: { title: 'item.edit.tabs.curate.title', showBreadcrumbs: true },
|
||||
canActivate: [ItemPageCurateGuard],
|
||||
canActivate: mapToCanActivate([ItemPageCurateGuard]),
|
||||
},
|
||||
{
|
||||
path: 'relationships',
|
||||
component: ItemRelationshipsComponent,
|
||||
data: { title: 'item.edit.tabs.relationships.title', showBreadcrumbs: true },
|
||||
canActivate: [ItemPageRelationshipsGuard],
|
||||
canActivate: mapToCanActivate([ItemPageRelationshipsGuard]),
|
||||
},
|
||||
/* TODO - uncomment & fix when view page exists
|
||||
{
|
||||
@@ -111,19 +114,19 @@ export const ROUTES: Route[] = [
|
||||
path: 'versionhistory',
|
||||
component: ItemVersionHistoryComponent,
|
||||
data: { title: 'item.edit.tabs.versionhistory.title', showBreadcrumbs: true },
|
||||
canActivate: [ItemPageVersionHistoryGuard],
|
||||
canActivate: mapToCanActivate([ItemPageVersionHistoryGuard]),
|
||||
},
|
||||
{
|
||||
path: 'access-control',
|
||||
component: ItemAccessControlComponent,
|
||||
data: { title: 'item.edit.tabs.access-control.title', showBreadcrumbs: true },
|
||||
canActivate: [ItemPageAccessControlGuard],
|
||||
canActivate: mapToCanActivate([ItemPageAccessControlGuard]),
|
||||
},
|
||||
{
|
||||
path: 'mapper',
|
||||
component: ItemCollectionMapperComponent,
|
||||
data: { title: 'item.edit.tabs.item-mapper.title', showBreadcrumbs: true },
|
||||
canActivate: [ItemPageCollectionMapperGuard],
|
||||
canActivate: mapToCanActivate([ItemPageCollectionMapperGuard]),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -134,12 +137,12 @@ export const ROUTES: Route[] = [
|
||||
{
|
||||
path: ITEM_EDIT_WITHDRAW_PATH,
|
||||
component: ItemWithdrawComponent,
|
||||
canActivate: [ItemPageWithdrawGuard],
|
||||
canActivate: mapToCanActivate([ItemPageWithdrawGuard]),
|
||||
},
|
||||
{
|
||||
path: ITEM_EDIT_REINSTATE_PATH,
|
||||
component: ItemReinstateComponent,
|
||||
canActivate: [ItemPageReinstateGuard],
|
||||
canActivate: mapToCanActivate([ItemPageReinstateGuard]),
|
||||
},
|
||||
{
|
||||
path: ITEM_EDIT_PRIVATE_PATH,
|
||||
@@ -161,7 +164,7 @@ export const ROUTES: Route[] = [
|
||||
{
|
||||
path: ITEM_EDIT_REGISTER_DOI_PATH,
|
||||
component: ItemRegisterDoiComponent,
|
||||
canActivate: [ItemPageRegisterDoiGuard],
|
||||
canActivate: mapToCanActivate([ItemPageRegisterDoiGuard]),
|
||||
data: { title: 'item.edit.register-doi.title' },
|
||||
},
|
||||
{
|
||||
@@ -170,7 +173,7 @@ export const ROUTES: Route[] = [
|
||||
{
|
||||
path: 'create',
|
||||
resolve: {
|
||||
resourcePolicyTarget: ResourcePolicyTargetResolver,
|
||||
resourcePolicyTarget: resourcePolicyTargetResolver,
|
||||
},
|
||||
component: ResourcePolicyCreateComponent,
|
||||
data: { title: 'resource-policies.create.page.title' },
|
||||
@@ -178,7 +181,7 @@ export const ROUTES: Route[] = [
|
||||
{
|
||||
path: 'edit',
|
||||
resolve: {
|
||||
resourcePolicy: ResourcePolicyResolver,
|
||||
resourcePolicy: resourcePolicyResolver,
|
||||
},
|
||||
component: ResourcePolicyEditComponent,
|
||||
data: { title: 'resource-policies.edit.page.title' },
|
||||
|
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable max-classes-per-file */
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
NO_ERRORS_SCHEMA,
|
||||
@@ -13,9 +12,9 @@ import { By } from '@angular/platform-browser';
|
||||
import {
|
||||
ActivatedRoute,
|
||||
ActivatedRouteSnapshot,
|
||||
CanActivateFn,
|
||||
RouterModule,
|
||||
RouterStateSnapshot,
|
||||
UrlTree,
|
||||
} from '@angular/router';
|
||||
import {
|
||||
TranslateLoader,
|
||||
@@ -31,29 +30,31 @@ import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
|
||||
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
|
||||
import { EditItemPageComponent } from './edit-item-page.component';
|
||||
|
||||
describe('ItemPageComponent', () => {
|
||||
describe('EditItemPageComponent', () => {
|
||||
let comp: EditItemPageComponent;
|
||||
let fixture: ComponentFixture<EditItemPageComponent>;
|
||||
|
||||
class AcceptAllGuard {
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
|
||||
return observableOf(true);
|
||||
}
|
||||
}
|
||||
const AcceptAllGuard: CanActivateFn = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
): Observable<boolean> => {
|
||||
return observableOf(true);
|
||||
};
|
||||
|
||||
class AcceptNoneGuard {
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
|
||||
return observableOf(false);
|
||||
}
|
||||
}
|
||||
const AcceptNoneGuard: CanActivateFn = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
): Observable<boolean> => {
|
||||
return observableOf(false);
|
||||
};
|
||||
|
||||
const accesiblePages = ['accessible'];
|
||||
const inaccesiblePages = ['inaccessible', 'inaccessibleDoubleGuard'];
|
||||
const accessiblePages = ['accessible'];
|
||||
const inaccessiblePages = ['inaccessible', 'inaccessibleDoubleGuard'];
|
||||
const mockRoute = {
|
||||
snapshot: {
|
||||
firstChild: {
|
||||
routeConfig: {
|
||||
path: accesiblePages[0],
|
||||
path: accessiblePages[0],
|
||||
},
|
||||
},
|
||||
routerState: {
|
||||
@@ -63,13 +64,13 @@ describe('ItemPageComponent', () => {
|
||||
routeConfig: {
|
||||
children: [
|
||||
{
|
||||
path: accesiblePages[0],
|
||||
path: accessiblePages[0],
|
||||
canActivate: [AcceptAllGuard],
|
||||
}, {
|
||||
path: inaccesiblePages[0],
|
||||
path: inaccessiblePages[0],
|
||||
canActivate: [AcceptNoneGuard],
|
||||
}, {
|
||||
path: inaccesiblePages[1],
|
||||
path: inaccessiblePages[1],
|
||||
canActivate: [AcceptAllGuard, AcceptNoneGuard],
|
||||
},
|
||||
],
|
||||
@@ -77,13 +78,6 @@ describe('ItemPageComponent', () => {
|
||||
data: observableOf({ dso: createSuccessfulRemoteDataObject(new Item()) }),
|
||||
};
|
||||
|
||||
const mockRouter = {
|
||||
routerState: {
|
||||
snapshot: undefined,
|
||||
},
|
||||
events: observableOf(undefined),
|
||||
};
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
@@ -98,8 +92,6 @@ describe('ItemPageComponent', () => {
|
||||
],
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: mockRoute },
|
||||
AcceptAllGuard,
|
||||
AcceptNoneGuard,
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
}).overrideComponent(EditItemPageComponent, {
|
||||
@@ -110,19 +102,19 @@ describe('ItemPageComponent', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
fixture = TestBed.createComponent(EditItemPageComponent);
|
||||
comp = fixture.componentInstance;
|
||||
spyOn((comp as any).injector, 'get').and.callFake((a) => new a());
|
||||
// spyOn((comp as any).injector, 'get').and.callFake((a) => new a());
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
describe('ngOnInit', () => {
|
||||
it('should enable tabs that the user can activate', fakeAsync(() => {
|
||||
const enabledItems = fixture.debugElement.queryAll(By.css('a.nav-link'));
|
||||
expect(enabledItems.length).toBe(accesiblePages.length);
|
||||
expect(enabledItems.length).toBe(accessiblePages.length);
|
||||
}));
|
||||
|
||||
it('should disable tabs that the user can not activate', () => {
|
||||
const disabledItems = fixture.debugElement.queryAll(By.css('button.nav-link.disabled'));
|
||||
expect(disabledItems.length).toBe(inaccesiblePages.length);
|
||||
expect(disabledItems.length).toBe(inaccessiblePages.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -9,6 +9,7 @@ import {
|
||||
Component,
|
||||
Injector,
|
||||
OnInit,
|
||||
runInInjectionContext,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
ActivatedRoute,
|
||||
@@ -28,7 +29,6 @@ import {
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import {
|
||||
fadeIn,
|
||||
@@ -88,15 +88,10 @@ export class EditItemPageComponent implements OnInit {
|
||||
.map((child: Route) => {
|
||||
let enabled = observableOf(true);
|
||||
if (isNotEmpty(child.canActivate)) {
|
||||
enabled = observableCombineLatest(child.canActivate.map((guardConstructor: GenericConstructor<{
|
||||
canActivate: CanActivateFn;
|
||||
}>) => {
|
||||
const guard: {
|
||||
canActivate: CanActivateFn;
|
||||
} = this.injector.get<{
|
||||
canActivate: CanActivateFn;
|
||||
}>(guardConstructor);
|
||||
return guard.canActivate(this.route.snapshot, this.router.routerState.snapshot);
|
||||
enabled = observableCombineLatest(child.canActivate.map((guardFn: CanActivateFn) => {
|
||||
return runInInjectionContext(this.injector, () => {
|
||||
return guardFn(this.route.snapshot, this.router.routerState.snapshot);
|
||||
});
|
||||
}),
|
||||
).pipe(
|
||||
map((canActivateOutcomes: any[]) => canActivateOutcomes.every((e) => e === true)),
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
@@ -13,8 +14,9 @@ import { AuthService } from '../../core/auth/auth.service';
|
||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||
import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard';
|
||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { ItemPageResolver } from '../item-page.resolver';
|
||||
import { itemPageResolver } from '../item-page.resolver';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -23,11 +25,13 @@ import { ItemPageResolver } from '../item-page.resolver';
|
||||
* Guard for preventing unauthorized access to certain {@link Item} pages requiring administrator rights
|
||||
*/
|
||||
export class ItemPageAccessControlGuard extends DsoPageSingleFeatureGuard<Item> {
|
||||
constructor(protected resolver: ItemPageResolver,
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
|
||||
protected resolver: ResolveFn<RemoteData<Item>> = itemPageResolver;
|
||||
|
||||
constructor(protected authorizationService: AuthorizationDataService,
|
||||
protected router: Router,
|
||||
protected authService: AuthService) {
|
||||
super(resolver, authorizationService, router, authService);
|
||||
super(authorizationService, router, authService);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
@@ -13,8 +14,9 @@ import { AuthService } from '../../core/auth/auth.service';
|
||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||
import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard';
|
||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { ItemPageResolver } from '../item-page.resolver';
|
||||
import { itemPageResolver } from '../item-page.resolver';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -23,11 +25,13 @@ import { ItemPageResolver } from '../item-page.resolver';
|
||||
* Guard for preventing unauthorized access to certain {@link Item} pages requiring manage bitstreams rights
|
||||
*/
|
||||
export class ItemPageBitstreamsGuard extends DsoPageSingleFeatureGuard<Item> {
|
||||
constructor(protected resolver: ItemPageResolver,
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
|
||||
protected resolver: ResolveFn<RemoteData<Item>> = itemPageResolver;
|
||||
|
||||
constructor(protected authorizationService: AuthorizationDataService,
|
||||
protected router: Router,
|
||||
protected authService: AuthService) {
|
||||
super(resolver, authorizationService, router, authService);
|
||||
super( authorizationService, router, authService);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
@@ -13,8 +14,9 @@ import { AuthService } from '../../core/auth/auth.service';
|
||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||
import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard';
|
||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { ItemPageResolver } from '../item-page.resolver';
|
||||
import { itemPageResolver } from '../item-page.resolver';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -23,11 +25,13 @@ import { ItemPageResolver } from '../item-page.resolver';
|
||||
* Guard for preventing unauthorized access to certain {@link Item} pages requiring manage mappings rights
|
||||
*/
|
||||
export class ItemPageCollectionMapperGuard extends DsoPageSingleFeatureGuard<Item> {
|
||||
constructor(protected resolver: ItemPageResolver,
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
|
||||
protected resolver: ResolveFn<RemoteData<Item>> = itemPageResolver;
|
||||
|
||||
constructor(protected authorizationService: AuthorizationDataService,
|
||||
protected router: Router,
|
||||
protected authService: AuthService) {
|
||||
super(resolver, authorizationService, router, authService);
|
||||
super(authorizationService, router, authService);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
@@ -13,8 +14,9 @@ import { AuthService } from '../../core/auth/auth.service';
|
||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||
import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard';
|
||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { ItemPageResolver } from '../item-page.resolver';
|
||||
import { itemPageResolver } from '../item-page.resolver';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -23,11 +25,13 @@ import { ItemPageResolver } from '../item-page.resolver';
|
||||
* Guard for preventing unauthorized access to certain {@link Item} pages requiring administrator rights
|
||||
*/
|
||||
export class ItemPageCurateGuard extends DsoPageSingleFeatureGuard<Item> {
|
||||
constructor(protected resolver: ItemPageResolver,
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
|
||||
protected resolver: ResolveFn<RemoteData<Item>> = itemPageResolver;
|
||||
|
||||
constructor(protected authorizationService: AuthorizationDataService,
|
||||
protected router: Router,
|
||||
protected authService: AuthService) {
|
||||
super(resolver, authorizationService, router, authService);
|
||||
super(authorizationService, router, authService);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
@@ -13,8 +14,9 @@ import { AuthService } from '../../core/auth/auth.service';
|
||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||
import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard';
|
||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { ItemPageResolver } from '../item-page.resolver';
|
||||
import { itemPageResolver } from '../item-page.resolver';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -23,11 +25,13 @@ import { ItemPageResolver } from '../item-page.resolver';
|
||||
* Guard for preventing unauthorized access to certain {@link Item} pages requiring edit metadata rights
|
||||
*/
|
||||
export class ItemPageMetadataGuard extends DsoPageSingleFeatureGuard<Item> {
|
||||
constructor(protected resolver: ItemPageResolver,
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
|
||||
protected resolver: ResolveFn<RemoteData<Item>> = itemPageResolver;
|
||||
|
||||
constructor(protected authorizationService: AuthorizationDataService,
|
||||
protected router: Router,
|
||||
protected authService: AuthService) {
|
||||
super(resolver, authorizationService, router, authService);
|
||||
super(authorizationService, router, authService);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
@@ -13,8 +14,9 @@ import { AuthService } from '../../core/auth/auth.service';
|
||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||
import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard';
|
||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { ItemPageResolver } from '../item-page.resolver';
|
||||
import { itemPageResolver } from '../item-page.resolver';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -23,11 +25,13 @@ import { ItemPageResolver } from '../item-page.resolver';
|
||||
* Guard for preventing unauthorized access to certain {@link Item} pages requiring DOI registration rights
|
||||
*/
|
||||
export class ItemPageRegisterDoiGuard extends DsoPageSingleFeatureGuard<Item> {
|
||||
constructor(protected resolver: ItemPageResolver,
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
|
||||
protected resolver: ResolveFn<RemoteData<Item>> = itemPageResolver;
|
||||
|
||||
constructor(protected authorizationService: AuthorizationDataService,
|
||||
protected router: Router,
|
||||
protected authService: AuthService) {
|
||||
super(resolver, authorizationService, router, authService);
|
||||
super(authorizationService, router, authService);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
@@ -13,8 +14,9 @@ import { AuthService } from '../../core/auth/auth.service';
|
||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||
import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard';
|
||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { ItemPageResolver } from '../item-page.resolver';
|
||||
import { itemPageResolver } from '../item-page.resolver';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -23,11 +25,13 @@ import { ItemPageResolver } from '../item-page.resolver';
|
||||
* Guard for preventing unauthorized access to certain {@link Item} pages requiring reinstate rights
|
||||
*/
|
||||
export class ItemPageReinstateGuard extends DsoPageSingleFeatureGuard<Item> {
|
||||
constructor(protected resolver: ItemPageResolver,
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
|
||||
protected resolver: ResolveFn<RemoteData<Item>> = itemPageResolver;
|
||||
|
||||
constructor(protected authorizationService: AuthorizationDataService,
|
||||
protected router: Router,
|
||||
protected authService: AuthService) {
|
||||
super(resolver, authorizationService, router, authService);
|
||||
super(authorizationService, router, authService);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user