Skip to content

Commit 1c325cd

Browse files
authored
Merge pull request DSpace#3062 from atmire/legacy-bitstream-redirect_contribute-main
Made legacy bitstream URLs redirect with 301 status code
2 parents 659052f + bd10925 commit 1c325cd

6 files changed

Lines changed: 248 additions & 201 deletions

src/app/bitstream-page/bitstream-page-routes.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { BitstreamAuthorizationsComponent } from './bitstream-authorizations/bit
1111
import { BitstreamDownloadPageComponent } from './bitstream-download-page/bitstream-download-page.component';
1212
import { bitstreamPageResolver } from './bitstream-page.resolver';
1313
import { ThemedEditBitstreamPageComponent } from './edit-bitstream-page/themed-edit-bitstream-page.component';
14-
import { legacyBitstreamUrlResolver } from './legacy-bitstream-url.resolver';
14+
import { legacyBitstreamURLRedirectGuard } from './legacy-bitstream-url-redirect.guard';
1515

1616
const EDIT_BITSTREAM_PATH = ':id/edit';
1717
const EDIT_BITSTREAM_AUTHORIZATIONS_PATH = ':id/authorizations';
@@ -23,18 +23,12 @@ export const ROUTES: Route[] = [
2323
{
2424
// Resolve XMLUI bitstream download URLs
2525
path: 'handle/:prefix/:suffix/:filename',
26-
component: BitstreamDownloadPageComponent,
27-
resolve: {
28-
bitstream: legacyBitstreamUrlResolver,
29-
},
26+
canActivate: [legacyBitstreamURLRedirectGuard],
3027
},
3128
{
3229
// Resolve JSPUI bitstream download URLs
3330
path: ':prefix/:suffix/:sequence_id/:filename',
34-
component: BitstreamDownloadPageComponent,
35-
resolve: {
36-
bitstream: legacyBitstreamUrlResolver,
37-
},
31+
canActivate: [legacyBitstreamURLRedirectGuard],
3832
},
3933
{
4034
// Resolve angular bitstream download URLs
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import { cold } from 'jasmine-marbles';
2+
import { EMPTY } from 'rxjs';
3+
4+
import { PAGE_NOT_FOUND_PATH } from '../app-routing-paths';
5+
import { BitstreamDataService } from '../core/data/bitstream-data.service';
6+
import { RemoteData } from '../core/data/remote-data';
7+
import { RequestEntryState } from '../core/data/request-entry-state.model';
8+
import { BrowserHardRedirectService } from '../core/services/browser-hard-redirect.service';
9+
import { HardRedirectService } from '../core/services/hard-redirect.service';
10+
import { Bitstream } from '../core/shared/bitstream.model';
11+
import { RouterStub } from '../shared/testing/router.stub';
12+
import { legacyBitstreamURLRedirectGuard } from './legacy-bitstream-url-redirect.guard';
13+
14+
describe('legacyBitstreamURLRedirectGuard', () => {
15+
let resolver: any;
16+
let bitstreamDataService: BitstreamDataService;
17+
let remoteDataMocks: { [type: string]: RemoteData<Bitstream> };
18+
let route;
19+
let state;
20+
let hardRedirectService: HardRedirectService;
21+
let router: RouterStub;
22+
23+
let bitstream: Bitstream;
24+
25+
beforeEach(() => {
26+
route = {
27+
params: {},
28+
queryParams: {},
29+
};
30+
router = new RouterStub();
31+
hardRedirectService = new BrowserHardRedirectService(window.location);
32+
state = {};
33+
bitstream = Object.assign(new Bitstream(), {
34+
uuid: 'bitstream-id',
35+
});
36+
remoteDataMocks = {
37+
RequestPending: new RemoteData(undefined, 0, 0, RequestEntryState.RequestPending, undefined, undefined, undefined),
38+
ResponsePending: new RemoteData(undefined, 0, 0, RequestEntryState.ResponsePending, undefined, undefined, undefined),
39+
Success: new RemoteData(0, 0, 0, RequestEntryState.Success, undefined, bitstream, 200),
40+
NoContent: new RemoteData(0, 0, 0, RequestEntryState.Success, undefined, undefined, 204),
41+
Error: new RemoteData(0, 0, 0, RequestEntryState.Error, 'Internal server error', undefined, 500),
42+
};
43+
bitstreamDataService = {
44+
findByItemHandle: () => undefined,
45+
} as any;
46+
resolver = legacyBitstreamURLRedirectGuard;
47+
});
48+
49+
describe(`resolve`, () => {
50+
describe(`For JSPUI-style URLs`, () => {
51+
beforeEach(() => {
52+
spyOn(bitstreamDataService, 'findByItemHandle').and.returnValue(EMPTY);
53+
route = Object.assign({}, route, {
54+
params: {
55+
prefix: '123456789',
56+
suffix: '1234',
57+
filename: 'some-file.pdf',
58+
sequence_id: '5',
59+
},
60+
});
61+
});
62+
it(`should call findByItemHandle with the handle, sequence id, and filename from the route`, () => {
63+
resolver(route, state, bitstreamDataService, hardRedirectService, router);
64+
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalledWith(
65+
`${route.params.prefix}/${route.params.suffix}`,
66+
route.params.sequence_id,
67+
route.params.filename,
68+
);
69+
});
70+
});
71+
72+
describe(`For XMLUI-style URLs`, () => {
73+
describe(`when there is a sequenceId query parameter`, () => {
74+
beforeEach(() => {
75+
spyOn(bitstreamDataService, 'findByItemHandle').and.returnValue(EMPTY);
76+
route = Object.assign({}, route, {
77+
params: {
78+
prefix: '123456789',
79+
suffix: '1234',
80+
filename: 'some-file.pdf',
81+
},
82+
queryParams: {
83+
sequenceId: '5',
84+
},
85+
});
86+
});
87+
it(`should call findByItemHandle with the handle and filename from the route, and the sequence ID from the queryParams`, () => {
88+
resolver(route, state, bitstreamDataService, hardRedirectService, router);
89+
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalledWith(
90+
`${route.params.prefix}/${route.params.suffix}`,
91+
route.queryParams.sequenceId,
92+
route.params.filename,
93+
);
94+
});
95+
});
96+
describe(`when there's no sequenceId query parameter`, () => {
97+
beforeEach(() => {
98+
spyOn(bitstreamDataService, 'findByItemHandle').and.returnValue(EMPTY);
99+
route = Object.assign({}, route, {
100+
params: {
101+
prefix: '123456789',
102+
suffix: '1234',
103+
filename: 'some-file.pdf',
104+
},
105+
});
106+
});
107+
it(`should call findByItemHandle with the handle, and filename from the route`, () => {
108+
resolver(route, state, bitstreamDataService, hardRedirectService, router);
109+
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalledWith(
110+
`${route.params.prefix}/${route.params.suffix}`,
111+
undefined,
112+
route.params.filename,
113+
);
114+
});
115+
});
116+
});
117+
describe('should return and complete after the RemoteData has...', () => {
118+
it('...failed', () => {
119+
spyOn(router, 'createUrlTree').and.callThrough();
120+
spyOn(bitstreamDataService, 'findByItemHandle').and.returnValue(cold('a-b-c', {
121+
a: remoteDataMocks.RequestPending,
122+
b: remoteDataMocks.ResponsePending,
123+
c: remoteDataMocks.Error,
124+
}));
125+
resolver(route, state, bitstreamDataService, hardRedirectService, router).subscribe(() => {
126+
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalled();
127+
expect(router.createUrlTree).toHaveBeenCalledWith([PAGE_NOT_FOUND_PATH]);
128+
});
129+
});
130+
131+
it('...succeeded without content', () => {
132+
spyOn(router, 'createUrlTree').and.callThrough();
133+
spyOn(bitstreamDataService, 'findByItemHandle').and.returnValue(cold('a-b-c', {
134+
a: remoteDataMocks.RequestPending,
135+
b: remoteDataMocks.ResponsePending,
136+
c: remoteDataMocks.NoContent,
137+
}));
138+
resolver(route, state, bitstreamDataService, hardRedirectService, router).subscribe(() => {
139+
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalled();
140+
expect(router.createUrlTree).toHaveBeenCalledWith([PAGE_NOT_FOUND_PATH]);
141+
});
142+
});
143+
144+
it('...succeeded', () => {
145+
spyOn(hardRedirectService, 'redirect');
146+
spyOn(bitstreamDataService, 'findByItemHandle').and.returnValue(cold('a-b-c', {
147+
a: remoteDataMocks.RequestPending,
148+
b: remoteDataMocks.ResponsePending,
149+
c: remoteDataMocks.Success,
150+
}));
151+
resolver(route, state, bitstreamDataService, hardRedirectService, router).subscribe(() => {
152+
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalled();
153+
expect(hardRedirectService.redirect).toHaveBeenCalledWith(new URL(`/bitstreams/${bitstream.uuid}/download`, window.location.origin).href, 301);
154+
});
155+
});
156+
});
157+
});
158+
});
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { inject } from '@angular/core';
2+
import {
3+
ActivatedRouteSnapshot,
4+
CanActivateFn,
5+
Router,
6+
RouterStateSnapshot,
7+
UrlTree,
8+
} from '@angular/router';
9+
import { Observable } from 'rxjs';
10+
import { map } from 'rxjs/operators';
11+
12+
import { PAGE_NOT_FOUND_PATH } from '../app-routing-paths';
13+
import { BitstreamDataService } from '../core/data/bitstream-data.service';
14+
import { RemoteData } from '../core/data/remote-data';
15+
import { HardRedirectService } from '../core/services/hard-redirect.service';
16+
import { Bitstream } from '../core/shared/bitstream.model';
17+
import { getFirstCompletedRemoteData } from '../core/shared/operators';
18+
import { hasNoValue } from '../shared/empty.util';
19+
20+
/**
21+
* Redirects to a bitstream based on the handle of the item, and the sequence id or the filename of the
22+
* bitstream. In production mode the status code will also be set the status code to 301 marking it as a permanent URL
23+
* redirect for bots to the regular bitstream download Page.
24+
*
25+
* @returns Either a {@link UrlTree} to the 404 page when the url isn't a valid format or false in order to make the
26+
* user wait until the {@link HardRedirectService#redirect} was performed
27+
*/
28+
export const legacyBitstreamURLRedirectGuard: CanActivateFn = (
29+
route: ActivatedRouteSnapshot,
30+
state: RouterStateSnapshot,
31+
bitstreamDataService: BitstreamDataService = inject(BitstreamDataService),
32+
serverHardRedirectService: HardRedirectService = inject(HardRedirectService),
33+
router: Router = inject(Router),
34+
): Observable<UrlTree | false> => {
35+
const prefix = route.params.prefix;
36+
const suffix = route.params.suffix;
37+
const filename = route.params.filename;
38+
let sequenceId = route.params.sequence_id;
39+
if (hasNoValue(sequenceId)) {
40+
sequenceId = route.queryParams.sequenceId;
41+
}
42+
return bitstreamDataService.findByItemHandle(
43+
`${prefix}/${suffix}`,
44+
sequenceId,
45+
filename,
46+
).pipe(
47+
getFirstCompletedRemoteData(),
48+
map((rd: RemoteData<Bitstream>) => {
49+
if (rd.hasSucceeded && !rd.hasNoContent) {
50+
serverHardRedirectService.redirect(new URL(`/bitstreams/${rd.payload.uuid}/download`, serverHardRedirectService.getCurrentOrigin()).href, 301);
51+
return false;
52+
} else {
53+
return router.createUrlTree([PAGE_NOT_FOUND_PATH]);
54+
}
55+
}),
56+
);
57+
};

src/app/bitstream-page/legacy-bitstream-url.resolver.spec.ts

Lines changed: 0 additions & 146 deletions
This file was deleted.

0 commit comments

Comments
 (0)