From 422a223c40718b8a89c81caeff913208fc5227bc Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Tue, 24 Oct 2023 15:14:55 +0200 Subject: [PATCH] [CST-12235] support for the LDN inbox for root & /home & /item/:uuid pages --- .../notify-info/notify-info.service.ts | 19 ++++-- src/app/home-page/home-page.component.ts | 47 +++++++++++--- .../full/full-item-page.component.spec.ts | 2 + .../full/full-item-page.component.ts | 4 +- .../simple/item-page.component.spec.ts | 9 +++ .../item-page/simple/item-page.component.ts | 61 ++++++++++++++++++- 6 files changed, 125 insertions(+), 17 deletions(-) diff --git a/src/app/core/coar-notify/notify-info/notify-info.service.ts b/src/app/core/coar-notify/notify-info/notify-info.service.ts index b489677634..4b2d572d3c 100644 --- a/src/app/core/coar-notify/notify-info/notify-info.service.ts +++ b/src/app/core/coar-notify/notify-info/notify-info.service.ts @@ -10,7 +10,10 @@ import { ConfigurationProperty } from '../../shared/configuration-property.model }) export class NotifyInfoService { - private relationLink = 'http://www.w3.org/ns/ldp#inbox'; + /** + * The relation link for the inbox + */ + private _inboxRelationLink = 'http://www.w3.org/ns/ldp#inbox'; constructor( private configService: ConfigurationDataService, @@ -27,7 +30,11 @@ export class NotifyInfoService { ); } - getCoarLdnLocalInboxUrl(): Observable { + /** + * Get the url of the local inbox from the REST configuration + * @returns the url of the local inbox + */ + getCoarLdnLocalInboxUrls(): Observable { return this.configService.findByPropertyName('ldn.notify.local-inbox-endpoint').pipe( getFirstSucceededRemoteData(), getRemoteDataPayload(), @@ -49,7 +56,11 @@ export class NotifyInfoService { return `${ssl ? 'https' : 'http'}://${host}:${port}${namespace}`; } - getRelationLink(): string{ - return this.relationLink; + /** + * Method to get the relation link for the inbox + * @returns the relation link for the inbox + */ + getInboxRelationLink(): string { + return this._inboxRelationLink; } } diff --git a/src/app/home-page/home-page.component.ts b/src/app/home-page/home-page.component.ts index 1e0833c0d1..585fd06fe6 100644 --- a/src/app/home-page/home-page.component.ts +++ b/src/app/home-page/home-page.component.ts @@ -1,23 +1,28 @@ -import { Component, Inject, OnInit, PLATFORM_ID } from '@angular/core'; +import { Component, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core'; import { map, switchMap } from 'rxjs/operators'; import { ActivatedRoute } from '@angular/router'; -import { Observable, of } from 'rxjs'; +import { Observable } from 'rxjs'; import { Site } from '../core/shared/site.model'; import { environment } from '../../environments/environment'; import { isPlatformServer } from '@angular/common'; import { ServerResponseService } from '../core/services/server-response.service'; import { NotifyInfoService } from '../core/coar-notify/notify-info/notify-info.service'; import { LinkDefinition, LinkHeadService } from '../core/services/link-head.service'; +import { isNotEmpty } from '../shared/empty.util'; @Component({ selector: 'ds-home-page', styleUrls: ['./home-page.component.scss'], templateUrl: './home-page.component.html' }) -export class HomePageComponent implements OnInit { +export class HomePageComponent implements OnInit, OnDestroy { site$: Observable; recentSubmissionspageSize: number; + /** + * An array of LinkDefinition objects representing inbox links for the home page. + */ + inboxLinks: LinkDefinition[] = []; constructor( private route: ActivatedRoute, @@ -27,14 +32,18 @@ export class HomePageComponent implements OnInit { @Inject(PLATFORM_ID) private platformId: string ) { this.recentSubmissionspageSize = environment.homePage.recentSubmissions.pageSize; + // Get COAR REST API URLs from REST configuration + // only if COAR configuration is enabled this.notifyInfoService.isCoarConfigEnabled().pipe( switchMap((coarLdnEnabled: boolean) => { if (coarLdnEnabled) { - return this.notifyInfoService.getCoarLdnLocalInboxUrl(); + return this.notifyInfoService.getCoarLdnLocalInboxUrls(); } }) ).subscribe((coarRestApiUrls: string[]) => { - this.initPageLinks(coarRestApiUrls); + if (coarRestApiUrls.length > 0) { + this.initPageLinks(coarRestApiUrls); + } }); } @@ -44,17 +53,37 @@ export class HomePageComponent implements OnInit { ); } + /** + * Initializes page links for COAR REST API URLs. + * @param coarRestApiUrls An array of COAR REST API URLs. + */ private initPageLinks(coarRestApiUrls: string[]): void { - const rel = this.notifyInfoService.getRelationLink(); + const rel = this.notifyInfoService.getInboxRelationLink(); + let links = ''; coarRestApiUrls.forEach((coarRestApiUrl: string) => { + // Add link to head let tag: LinkDefinition = { href: coarRestApiUrl, rel: rel }; + this.inboxLinks.push(tag); this.linkHeadService.addTag(tag); - if (isPlatformServer(this.platformId)) { - this.responseService.setHeader('Link', `<${coarRestApiUrl}>; rel="${rel}"`); - } + + links = links + (isNotEmpty(links) ? ', ' : '') + `<${coarRestApiUrl}> ; rel="${rel}"`; + }); + + if (isPlatformServer(this.platformId)) { + // Add link to response header + this.responseService.setHeader('Link', links); + } + } + + /** + * It removes the inbox links from the head of the html. + */ + ngOnDestroy(): void { + this.inboxLinks.forEach((link: LinkDefinition) => { + this.linkHeadService.removeTag(`href='${link.href}'`); }); } } diff --git a/src/app/item-page/full/full-item-page.component.spec.ts b/src/app/item-page/full/full-item-page.component.spec.ts index 9fc078c2cd..c1917f77f4 100644 --- a/src/app/item-page/full/full-item-page.component.spec.ts +++ b/src/app/item-page/full/full-item-page.component.spec.ts @@ -23,6 +23,7 @@ import { RemoteData } from '../../core/data/remote-data'; import { ServerResponseService } from '../../core/services/server-response.service'; import { SignpostingDataService } from '../../core/data/signposting-data.service'; import { LinkHeadService } from '../../core/services/link-head.service'; +import { NotifyInfoService } from '../../core/coar-notify/notify-info/notify-info.service'; const mockItem: Item = Object.assign(new Item(), { bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), @@ -122,6 +123,7 @@ describe('FullItemPageComponent', () => { { provide: ServerResponseService, useValue: serverResponseService }, { provide: SignpostingDataService, useValue: signpostingDataService }, { provide: LinkHeadService, useValue: linkHeadService }, + { provide: NotifyInfoService, useValue: {} }, { provide: PLATFORM_ID, useValue: 'server' } ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/item-page/full/full-item-page.component.ts b/src/app/item-page/full/full-item-page.component.ts index 31dd2c5fc2..da79fc04cc 100644 --- a/src/app/item-page/full/full-item-page.component.ts +++ b/src/app/item-page/full/full-item-page.component.ts @@ -19,6 +19,7 @@ import { AuthorizationDataService } from '../../core/data/feature-authorization/ import { ServerResponseService } from '../../core/services/server-response.service'; import { SignpostingDataService } from '../../core/data/signposting-data.service'; import { LinkHeadService } from '../../core/services/link-head.service'; +import { NotifyInfoService } from '../../core/coar-notify/notify-info/notify-info.service'; /** * This component renders a full item page. @@ -55,9 +56,10 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit, protected responseService: ServerResponseService, protected signpostingDataService: SignpostingDataService, protected linkHeadService: LinkHeadService, + protected notifyInfoService: NotifyInfoService, @Inject(PLATFORM_ID) protected platformId: string, ) { - super(route, router, items, authService, authorizationService, responseService, signpostingDataService, linkHeadService, platformId); + super(route, router, items, authService, authorizationService, responseService, signpostingDataService, linkHeadService,notifyInfoService, platformId); } /*** AoT inheritance fix, will hopefully be resolved in the near future **/ diff --git a/src/app/item-page/simple/item-page.component.spec.ts b/src/app/item-page/simple/item-page.component.spec.ts index b3202108f4..b8354496da 100644 --- a/src/app/item-page/simple/item-page.component.spec.ts +++ b/src/app/item-page/simple/item-page.component.spec.ts @@ -26,6 +26,7 @@ import { ServerResponseService } from '../../core/services/server-response.servi import { SignpostingDataService } from '../../core/data/signposting-data.service'; import { LinkDefinition, LinkHeadService } from '../../core/services/link-head.service'; import { SignpostingLink } from '../../core/data/signposting-links.model'; +import { NotifyInfoService } from '../../core/coar-notify/notify-info/notify-info.service'; const mockItem: Item = Object.assign(new Item(), { bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), @@ -62,6 +63,7 @@ describe('ItemPageComponent', () => { let serverResponseService: jasmine.SpyObj; let signpostingDataService: jasmine.SpyObj; let linkHeadService: jasmine.SpyObj; + let notifyInfoService: jasmine.SpyObj; const mockMetadataService = { /* eslint-disable no-empty,@typescript-eslint/no-empty-function */ @@ -94,6 +96,12 @@ describe('ItemPageComponent', () => { removeTag: jasmine.createSpy('removeTag'), }); + notifyInfoService = jasmine.createSpyObj('NotifyInfoService', { + getInboxRelationLink: 'http://www.w3.org/ns/ldp#inbox', + isCoarConfigEnabled: observableOf(true), + getCoarLdnLocalInboxUrls: observableOf(['http://test.org', 'http://test2.org']), + }); + TestBed.configureTestingModule({ imports: [TranslateModule.forRoot({ loader: { @@ -112,6 +120,7 @@ describe('ItemPageComponent', () => { { provide: ServerResponseService, useValue: serverResponseService }, { provide: SignpostingDataService, useValue: signpostingDataService }, { provide: LinkHeadService, useValue: linkHeadService }, + { provide: NotifyInfoService, useValue: notifyInfoService}, { provide: PLATFORM_ID, useValue: 'server' }, ], diff --git a/src/app/item-page/simple/item-page.component.ts b/src/app/item-page/simple/item-page.component.ts index b9be6bebfb..2e4e357572 100644 --- a/src/app/item-page/simple/item-page.component.ts +++ b/src/app/item-page/simple/item-page.component.ts @@ -2,8 +2,8 @@ import { ChangeDetectionStrategy, Component, Inject, OnDestroy, OnInit, PLATFORM import { ActivatedRoute, Router } from '@angular/router'; import { isPlatformServer } from '@angular/common'; -import { Observable } from 'rxjs'; -import { map, take } from 'rxjs/operators'; +import { Observable, combineLatest } from 'rxjs'; +import { map, switchMap, take } from 'rxjs/operators'; import { ItemDataService } from '../../core/data/item-data.service'; import { RemoteData } from '../../core/data/remote-data'; @@ -21,6 +21,7 @@ import { SignpostingDataService } from '../../core/data/signposting-data.service import { SignpostingLink } from '../../core/data/signposting-links.model'; import { isNotEmpty } from '../../shared/empty.util'; import { LinkDefinition, LinkHeadService } from '../../core/services/link-head.service'; +import { NotifyInfoService } from 'src/app/core/coar-notify/notify-info/notify-info.service'; /** * This component renders a simple item page. @@ -68,6 +69,13 @@ export class ItemPageComponent implements OnInit, OnDestroy { */ signpostingLinks: SignpostingLink[] = []; + /** + * An array of LinkDefinition objects representing inbox links for the item page. + */ + inboxTags: LinkDefinition[] = []; + + coarRestApiUrls: string[] = []; + constructor( protected route: ActivatedRoute, protected router: Router, @@ -77,6 +85,7 @@ export class ItemPageComponent implements OnInit, OnDestroy { protected responseService: ServerResponseService, protected signpostingDataService: SignpostingDataService, protected linkHeadService: LinkHeadService, + protected notifyInfoService: NotifyInfoService, @Inject(PLATFORM_ID) protected platformId: string ) { this.initPageLinks(); @@ -106,7 +115,8 @@ export class ItemPageComponent implements OnInit, OnDestroy { */ private initPageLinks(): void { this.route.params.subscribe(params => { - this.signpostingDataService.getLinks(params.id).pipe(take(1)).subscribe((signpostingLinks: SignpostingLink[]) => { + combineLatest([this.signpostingDataService.getLinks(params.id).pipe(take(1)), this.getCoarLdnLocalInboxUrls()]) + .subscribe(([signpostingLinks, coarRestApiUrls]) => { let links = ''; this.signpostingLinks = signpostingLinks; @@ -124,6 +134,11 @@ export class ItemPageComponent implements OnInit, OnDestroy { this.linkHeadService.addTag(tag); }); + if (coarRestApiUrls.length > 0) { + let inboxLinks = this.initPageInboxLinks(coarRestApiUrls); + links = links + (isNotEmpty(links) ? ', ' : '') + inboxLinks; + } + if (isPlatformServer(this.platformId)) { this.responseService.setHeader('Link', links); } @@ -131,9 +146,49 @@ export class ItemPageComponent implements OnInit, OnDestroy { }); } + /** + * Sets the COAR LDN local inbox URL if COAR configuration is enabled. + * If the COAR LDN local inbox URL is retrieved successfully, initializes the page inbox links. + */ + private getCoarLdnLocalInboxUrls(): Observable { + return this.notifyInfoService.isCoarConfigEnabled().pipe( + switchMap((coarLdnEnabled: boolean) => { + if (coarLdnEnabled) { + return this.notifyInfoService.getCoarLdnLocalInboxUrls(); + } + }) + ); + } + + /** + * Initializes the page inbox links. + * @param coarRestApiUrls - An array of COAR REST API URLs. + */ + private initPageInboxLinks(coarRestApiUrls: string[]): string { + const rel = this.notifyInfoService.getInboxRelationLink(); + let links = ''; + + coarRestApiUrls.forEach((coarRestApiUrl: string) => { + // Add link to head + let tag: LinkDefinition = { + href: coarRestApiUrl, + rel: rel + }; + this.inboxTags.push(tag); + this.linkHeadService.addTag(tag); + + links = links + (isNotEmpty(links) ? ', ' : '') + `<${coarRestApiUrl}> ; rel="${rel}"`; + }); + + return links; + } + ngOnDestroy(): void { this.signpostingLinks.forEach((link: SignpostingLink) => { this.linkHeadService.removeTag(`href='${link.href}'`); }); + this.inboxTags.forEach((link: LinkDefinition) => { + this.linkHeadService.removeTag(`href='${link.href}'`); + }); } }