Skip to content

Commit 9a74190

Browse files
authored
Merge branch 'main' into feature/CST-9636
2 parents 706c49d + 00e0028 commit 9a74190

16 files changed

Lines changed: 524 additions & 73 deletions

server.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,14 @@ import * as ejs from 'ejs';
2626
import * as compression from 'compression';
2727
import * as expressStaticGzip from 'express-static-gzip';
2828
/* eslint-enable import/no-namespace */
29-
3029
import axios from 'axios';
3130
import LRU from 'lru-cache';
3231
import isbot from 'isbot';
3332
import { createCertificate } from 'pem';
3433
import { createServer } from 'https';
3534
import { json } from 'body-parser';
3635

37-
import { existsSync, readFileSync } from 'fs';
36+
import { readFileSync } from 'fs';
3837
import { join } from 'path';
3938

4039
import { enableProdMode } from '@angular/core';
@@ -180,6 +179,15 @@ export function app() {
180179
changeOrigin: true
181180
}));
182181

182+
/**
183+
* Proxy the linksets
184+
*/
185+
router.use('/signposting**', createProxyMiddleware({
186+
target: `${environment.rest.baseUrl}`,
187+
pathRewrite: path => path.replace(environment.ui.nameSpace, '/'),
188+
changeOrigin: true
189+
}));
190+
183191
/**
184192
* Checks if the rateLimiter property is present
185193
* When it is present, the rateLimiter will be enabled. When it is undefined, the rateLimiter will be disabled.

src/app/access-control/epeople-registry/epeople-registry.component.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -287,14 +287,17 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
287287
/**
288288
* This method will set everything to stale, which will cause the lists on this page to update.
289289
*/
290-
reset() {
290+
reset(): void {
291291
this.epersonService.getBrowseEndpoint().pipe(
292-
take(1)
293-
).subscribe((href: string) => {
294-
this.requestService.setStaleByHrefSubstring(href).pipe(take(1)).subscribe(() => {
295-
this.epersonService.cancelEditEPerson();
296-
this.isEPersonFormShown = false;
297-
});
292+
take(1),
293+
switchMap((href: string) => {
294+
return this.requestService.setStaleByHrefSubstring(href).pipe(
295+
take(1),
296+
);
297+
})
298+
).subscribe(()=>{
299+
this.epersonService.cancelEditEPerson();
300+
this.isEPersonFormShown = false;
298301
});
299302
}
300303
}

src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
} from '@ng-dynamic-forms/core';
99
import { TranslateService } from '@ngx-translate/core';
1010
import { combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
11-
import { debounceTime, switchMap, take } from 'rxjs/operators';
11+
import { debounceTime, finalize, map, switchMap, take } from 'rxjs/operators';
1212
import { PaginatedList } from '../../../core/data/paginated-list.model';
1313
import { RemoteData } from '../../../core/data/remote-data';
1414
import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
@@ -463,31 +463,42 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
463463
* Deletes the EPerson from the Repository. The EPerson will be the only that this form is showing.
464464
* It'll either show a success or error message depending on whether the delete was successful or not.
465465
*/
466-
delete() {
467-
this.epersonService.getActiveEPerson().pipe(take(1)).subscribe((eperson: EPerson) => {
468-
const modalRef = this.modalService.open(ConfirmationModalComponent);
469-
modalRef.componentInstance.dso = eperson;
470-
modalRef.componentInstance.headerLabel = 'confirmation-modal.delete-eperson.header';
471-
modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-eperson.info';
472-
modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-eperson.cancel';
473-
modalRef.componentInstance.confirmLabel = 'confirmation-modal.delete-eperson.confirm';
474-
modalRef.componentInstance.brandColor = 'danger';
475-
modalRef.componentInstance.confirmIcon = 'fas fa-trash';
476-
modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => {
477-
if (confirm) {
478-
if (hasValue(eperson.id)) {
479-
this.epersonService.deleteEPerson(eperson).pipe(getFirstCompletedRemoteData()).subscribe((restResponse: RemoteData<NoContent>) => {
480-
if (restResponse.hasSucceeded) {
481-
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: this.dsoNameService.getName(eperson) }));
482-
this.submitForm.emit();
483-
} else {
484-
this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + eperson.id + ' with code: ' + restResponse.statusCode + ' and message: ' + restResponse.errorMessage);
485-
}
486-
this.cancelForm.emit();
487-
});
488-
}
489-
}
490-
});
466+
delete(): void {
467+
this.epersonService.getActiveEPerson().pipe(
468+
take(1),
469+
switchMap((eperson: EPerson) => {
470+
const modalRef = this.modalService.open(ConfirmationModalComponent);
471+
modalRef.componentInstance.dso = eperson;
472+
modalRef.componentInstance.headerLabel = 'confirmation-modal.delete-eperson.header';
473+
modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-eperson.info';
474+
modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-eperson.cancel';
475+
modalRef.componentInstance.confirmLabel = 'confirmation-modal.delete-eperson.confirm';
476+
modalRef.componentInstance.brandColor = 'danger';
477+
modalRef.componentInstance.confirmIcon = 'fas fa-trash';
478+
479+
return modalRef.componentInstance.response.pipe(
480+
take(1),
481+
switchMap((confirm: boolean) => {
482+
if (confirm && hasValue(eperson.id)) {
483+
this.canDelete$ = observableOf(false);
484+
return this.epersonService.deleteEPerson(eperson).pipe(
485+
getFirstCompletedRemoteData(),
486+
map((restResponse: RemoteData<NoContent>) => ({ restResponse, eperson }))
487+
);
488+
} else {
489+
return observableOf(null);
490+
}
491+
}),
492+
finalize(() => this.canDelete$ = observableOf(true))
493+
);
494+
})
495+
).subscribe(({ restResponse, eperson }: { restResponse: RemoteData<NoContent> | null, eperson: EPerson }) => {
496+
if (restResponse?.hasSucceeded) {
497+
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: this.dsoNameService.getName(eperson) }));
498+
} else {
499+
this.notificationsService.error(`Error occurred when trying to delete EPerson with id: ${eperson?.id} with code: ${restResponse?.statusCode} and message: ${restResponse?.errorMessage}`);
500+
}
501+
this.cancelForm.emit();
491502
});
492503
}
493504

