From 0fe33eecd1dedd56faedb43010c55a517c975f7e Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 21 Jun 2023 15:36:59 -0500 Subject: [PATCH] Implement basic 301 Redirect when SSR is used. --- .../core/data/dso-redirect.service.spec.ts | 24 +++++++++++++++++++ src/app/core/data/dso-redirect.service.ts | 21 +++++++++++++--- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/app/core/data/dso-redirect.service.spec.ts b/src/app/core/data/dso-redirect.service.spec.ts index ca064b5608..64fbd94367 100644 --- a/src/app/core/data/dso-redirect.service.spec.ts +++ b/src/app/core/data/dso-redirect.service.spec.ts @@ -27,6 +27,9 @@ describe('DsoRedirectService', () => { const requestUUIDURL = `https://rest.api/rest/api/pid/find?id=${dsoUUID}`; const requestUUID = '34cfed7c-f597-49ef-9cbe-ea351f0023c2'; const objectCache = {} as ObjectCacheService; + const mockResponse = jasmine.createSpyObj(['redirect']); + const mockPlatformBrowser = 'browser'; + const mockPlatformServer = 'server'; beforeEach(() => { scheduler = getTestScheduler(); @@ -58,6 +61,8 @@ describe('DsoRedirectService', () => { objectCache, halService, router, + mockResponse, + mockPlatformBrowser // default to CSR except where explicitly SSR below ); }); @@ -141,6 +146,25 @@ describe('DsoRedirectService', () => { scheduler.flush(); 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 diff --git a/src/app/core/data/dso-redirect.service.ts b/src/app/core/data/dso-redirect.service.ts index 81ce678e43..9b87590f7e 100644 --- a/src/app/core/data/dso-redirect.service.ts +++ b/src/app/core/data/dso-redirect.service.ts @@ -6,7 +6,7 @@ * http://www.dspace.org/license/ */ /* 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 { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; @@ -21,6 +21,8 @@ import { getFirstCompletedRemoteData } from '../shared/operators'; import { DSpaceObject } from '../shared/dspace-object.model'; import { IdentifiableDataService } from './base/identifiable-data.service'; import { getDSORoute } from '../../app-routing-paths'; +import { RESPONSE } from '@nguniversal/express-engine/tokens'; +import { isPlatformServer } from '@angular/common'; const ID_ENDPOINT = 'pid'; const UUID_ENDPOINT = 'dso'; @@ -75,12 +77,20 @@ export class DsoRedirectService { protected objectCache: ObjectCacheService, protected halService: HALEndpointService, private router: Router, + @Optional() @Inject(RESPONSE) private response: any, + @Inject(PLATFORM_ID) private platformId: any ) { 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 identifierType the type of the given identifier (defaults to UUID) */ @@ -94,7 +104,12 @@ export class DsoRedirectService { if (hasValue(dso.uuid)) { let newRoute = getDSORoute(dso); if (hasValue(newRoute)) { - this.router.navigate([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]); + } } } }