[CST-12235] support for the LDN inbox for root & /home & /item/:uuid pages

This commit is contained in:
Alisa Ismailati
2023-10-24 15:14:55 +02:00
parent 6d794cd51f
commit 422a223c40
6 changed files with 125 additions and 17 deletions

View File

@@ -10,7 +10,10 @@ import { ConfigurationProperty } from '../../shared/configuration-property.model
}) })
export class NotifyInfoService { 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( constructor(
private configService: ConfigurationDataService, private configService: ConfigurationDataService,
@@ -27,7 +30,11 @@ export class NotifyInfoService {
); );
} }
getCoarLdnLocalInboxUrl(): Observable<string[]> { /**
* Get the url of the local inbox from the REST configuration
* @returns the url of the local inbox
*/
getCoarLdnLocalInboxUrls(): Observable<string[]> {
return this.configService.findByPropertyName('ldn.notify.local-inbox-endpoint').pipe( return this.configService.findByPropertyName('ldn.notify.local-inbox-endpoint').pipe(
getFirstSucceededRemoteData(), getFirstSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
@@ -49,7 +56,11 @@ export class NotifyInfoService {
return `${ssl ? 'https' : 'http'}://${host}:${port}${namespace}`; 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;
} }
} }

View File

@@ -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 { map, switchMap } from 'rxjs/operators';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Observable, of } from 'rxjs'; import { Observable } from 'rxjs';
import { Site } from '../core/shared/site.model'; import { Site } from '../core/shared/site.model';
import { environment } from '../../environments/environment'; import { environment } from '../../environments/environment';
import { isPlatformServer } from '@angular/common'; import { isPlatformServer } from '@angular/common';
import { ServerResponseService } from '../core/services/server-response.service'; import { ServerResponseService } from '../core/services/server-response.service';
import { NotifyInfoService } from '../core/coar-notify/notify-info/notify-info.service'; import { NotifyInfoService } from '../core/coar-notify/notify-info/notify-info.service';
import { LinkDefinition, LinkHeadService } from '../core/services/link-head.service'; import { LinkDefinition, LinkHeadService } from '../core/services/link-head.service';
import { isNotEmpty } from '../shared/empty.util';
@Component({ @Component({
selector: 'ds-home-page', selector: 'ds-home-page',
styleUrls: ['./home-page.component.scss'], styleUrls: ['./home-page.component.scss'],
templateUrl: './home-page.component.html' templateUrl: './home-page.component.html'
}) })
export class HomePageComponent implements OnInit { export class HomePageComponent implements OnInit, OnDestroy {
site$: Observable<Site>; site$: Observable<Site>;
recentSubmissionspageSize: number; recentSubmissionspageSize: number;
/**
* An array of LinkDefinition objects representing inbox links for the home page.
*/
inboxLinks: LinkDefinition[] = [];
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
@@ -27,14 +32,18 @@ export class HomePageComponent implements OnInit {
@Inject(PLATFORM_ID) private platformId: string @Inject(PLATFORM_ID) private platformId: string
) { ) {
this.recentSubmissionspageSize = environment.homePage.recentSubmissions.pageSize; this.recentSubmissionspageSize = environment.homePage.recentSubmissions.pageSize;
// Get COAR REST API URLs from REST configuration
// only if COAR configuration is enabled
this.notifyInfoService.isCoarConfigEnabled().pipe( this.notifyInfoService.isCoarConfigEnabled().pipe(
switchMap((coarLdnEnabled: boolean) => { switchMap((coarLdnEnabled: boolean) => {
if (coarLdnEnabled) { if (coarLdnEnabled) {
return this.notifyInfoService.getCoarLdnLocalInboxUrl(); return this.notifyInfoService.getCoarLdnLocalInboxUrls();
} }
}) })
).subscribe((coarRestApiUrls: string[]) => { ).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 { private initPageLinks(coarRestApiUrls: string[]): void {
const rel = this.notifyInfoService.getRelationLink(); const rel = this.notifyInfoService.getInboxRelationLink();
let links = '';
coarRestApiUrls.forEach((coarRestApiUrl: string) => { coarRestApiUrls.forEach((coarRestApiUrl: string) => {
// Add link to head
let tag: LinkDefinition = { let tag: LinkDefinition = {
href: coarRestApiUrl, href: coarRestApiUrl,
rel: rel rel: rel
}; };
this.inboxLinks.push(tag);
this.linkHeadService.addTag(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}'`);
}); });
} }
} }

View File

@@ -23,6 +23,7 @@ import { RemoteData } from '../../core/data/remote-data';
import { ServerResponseService } from '../../core/services/server-response.service'; import { ServerResponseService } from '../../core/services/server-response.service';
import { SignpostingDataService } from '../../core/data/signposting-data.service'; import { SignpostingDataService } from '../../core/data/signposting-data.service';
import { LinkHeadService } from '../../core/services/link-head.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(), { const mockItem: Item = Object.assign(new Item(), {
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
@@ -122,6 +123,7 @@ describe('FullItemPageComponent', () => {
{ provide: ServerResponseService, useValue: serverResponseService }, { provide: ServerResponseService, useValue: serverResponseService },
{ provide: SignpostingDataService, useValue: signpostingDataService }, { provide: SignpostingDataService, useValue: signpostingDataService },
{ provide: LinkHeadService, useValue: linkHeadService }, { provide: LinkHeadService, useValue: linkHeadService },
{ provide: NotifyInfoService, useValue: {} },
{ provide: PLATFORM_ID, useValue: 'server' } { provide: PLATFORM_ID, useValue: 'server' }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]

View File

@@ -19,6 +19,7 @@ import { AuthorizationDataService } from '../../core/data/feature-authorization/
import { ServerResponseService } from '../../core/services/server-response.service'; import { ServerResponseService } from '../../core/services/server-response.service';
import { SignpostingDataService } from '../../core/data/signposting-data.service'; import { SignpostingDataService } from '../../core/data/signposting-data.service';
import { LinkHeadService } from '../../core/services/link-head.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. * This component renders a full item page.
@@ -55,9 +56,10 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit,
protected responseService: ServerResponseService, protected responseService: ServerResponseService,
protected signpostingDataService: SignpostingDataService, protected signpostingDataService: SignpostingDataService,
protected linkHeadService: LinkHeadService, protected linkHeadService: LinkHeadService,
protected notifyInfoService: NotifyInfoService,
@Inject(PLATFORM_ID) protected platformId: string, @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 **/ /*** AoT inheritance fix, will hopefully be resolved in the near future **/

View File

@@ -26,6 +26,7 @@ import { ServerResponseService } from '../../core/services/server-response.servi
import { SignpostingDataService } from '../../core/data/signposting-data.service'; import { SignpostingDataService } from '../../core/data/signposting-data.service';
import { LinkDefinition, LinkHeadService } from '../../core/services/link-head.service'; import { LinkDefinition, LinkHeadService } from '../../core/services/link-head.service';
import { SignpostingLink } from '../../core/data/signposting-links.model'; 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(), { const mockItem: Item = Object.assign(new Item(), {
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
@@ -62,6 +63,7 @@ describe('ItemPageComponent', () => {
let serverResponseService: jasmine.SpyObj<ServerResponseService>; let serverResponseService: jasmine.SpyObj<ServerResponseService>;
let signpostingDataService: jasmine.SpyObj<SignpostingDataService>; let signpostingDataService: jasmine.SpyObj<SignpostingDataService>;
let linkHeadService: jasmine.SpyObj<LinkHeadService>; let linkHeadService: jasmine.SpyObj<LinkHeadService>;
let notifyInfoService: jasmine.SpyObj<NotifyInfoService>;
const mockMetadataService = { const mockMetadataService = {
/* eslint-disable no-empty,@typescript-eslint/no-empty-function */ /* eslint-disable no-empty,@typescript-eslint/no-empty-function */
@@ -94,6 +96,12 @@ describe('ItemPageComponent', () => {
removeTag: jasmine.createSpy('removeTag'), 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({ TestBed.configureTestingModule({
imports: [TranslateModule.forRoot({ imports: [TranslateModule.forRoot({
loader: { loader: {
@@ -112,6 +120,7 @@ describe('ItemPageComponent', () => {
{ provide: ServerResponseService, useValue: serverResponseService }, { provide: ServerResponseService, useValue: serverResponseService },
{ provide: SignpostingDataService, useValue: signpostingDataService }, { provide: SignpostingDataService, useValue: signpostingDataService },
{ provide: LinkHeadService, useValue: linkHeadService }, { provide: LinkHeadService, useValue: linkHeadService },
{ provide: NotifyInfoService, useValue: notifyInfoService},
{ provide: PLATFORM_ID, useValue: 'server' }, { provide: PLATFORM_ID, useValue: 'server' },
], ],

View File

@@ -2,8 +2,8 @@ import { ChangeDetectionStrategy, Component, Inject, OnDestroy, OnInit, PLATFORM
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { isPlatformServer } from '@angular/common'; import { isPlatformServer } from '@angular/common';
import { Observable } from 'rxjs'; import { Observable, combineLatest } from 'rxjs';
import { map, take } from 'rxjs/operators'; import { map, switchMap, take } from 'rxjs/operators';
import { ItemDataService } from '../../core/data/item-data.service'; import { ItemDataService } from '../../core/data/item-data.service';
import { RemoteData } from '../../core/data/remote-data'; 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 { SignpostingLink } from '../../core/data/signposting-links.model';
import { isNotEmpty } from '../../shared/empty.util'; import { isNotEmpty } from '../../shared/empty.util';
import { LinkDefinition, LinkHeadService } from '../../core/services/link-head.service'; 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. * This component renders a simple item page.
@@ -68,6 +69,13 @@ export class ItemPageComponent implements OnInit, OnDestroy {
*/ */
signpostingLinks: SignpostingLink[] = []; signpostingLinks: SignpostingLink[] = [];
/**
* An array of LinkDefinition objects representing inbox links for the item page.
*/
inboxTags: LinkDefinition[] = [];
coarRestApiUrls: string[] = [];
constructor( constructor(
protected route: ActivatedRoute, protected route: ActivatedRoute,
protected router: Router, protected router: Router,
@@ -77,6 +85,7 @@ export class ItemPageComponent implements OnInit, OnDestroy {
protected responseService: ServerResponseService, protected responseService: ServerResponseService,
protected signpostingDataService: SignpostingDataService, protected signpostingDataService: SignpostingDataService,
protected linkHeadService: LinkHeadService, protected linkHeadService: LinkHeadService,
protected notifyInfoService: NotifyInfoService,
@Inject(PLATFORM_ID) protected platformId: string @Inject(PLATFORM_ID) protected platformId: string
) { ) {
this.initPageLinks(); this.initPageLinks();
@@ -106,7 +115,8 @@ export class ItemPageComponent implements OnInit, OnDestroy {
*/ */
private initPageLinks(): void { private initPageLinks(): void {
this.route.params.subscribe(params => { 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 = ''; let links = '';
this.signpostingLinks = signpostingLinks; this.signpostingLinks = signpostingLinks;
@@ -124,6 +134,11 @@ export class ItemPageComponent implements OnInit, OnDestroy {
this.linkHeadService.addTag(tag); this.linkHeadService.addTag(tag);
}); });
if (coarRestApiUrls.length > 0) {
let inboxLinks = this.initPageInboxLinks(coarRestApiUrls);
links = links + (isNotEmpty(links) ? ', ' : '') + inboxLinks;
}
if (isPlatformServer(this.platformId)) { if (isPlatformServer(this.platformId)) {
this.responseService.setHeader('Link', links); 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<string[]> {
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 { ngOnDestroy(): void {
this.signpostingLinks.forEach((link: SignpostingLink) => { this.signpostingLinks.forEach((link: SignpostingLink) => {
this.linkHeadService.removeTag(`href='${link.href}'`); this.linkHeadService.removeTag(`href='${link.href}'`);
}); });
this.inboxTags.forEach((link: LinkDefinition) => {
this.linkHeadService.removeTag(`href='${link.href}'`);
});
} }
} }