Implement basic 301 Redirect when SSR is used.

This commit is contained in:
Tim Donohue
2023-06-21 15:36:59 -05:00
parent 499bfe3154
commit 0fe33eecd1
2 changed files with 42 additions and 3 deletions

View File

@@ -27,6 +27,9 @@ describe('DsoRedirectService', () => {
const requestUUIDURL = `https://rest.api/rest/api/pid/find?id=${dsoUUID}`; const requestUUIDURL = `https://rest.api/rest/api/pid/find?id=${dsoUUID}`;
const requestUUID = '34cfed7c-f597-49ef-9cbe-ea351f0023c2'; const requestUUID = '34cfed7c-f597-49ef-9cbe-ea351f0023c2';
const objectCache = {} as ObjectCacheService; const objectCache = {} as ObjectCacheService;
const mockResponse = jasmine.createSpyObj(['redirect']);
const mockPlatformBrowser = 'browser';
const mockPlatformServer = 'server';
beforeEach(() => { beforeEach(() => {
scheduler = getTestScheduler(); scheduler = getTestScheduler();
@@ -58,6 +61,8 @@ describe('DsoRedirectService', () => {
objectCache, objectCache,
halService, halService,
router, router,
mockResponse,
mockPlatformBrowser // default to CSR except where explicitly SSR below
); );
}); });
@@ -141,6 +146,25 @@ describe('DsoRedirectService', () => {
scheduler.flush(); scheduler.flush();
expect(router.navigate).toHaveBeenCalledWith(['/communities/' + remoteData.payload.uuid]); expect(router.navigate).toHaveBeenCalledWith(['/communities/' + remoteData.payload.uuid]);
}); });
it('should return 301 redirect when SSR is used', () => {
service = new DsoRedirectService(
requestService,
rdbService,
objectCache,
halService,
router,
mockResponse,
mockPlatformServer // explicitly SSR mode
);
remoteData.payload.type = 'item';
const redir = service.findByIdAndIDType(dsoHandle, IdentifierType.HANDLE);
// The framework would normally subscribe but do it here so we can test navigation.
redir.subscribe();
scheduler.schedule(() => redir);
scheduler.flush();
expect(mockResponse.redirect).toHaveBeenCalledWith(301, '/items/' + remoteData.payload.uuid);
});
}); });
describe('DataService', () => { // todo: should only test the id/uuid interpolation thingy describe('DataService', () => { // todo: should only test the id/uuid interpolation thingy

View File

@@ -6,7 +6,7 @@
* http://www.dspace.org/license/ * http://www.dspace.org/license/
*/ */
/* eslint-disable max-classes-per-file */ /* eslint-disable max-classes-per-file */
import { Injectable } from '@angular/core'; import { Inject, Injectable, Optional, PLATFORM_ID } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
@@ -21,6 +21,8 @@ import { getFirstCompletedRemoteData } from '../shared/operators';
import { DSpaceObject } from '../shared/dspace-object.model'; import { DSpaceObject } from '../shared/dspace-object.model';
import { IdentifiableDataService } from './base/identifiable-data.service'; import { IdentifiableDataService } from './base/identifiable-data.service';
import { getDSORoute } from '../../app-routing-paths'; import { getDSORoute } from '../../app-routing-paths';
import { RESPONSE } from '@nguniversal/express-engine/tokens';
import { isPlatformServer } from '@angular/common';
const ID_ENDPOINT = 'pid'; const ID_ENDPOINT = 'pid';
const UUID_ENDPOINT = 'dso'; const UUID_ENDPOINT = 'dso';
@@ -75,12 +77,20 @@ export class DsoRedirectService {
protected objectCache: ObjectCacheService, protected objectCache: ObjectCacheService,
protected halService: HALEndpointService, protected halService: HALEndpointService,
private router: Router, private router: Router,
@Optional() @Inject(RESPONSE) private response: any,
@Inject(PLATFORM_ID) private platformId: any
) { ) {
this.dataService = new DsoByIdOrUUIDDataService(requestService, rdbService, objectCache, halService); this.dataService = new DsoByIdOrUUIDDataService(requestService, rdbService, objectCache, halService);
} }
/** /**
* Retrieve a DSpaceObject by * 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.
*
* If this is called server side (via SSR), it performs a 301 Redirect.
* If this is called client side (via CSR), it simply uses the Angular router to do the redirect.
*
* @param id the identifier of the object to retrieve * @param id the identifier of the object to retrieve
* @param identifierType the type of the given identifier (defaults to UUID) * @param identifierType the type of the given identifier (defaults to UUID)
*/ */
@@ -94,10 +104,15 @@ export class DsoRedirectService {
if (hasValue(dso.uuid)) { if (hasValue(dso.uuid)) {
let newRoute = getDSORoute(dso); let newRoute = getDSORoute(dso);
if (hasValue(newRoute)) { if (hasValue(newRoute)) {
// If running via SSR, perform a "301 Moved Permanently" redirect for SEO purposes.
if (isPlatformServer(this.platformId)) {
this.response.redirect(301, newRoute);
} else {
this.router.navigate([newRoute]); this.router.navigate([newRoute]);
} }
} }
} }
}
}) })
); );
} }