diff --git a/src/app/item-page/access-by-token/field-components/file-download-link/item-secure-file-download-link.component.html b/src/app/item-page/access-by-token/field-components/file-download-link/item-secure-file-download-link.component.html new file mode 100644 index 0000000000..476b3f356b --- /dev/null +++ b/src/app/item-page/access-by-token/field-components/file-download-link/item-secure-file-download-link.component.html @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/app/item-page/access-by-token/field-components/file-download-link/item-secure-file-download-link.component.scss b/src/app/item-page/access-by-token/field-components/file-download-link/item-secure-file-download-link.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/item-page/access-by-token/field-components/file-download-link/item-secure-file-download-link.component.spec.ts b/src/app/item-page/access-by-token/field-components/file-download-link/item-secure-file-download-link.component.spec.ts new file mode 100644 index 0000000000..dffdd007c0 --- /dev/null +++ b/src/app/item-page/access-by-token/field-components/file-download-link/item-secure-file-download-link.component.spec.ts @@ -0,0 +1,343 @@ +import { + ComponentFixture, + TestBed, + waitForAsync, +} from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { of as observableOf } from 'rxjs'; + +import { getBitstreamModuleRoute } from '../../../../app-routing-paths'; +import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from '../../../../core/data/feature-authorization/feature-id'; +import { ItemRequestDataService } from '../../../../core/data/item-request-data.service'; +import { Bitstream } from '../../../../core/shared/bitstream.model'; +import { Item } from '../../../../core/shared/item.model'; +import { ItemRequest } from '../../../../core/shared/item-request.model'; +import { URLCombiner } from '../../../../core/url-combiner/url-combiner'; +import { createSuccessfulRemoteDataObject } from '../../../../shared/remote-data.utils'; +import { RouterLinkDirectiveStub } from '../../../../shared/testing/router-link-directive.stub'; +import { getItemModuleRoute } from '../../../item-page-routing-paths'; +import { ItemSecureFileDownloadLinkComponent } from './item-secure-file-download-link.component'; + +describe('FileDownloadLinkComponent', () => { + let component: ItemSecureFileDownloadLinkComponent; + let fixture: ComponentFixture; + + let authorizationService: AuthorizationDataService; + let itemRequestDataService: ItemRequestDataService; + let bitstream: Bitstream; + let item: Item; + let itemRequest: ItemRequest; + let routeStub: any; + + function init() { + itemRequestDataService = jasmine.createSpyObj('itemRequestDataService', { + canDownload: observableOf(true), + }); + bitstream = Object.assign(new Bitstream(), { + uuid: 'bitstreamUuid', + _links: { + self: { href: 'obj-selflink' }, + }, + }); + item = Object.assign(new Item(), { + uuid: 'itemUuid', + _links: { + self: { href: 'obj-selflink' }, + }, + }); + routeStub = { + data: observableOf({ + dso: createSuccessfulRemoteDataObject(item), + }), + children: [], + }; + + itemRequest = Object.assign(new ItemRequest(), + { + accessToken: 'accessToken', + itemId: item.uuid, + bitstreamId: bitstream.uuid, + allfiles: false, + requestEmail: 'user@name.org', + requestName: 'User Name', + requestMessage: 'I would like to request a copy', + }); + } + + function initTestbed() { + + TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot(), ItemSecureFileDownloadLinkComponent, + RouterLinkDirectiveStub, + ], + providers: [ + { provide: AuthorizationDataService, useValue: authorizationService }, + { provide: ActivatedRoute, useValue: routeStub }, + { provide: RouterLinkDirectiveStub }, + { provide: ItemRequestDataService, useValue: itemRequestDataService }, + ], + }) .compileComponents(); + } + + describe('when the user has download rights AND a valid item access token', () => { + /** + * We expect the normal download link to be rendered, whether or not there is a valid item request or request a copy feature + * available, since the user already has the right to download this file + */ + beforeEach(waitForAsync(() => { + init(); + authorizationService = jasmine.createSpyObj('authorizationService', { + isAuthorized: observableOf(true), + }); + initTestbed(); + })); + beforeEach(() => { + fixture = TestBed.createComponent(ItemSecureFileDownloadLinkComponent); + component = fixture.componentInstance; + component.bitstream = bitstream; + component.item = item; + component.itemRequest = itemRequest; + component.enableRequestACopy = true; + fixture.detectChanges(); + }); + it('should init the component', () => { + expect(component).toBeTruthy(); + }); + it('canDownload$ should return true', () => { + component.canDownload$.subscribe((canDownload) => { + expect(canDownload).toBe(true); + }); + }); + it('canDownloadWithToken$ should return true', () => { + component.canDownloadWithToken$.subscribe((canDownloadWithToken) => { + expect(canDownloadWithToken).toBe(true); + }); + }); + it('canRequestACopy$ should return true', () => { + component.canRequestACopy$.subscribe((canRequestACopy) => { + expect(canRequestACopy).toBe(true); + }); + }); + it('should return the bitstreamPath based on the input bitstream', () => { + component.bitstreamPath$.subscribe((bitstreamPath) => { + expect(bitstreamPath).toEqual({ + routerLink: new URLCombiner(getBitstreamModuleRoute(), bitstream.uuid, 'download').toString(), + queryParams: {}, + }); + }); + }); + }); + + describe('when the user has download rights but no valid item access token', () => { + /** + * We expect the normal download link to be rendered, whether or not there is a valid item request or request a copy feature + * available, since the user already has the right to download this file + */ + beforeEach(waitForAsync(() => { + init(); + authorizationService = jasmine.createSpyObj('authorizationService', { + isAuthorized: observableOf(true), + }); + initTestbed(); + })); + beforeEach(() => { + fixture = TestBed.createComponent(ItemSecureFileDownloadLinkComponent); + component = fixture.componentInstance; + component.bitstream = bitstream; + component.item = item; + component.itemRequest = null; + component.enableRequestACopy = true; + fixture.detectChanges(); + }); + it('should init the component', () => { + expect(component).toBeTruthy(); + }); + it('canDownload$ should return true', () => { + component.canDownload$.subscribe((canDownload) => { + expect(canDownload).toBe(true); + }); + }); + it('canDownloadWithToken$ should return false', () => { + component.canDownloadWithToken$.subscribe((canDownloadWithToken) => { + expect(canDownloadWithToken).toBe(false); + }); + }); + it('canRequestACopy$ should return true', () => { + component.canRequestACopy$.subscribe((canRequestACopy) => { + expect(canRequestACopy).toBe(true); + }); + }); + it('should return the bitstreamPath based on the input bitstream', () => { + component.bitstreamPath$.subscribe((bitstreamPath) => { + expect(bitstreamPath).toEqual({ + routerLink: new URLCombiner(getBitstreamModuleRoute(), bitstream.uuid, 'download').toString(), + queryParams: {}, + }); + }); + }); + }); + + describe('when the user has no download rights but there is a valid access token', () => { + /** + * We expect the download-with-token link to be rendered, since we have a valid request but no normal download rights + */ + beforeEach(waitForAsync(() => { + init(); + authorizationService = { + isAuthorized: (featureId: FeatureID) => { + if (featureId === FeatureID.CanDownload) { + return observableOf(false); + } + return observableOf(true); + }, + } as AuthorizationDataService; + initTestbed(); + })); + beforeEach(() => { + fixture = TestBed.createComponent(ItemSecureFileDownloadLinkComponent); + component = fixture.componentInstance; + component.bitstream = bitstream; + component.item = item; + component.itemRequest = itemRequest; + component.enableRequestACopy = true; + fixture.detectChanges(); + }); + it('should init the component', () => { + expect(component).toBeTruthy(); + }); + it('canDownload$ should return false', () => { + component.canDownload$.subscribe((canDownload) => { + expect(canDownload).toBe(false); + }); + }); + it('canDownloadWithToken$ should return true', () => { + component.canDownloadWithToken$.subscribe((canDownloadWithToken) => { + expect(canDownloadWithToken).toBe(true); + }); + }); + it('canRequestACopy$ should return true', () => { + component.canRequestACopy$.subscribe((canRequestACopy) => { + expect(canRequestACopy).toBe(true); + }); + }); + it('should return the access token path based on the input bitstream', () => { + component.bitstreamPath$.subscribe((accessTokenPath) => { + expect(accessTokenPath).toEqual({ + routerLink: new URLCombiner(getBitstreamModuleRoute(), bitstream.uuid, 'download').toString(), + queryParams: { + accessToken: itemRequest.accessToken, + }, + }); + }); + }); + }); + + describe('when the user has no download rights but has the right to request a copy and there is no valid access token', () => { + /** + * We expect the request-a-copy link to be rendered instead of the normal download link or download-by-token link + */ + beforeEach(waitForAsync(() => { + init(); + authorizationService = { + isAuthorized: (featureId: FeatureID) => { + if (featureId === FeatureID.CanDownload) { + return observableOf(false); + } + return observableOf(true); + }, + } as AuthorizationDataService; + initTestbed(); + })); + beforeEach(() => { + fixture = TestBed.createComponent(ItemSecureFileDownloadLinkComponent); + component = fixture.componentInstance; + component.item = item; + component.bitstream = bitstream; + component.itemRequest = null; + component.enableRequestACopy = true; + fixture.detectChanges(); + }); + it('should init the component', () => { + expect(component).toBeTruthy(); + }); + it('canDownload should be false', () => { + component.canDownload$.subscribe((canDownload) => { + expect(canDownload).toBeFalse(); + }); + }); + it('canDownloadWithToken should be false', () => { + component.canDownloadWithToken$.subscribe((canDownload) => { + expect(canDownload).toBeFalse(); + }); + }); + it('canRequestACopy should be true', () => { + component.canRequestACopy$.subscribe((canRequestACopy) => { + expect(canRequestACopy).toBeTrue(); + }); + }); + it('should return the bitstreamPath based a request-a-copy item + bitstream ID link', () => { + component.bitstreamPath$.subscribe((bitstreamPath) => { + expect(bitstreamPath).toEqual({ + routerLink: new URLCombiner(getItemModuleRoute(), item.uuid, 'request-a-copy').toString(), + queryParams: { bitstream: bitstream.uuid }, + }); + }); + }); + + }); + + describe('when the user has no download rights and no request a copy rights and there is no valid itemRequest', () => { + /** + * We expect a normal download link (which would then be treated as a forbidden and redirect to the login page as per normal) + */ + beforeEach(waitForAsync(() => { + init(); + // This mock will return false for both canDownload and canRequestACopy checks + authorizationService = { + isAuthorized: (featureId: FeatureID) => { + return observableOf(false); + }, + } as AuthorizationDataService; + initTestbed(); + })); + beforeEach(() => { + fixture = TestBed.createComponent(ItemSecureFileDownloadLinkComponent); + component = fixture.componentInstance; + component.bitstream = bitstream; + component.item = item; + component.itemRequest = null; + component.enableRequestACopy = false; + fixture.detectChanges(); + }); + it('should init the component', () => { + expect(component).toBeTruthy(); + }); + it('canDownload$ should be false', () => { + component.canDownload$.subscribe((canDownload) => { + expect(canDownload).toBeFalse(); + }); + }); + it('canDownloadWithToken$ should be false', () => { + component.canDownloadWithToken$.subscribe((canDownloadWithToken) => { + expect(canDownloadWithToken).toBeFalse(); + }); + }); + it('canRequestACopy$ should be false', () => { + component.canRequestACopy$.subscribe((canRequestACopy) => { + expect(canRequestACopy).toBeFalse(); + }); + }); + it('should return the bitstreamPath based on the input bitstream', () => { + component.bitstreamPath$.subscribe((bitstreamPath) => { + expect(bitstreamPath).toEqual({ + routerLink: new URLCombiner(getBitstreamModuleRoute(), bitstream.uuid, 'download').toString(), + queryParams: {}, + }); + }); + }); + }); +}); + diff --git a/src/app/item-page/access-by-token/field-components/file-download-link/item-secure-file-download-link.component.ts b/src/app/item-page/access-by-token/field-components/file-download-link/item-secure-file-download-link.component.ts new file mode 100644 index 0000000000..62b432e68a --- /dev/null +++ b/src/app/item-page/access-by-token/field-components/file-download-link/item-secure-file-download-link.component.ts @@ -0,0 +1,146 @@ +import { + AsyncPipe, + NgClass, + NgIf, + NgTemplateOutlet, +} from '@angular/common'; +import { + Component, + Input, + OnInit, +} from '@angular/core'; +import { RouterLink } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { + combineLatest as observableCombineLatest, + Observable, + of as observableOf, +} from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { + getBitstreamDownloadRoute, + getBitstreamDownloadWithAccessTokenRoute, + getBitstreamRequestACopyRoute, +} from '../../../../app-routing-paths'; +import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from '../../../../core/data/feature-authorization/feature-id'; +import { Bitstream } from '../../../../core/shared/bitstream.model'; +import { Item } from '../../../../core/shared/item.model'; +import { ItemRequest } from '../../../../core/shared/item-request.model'; +import { + hasValue, + isNotEmpty, +} from '../../../../shared/empty.util'; + +@Component({ + selector: 'ds-item-secure-file-download-link', + templateUrl: './item-secure-file-download-link.component.html', + styleUrls: ['./item-secure-file-download-link.component.scss'], + standalone: true, + imports: [ + RouterLink, NgClass, NgIf, NgTemplateOutlet, AsyncPipe, TranslateModule, + ], +}) +/** + * Component displaying a download link + * When the user is authenticated, a short-lived token retrieved from the REST API is added to the download link, + * ensuring the user is authorized to download the file. + */ +export class ItemSecureFileDownloadLinkComponent implements OnInit { + + /** + * Optional bitstream instead of href and file name + */ + @Input() bitstream: Bitstream; + + @Input() item: Item; + + /** + * Additional css classes to apply to link + */ + @Input() cssClasses = ''; + + /** + * A boolean representing if link is shown in same tab or in a new one. + */ + @Input() isBlank = false; + + @Input() itemRequest: ItemRequest; + + @Input() enableRequestACopy = true; + + bitstreamPath$: Observable<{ + routerLink: string, + queryParams: any, + }>; + + // authorized to download normally + canDownload$: Observable; + // authorized to download with token + canDownloadWithToken$: Observable; + // authorized to request a copy + canRequestACopy$: Observable; + + constructor( + private authorizationService: AuthorizationDataService, + ) { + } + + /** + * Initialise component observables to test access rights to a normal bitstream download, a valid token download + * (for a given bitstream), and ability to request a copy of a bitstream. + */ + ngOnInit() { + this.canDownload$ = this.authorizationService.isAuthorized(FeatureID.CanDownload, isNotEmpty(this.bitstream) ? this.bitstream.self : undefined); + this.canDownloadWithToken$ = observableOf(this.itemRequest ? (this.itemRequest.allfiles !== false || this.itemRequest.bitstreamId === this.bitstream.uuid) : false); + this.canRequestACopy$ = this.authorizationService.isAuthorized(FeatureID.CanRequestACopy, isNotEmpty(this.bitstream) ? this.bitstream.self : undefined); + + this.bitstreamPath$ = observableCombineLatest([this.canDownload$, this.canDownloadWithToken$, this.canRequestACopy$]).pipe( + map(([canDownload, canDownloadWithToken, canRequestACopy]) => this.getBitstreamPath(canDownload, canDownloadWithToken, canRequestACopy)), + ); + } + + /** + * Return a path to the bitstream based on what kind of access and authorization the user has, and whether + * they may request a copy + * + * @param canDownload user can download normally + * @param canDownloadWithToken user can download using a token granted by a request approver + * @param canRequestACopy user can request approval to access a copy + */ + getBitstreamPath(canDownload: boolean, canDownloadWithToken, canRequestACopy: boolean) { + // No matter what, if the user can download with their own authZ, allow it + if (canDownload) { + return this.getBitstreamDownloadPath(); + } + // Otherwise, if they access token is valid, use this + if (canDownloadWithToken) { + return this.getAccessByTokenBitstreamPath(this.itemRequest); + } + // If the user can't download, but can request a copy, show the request a copy link + if (!canDownload && canRequestACopy && hasValue(this.item)) { + return getBitstreamRequestACopyRoute(this.item, this.bitstream); + } + // By default, return the plain path + return this.getBitstreamDownloadPath(); + } + + /** + * Resolve special bitstream path which includes access token parameter + * @param itemRequest the item request object + */ + getAccessByTokenBitstreamPath(itemRequest: ItemRequest) { + return getBitstreamDownloadWithAccessTokenRoute(this.bitstream, itemRequest.accessToken); + } + + /** + * Get normal bitstream download path, with no parameters + */ + getBitstreamDownloadPath() { + return { + routerLink: getBitstreamDownloadRoute(this.bitstream), + queryParams: {}, + }; + } +} diff --git a/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.html b/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.html new file mode 100644 index 0000000000..4792f2657d --- /dev/null +++ b/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.html @@ -0,0 +1,87 @@ + +
+
+

{{"item.page.filesection.original.bundle" | translate}}

+ + + +
+
+ +
+
+
+
{{"item.page.filesection.name" | translate}}
+
{{ dsoNameService.getName(file) }}
+ +
{{"item.page.filesection.size" | translate}}
+
{{(file.sizeBytes) | dsFileSize }}
+ + +
{{"item.page.filesection.format" | translate}}
+
{{(file.format | async)?.payload?.description}}
+ + +
{{"item.page.filesection.description" | translate}}
+
{{file.firstMetadataValue("dc.description")}}
+
+
+
+
+ + + + + {{"item.page.filesection.download" | translate}} + +
+
+
+
+
+
+
+

{{"item.page.filesection.license.bundle" | translate}}

+ + + +
+
+ +
+
+
+
{{"item.page.filesection.name" | translate}}
+
{{ dsoNameService.getName(file) }}
+ +
{{"item.page.filesection.size" | translate}}
+
{{(file.sizeBytes) | dsFileSize }}
+ +
{{"item.page.filesection.format" | translate}}
+
{{(file.format | async)?.payload?.description}}
+ + +
{{"item.page.filesection.description" | translate}}
+
{{file.firstMetadataValue("dc.description")}}
+
+
+
+ + {{"item.page.filesection.download" | translate}} + +
+
+
+
+
+
diff --git a/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.scss b/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.scss new file mode 100644 index 0000000000..5384f90cec --- /dev/null +++ b/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.scss @@ -0,0 +1,5 @@ +@media screen and (min-width: map-get($grid-breakpoints, md)) { + dt { + text-align: right; + } +} diff --git a/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.spec.ts b/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.spec.ts new file mode 100644 index 0000000000..557eb06b86 --- /dev/null +++ b/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.spec.ts @@ -0,0 +1,96 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { + ComponentFixture, + TestBed, + waitForAsync, +} from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { provideMockStore } from '@ngrx/store/testing'; +import { + TranslateLoader, + TranslateModule, +} from '@ngx-translate/core'; +import { of as observableOf } from 'rxjs'; +import { APP_CONFIG } from 'src/config/app-config.interface'; +import { environment } from 'src/environments/environment'; + +import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { Bitstream } from '../../../../core/shared/bitstream.model'; +import { ThemedFileDownloadLinkComponent } from '../../../../shared/file-download-link/themed-file-download-link.component'; +import { MetadataFieldWrapperComponent } from '../../../../shared/metadata-field-wrapper/metadata-field-wrapper.component'; +import { MockBitstreamFormat1 } from '../../../../shared/mocks/item.mock'; +import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; +import { NotificationsService } from '../../../../shared/notifications/notifications.service'; +import { PaginationComponent } from '../../../../shared/pagination/pagination.component'; +import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils'; +import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub'; +import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub'; +import { createPaginatedList } from '../../../../shared/testing/utils.test'; +import { FileSizePipe } from '../../../../shared/utils/file-size-pipe'; +import { VarDirective } from '../../../../shared/utils/var.directive'; +import { ThemedThumbnailComponent } from '../../../../thumbnail/themed-thumbnail.component'; +import { ItemSecureFileDownloadLinkComponent } from '../file-download-link/item-secure-file-download-link.component'; +import { ItemSecureFileSectionComponent } from './item-secure-file-section.component'; + +describe('FullFileSectionComponent', () => { + let comp: ItemSecureFileSectionComponent; + let fixture: ComponentFixture; + + const mockBitstream: Bitstream = Object.assign(new Bitstream(), { + sizeBytes: 10201, + content: 'test-content-url', + format: observableOf(MockBitstreamFormat1), + bundleName: 'ORIGINAL', + id: 'test-id', + _links: { + self: { href: 'test-href' }, + content: { href: 'test-content-href' }, + }, + }); + + const bitstreamDataService = jasmine.createSpyObj('bitstreamDataService', { + findAllByItemAndBundleName: createSuccessfulRemoteDataObject$(createPaginatedList([mockBitstream, mockBitstream, mockBitstream])), + }); + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock, + }, + }), + BrowserAnimationsModule, + ItemSecureFileSectionComponent, + VarDirective, + FileSizePipe, + MetadataFieldWrapperComponent, + ], + providers: [ + provideMockStore(), + { provide: BitstreamDataService, useValue: bitstreamDataService }, + { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + { provide: PaginationService, useValue: new PaginationServiceStub() }, + { provide: APP_CONFIG, useValue: environment }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).overrideComponent(ItemSecureFileSectionComponent, { + remove: { imports: [PaginationComponent, MetadataFieldWrapperComponent,ItemSecureFileDownloadLinkComponent, ThemedThumbnailComponent, ThemedFileDownloadLinkComponent] }, + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ItemSecureFileSectionComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + }); + + describe('when the full file section gets loaded with bitstreams available', () => { + it('should contain a list with bitstreams', () => { + const fileSection = fixture.debugElement.queryAll(By.css('.file-section')); + expect(fileSection.length).toEqual(6); + }); + }); +}); diff --git a/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.ts b/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.ts new file mode 100644 index 0000000000..de1602a7f7 --- /dev/null +++ b/src/app/item-page/access-by-token/field-components/file-section/item-secure-file-section.component.ts @@ -0,0 +1,156 @@ +import { CommonModule } from '@angular/common'; +import { + Component, + Inject, + Input, + OnDestroy, + OnInit, +} from '@angular/core'; +import { + TranslateModule, + TranslateService, +} from '@ngx-translate/core'; +import { Observable } from 'rxjs'; +import { + switchMap, + tap, +} from 'rxjs/operators'; +import { + APP_CONFIG, + AppConfig, +} from 'src/config/app-config.interface'; + +import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; +import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; +import { PaginatedList } from '../../../../core/data/paginated-list.model'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { Bitstream } from '../../../../core/shared/bitstream.model'; +import { Item } from '../../../../core/shared/item.model'; +import { ItemRequest } from '../../../../core/shared/item-request.model'; +import { + hasValue, + isEmpty, +} from '../../../../shared/empty.util'; +import { ThemedFileDownloadLinkComponent } from '../../../../shared/file-download-link/themed-file-download-link.component'; +import { ThemedLoadingComponent } from '../../../../shared/loading/themed-loading.component'; +import { MetadataFieldWrapperComponent } from '../../../../shared/metadata-field-wrapper/metadata-field-wrapper.component'; +import { NotificationsService } from '../../../../shared/notifications/notifications.service'; +import { PaginationComponent } from '../../../../shared/pagination/pagination.component'; +import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; +import { FileSizePipe } from '../../../../shared/utils/file-size-pipe'; +import { followLink } from '../../../../shared/utils/follow-link-config.model'; +import { VarDirective } from '../../../../shared/utils/var.directive'; +import { ThemedThumbnailComponent } from '../../../../thumbnail/themed-thumbnail.component'; +import { FileSectionComponent } from '../../../simple/field-components/file-section/file-section.component'; +import { ItemSecureFileDownloadLinkComponent } from '../file-download-link/item-secure-file-download-link.component'; + +/** + * This component renders the file section of the item + * inside a 'ds-metadata-field-wrapper' component. + */ + +@Component({ + selector: 'ds-item-secure-full-file-section', + styleUrls: ['./item-secure-file-section.component.scss'], + templateUrl: './item-secure-file-section.component.html', + standalone: true, + imports: [ + ItemSecureFileDownloadLinkComponent, + CommonModule, + ThemedFileDownloadLinkComponent, + MetadataFieldWrapperComponent, + ThemedLoadingComponent, + TranslateModule, + FileSizePipe, + VarDirective, + PaginationComponent, + ThemedThumbnailComponent, + ], +}) +export class ItemSecureFileSectionComponent extends FileSectionComponent implements OnDestroy, OnInit { + + @Input() item: Item; + @Input() itemRequest: ItemRequest; + + label: string; + + originals$: Observable>>; + licenses$: Observable>>; + + originalOptions = Object.assign(new PaginationComponentOptions(), { + id: 'obo', + currentPage: 1, + pageSize: this.appConfig.item.bitstream.pageSize, + }); + + licenseOptions = Object.assign(new PaginationComponentOptions(), { + id: 'lbo', + currentPage: 1, + pageSize: this.appConfig.item.bitstream.pageSize, + }); + + constructor( + bitstreamDataService: BitstreamDataService, + protected notificationsService: NotificationsService, + protected translateService: TranslateService, + protected paginationService: PaginationService, + public dsoNameService: DSONameService, + @Inject(APP_CONFIG) protected appConfig: AppConfig, + ) { + super(bitstreamDataService, notificationsService, translateService, dsoNameService, appConfig); + } + + ngOnInit(): void { + this.initialize(); + } + + initialize(): void { + this.originals$ = this.paginationService.getCurrentPagination(this.originalOptions.id, this.originalOptions).pipe( + switchMap((options: PaginationComponentOptions) => this.bitstreamDataService.findAllByItemAndBundleName( + this.item, + 'ORIGINAL', + { elementsPerPage: options.pageSize, currentPage: options.currentPage }, + true, + true, + followLink('format'), + followLink('thumbnail'), + )), + tap((rd: RemoteData>) => { + if (hasValue(rd.errorMessage)) { + this.notificationsService.error(this.translateService.get('file-section.error.header'), `${rd.statusCode} ${rd.errorMessage}`); + } + }, + ), + ); + + this.licenses$ = this.paginationService.getCurrentPagination(this.licenseOptions.id, this.licenseOptions).pipe( + switchMap((options: PaginationComponentOptions) => this.bitstreamDataService.findAllByItemAndBundleName( + this.item, + 'LICENSE', + { elementsPerPage: options.pageSize, currentPage: options.currentPage }, + true, + true, + followLink('format'), + followLink('thumbnail'), + )), + tap((rd: RemoteData>) => { + if (hasValue(rd.errorMessage)) { + this.notificationsService.error(this.translateService.get('file-section.error.header'), `${rd.statusCode} ${rd.errorMessage}`); + } + }, + ), + ); + + } + + hasValuesInBundle(bundle: PaginatedList) { + return hasValue(bundle) && !isEmpty(bundle.page); + } + + ngOnDestroy(): void { + this.paginationService.clearPagination(this.originalOptions.id); + this.paginationService.clearPagination(this.licenseOptions.id); + } + +}