@@ -523,7 +534,6 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
523534
* Cancel the current edit when component is destroyed & unsub all subscriptions
524535
*/
525536
ngOnDestroy(): void {
526-
this.onCancel();
527537
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
528538
this.paginationService.clearPagination(this.config.id);
529539
if (hasValue(this.emailValueChangeSubscribe)) {

src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.spec.ts

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import { ActivatedRoute, Router } from '@angular/router';
1111
import { getForbiddenRoute } from '../../app-routing-paths';
1212
import { TranslateModule } from '@ngx-translate/core';
1313
import { CommonModule } from '@angular/common';
14+
import { SignpostingDataService } from '../../core/data/signposting-data.service';
15+
import { ServerResponseService } from '../../core/services/server-response.service';
16+
import { PLATFORM_ID } from '@angular/core';
1417

1518
describe('BitstreamDownloadPageComponent', () => {
1619
let component: BitstreamDownloadPageComponent;
@@ -24,6 +27,20 @@ describe('BitstreamDownloadPageComponent', () => {
2427
let router;
2528

2629
let bitstream: Bitstream;
30+
let serverResponseService: jasmine.SpyObj<ServerResponseService>;
31+
let signpostingDataService: jasmine.SpyObj<SignpostingDataService>;
32+
33+
const mocklink = {
34+
href: 'http://test.org',
35+
rel: 'test',
36+
type: 'test'
37+
};
38+
39+
const mocklink2 = {
40+
href: 'http://test2.org',
41+
rel: 'test',
42+
type: 'test'
43+
};
2744

2845
function init() {
2946
authService = jasmine.createSpyObj('authService', {
@@ -44,8 +61,8 @@ describe('BitstreamDownloadPageComponent', () => {
4461
bitstream = Object.assign(new Bitstream(), {
4562
uuid: 'bitstreamUuid',
4663
_links: {
47-
content: {href: 'bitstream-content-link'},
48-
self: {href: 'bitstream-self-link'},
64+
content: { href: 'bitstream-content-link' },
65+
self: { href: 'bitstream-self-link' },
4966
}
5067
});
5168

@@ -54,23 +71,37 @@ describe('BitstreamDownloadPageComponent', () => {
5471
bitstream: createSuccessfulRemoteDataObject(
5572
bitstream
5673
)
74+
}),
75+
params: observableOf({
76+
id: 'testid'
5777
})
5878
};
5979

6080
router = jasmine.createSpyObj('router', ['navigateByUrl']);
81+
82+
serverResponseService = jasmine.createSpyObj('ServerResponseService', {
83+
setHeader: jasmine.createSpy('setHeader'),
84+
});
85+
86+
signpostingDataService = jasmine.createSpyObj('SignpostingDataService', {
87+
getLinks: observableOf([mocklink, mocklink2])
88+
});
6189
}
6290

6391
function initTestbed() {
6492
TestBed.configureTestingModule({
6593
imports: [CommonModule, TranslateModule.forRoot()],
6694
declarations: [BitstreamDownloadPageComponent],
6795
providers: [
68-
{provide: ActivatedRoute, useValue: activatedRoute},
69-
{provide: Router, useValue: router},
70-
{provide: AuthorizationDataService, useValue: authorizationService},
71-
{provide: AuthService, useValue: authService},
72-
{provide: FileService, useValue: fileService},
73-
{provide: HardRedirectService, useValue: hardRedirectService},
96+
{ provide: ActivatedRoute, useValue: activatedRoute },
97+
{ provide: Router, useValue: router },
98+
{ provide: AuthorizationDataService, useValue: authorizationService },
99+
{ provide: AuthService, useValue: authService },
100+
{ provide: FileService, useValue: fileService },
101+
{ provide: HardRedirectService, useValue: hardRedirectService },
102+
{ provide: ServerResponseService, useValue: serverResponseService },
103+
{ provide: SignpostingDataService, useValue: signpostingDataService },
104+
{ provide: PLATFORM_ID, useValue: 'server' }
74105
]
75106
})
76107
.compileComponents();
@@ -107,6 +138,9 @@ describe('BitstreamDownloadPageComponent', () => {
107138
it('should redirect to the content link', () => {
108139
expect(hardRedirectService.redirect).toHaveBeenCalledWith('bitstream-content-link');
109140
});
141+
it('should add the signposting links', () => {
142+
expect(serverResponseService.setHeader).toHaveBeenCalled();
143+
});
110144
});
111145
describe('when the user is authorized and logged in', () => {
112146
beforeEach(waitForAsync(() => {
@@ -134,7 +168,7 @@ describe('BitstreamDownloadPageComponent', () => {
134168
fixture.detectChanges();
135169
});
136170
it('should navigate to the forbidden route', () => {
137-
expect(router.navigateByUrl).toHaveBeenCalledWith(getForbiddenRoute(), {skipLocationChange: true});
171+
expect(router.navigateByUrl).toHaveBeenCalledWith(getForbiddenRoute(), { skipLocationChange: true });
138172
});
139173
});
140174
describe('when the user is not authorized and not logged in', () => {

src/app/bitstream-page/bitstream-download-page/bitstream-download-page.component.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { Component, OnInit } from '@angular/core';
1+
import { Component, Inject, OnInit, PLATFORM_ID } from '@angular/core';
22
import { filter, map, switchMap, take } from 'rxjs/operators';
33
import { ActivatedRoute, Router } from '@angular/router';
44
import { hasValue, isNotEmpty } from '../../shared/empty.util';
5-
import { getRemoteDataPayload} from '../../core/shared/operators';
5+
import { getRemoteDataPayload } from '../../core/shared/operators';
66
import { Bitstream } from '../../core/shared/bitstream.model';
77
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
88
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
@@ -13,8 +13,11 @@ import { HardRedirectService } from '../../core/services/hard-redirect.service';
1313
import { getForbiddenRoute } from '../../app-routing-paths';
1414
import { RemoteData } from '../../core/data/remote-data';
1515
import { redirectOn4xx } from '../../core/shared/authorized.operators';
16-
import { Location } from '@angular/common';
16+
import { isPlatformServer, Location } from '@angular/common';
1717
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
18+
import { SignpostingDataService } from '../../core/data/signposting-data.service';
19+
import { ServerResponseService } from '../../core/services/server-response.service';
20+
import { SignpostingLink } from '../../core/data/signposting-links.model';
1821

1922
@Component({
2023
selector: 'ds-bitstream-download-page',
@@ -28,7 +31,6 @@ export class BitstreamDownloadPageComponent implements OnInit {
2831
bitstream$: Observable<Bitstream>;
2932
bitstreamRD$: Observable<RemoteData<Bitstream>>;
3033

31-
3234
constructor(
3335
private route: ActivatedRoute,
3436
protected router: Router,
@@ -38,8 +40,11 @@ export class BitstreamDownloadPageComponent implements OnInit {
3840
private hardRedirectService: HardRedirectService,
3941
private location: Location,
4042
public dsoNameService: DSONameService,
43+
private signpostingDataService: SignpostingDataService,
44+
private responseService: ServerResponseService,
45+
@Inject(PLATFORM_ID) protected platformId: string
4146
) {
42-
47+
this.initPageLinks();
4348
}
4449

4550
back(): void {
@@ -89,4 +94,26 @@ export class BitstreamDownloadPageComponent implements OnInit {
8994
}
9095
});
9196
}
97+
98+
/**
99+
* Create page links if any are retrieved by signposting endpoint
100+
*
101+
* @private
102+
*/
103+
private initPageLinks(): void {
104+
if (isPlatformServer(this.platformId)) {
105+
this.route.params.subscribe(params => {
106+
this.signpostingDataService.getLinks(params.id).pipe(take(1)).subscribe((signpostingLinks: SignpostingLink[]) => {
107+
let links = '';
108+
109+
signpostingLinks.forEach((link: SignpostingLink) => {
110+
links = links + (isNotEmpty(links) ? ', ' : '') + `<${link.href}> ; rel="${link.rel}"` + (isNotEmpty(link.type) ? ` ; type="${link.type}" ` : ' ');
111+
links = links + (isNotEmpty(links) ? ', ' : '') + `<${link.href}> ; rel="${link.rel}" ; type="${link.type}" `;
112+
});
113+
114+
this.responseService.setHeader('Link', links);
115+
});
116+
});
117+
}
118+
}
92119
}

src/app/core/data/request.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ export class RequestService {
331331
map((request: RequestEntry) => isStale(request.state)),
332332
filter((stale: boolean) => stale),
333333
take(1),
334-
);
334+
);
335335
}
336336

337337
/**

0 commit comments

Comments
 (0)