/* * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den * Ministerpräsidenten des Landes Schleswig-Holstein * Staatskanzlei * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung * * Lizenziert unter der EUPL, Version 1.2 oder - sobald * diese von der Europäischen Kommission genehmigt wurden - * Folgeversionen der EUPL ("Lizenz"); * Sie dürfen dieses Werk ausschließlich gemäß * dieser Lizenz nutzen. * Eine Kopie der Lizenz finden Sie hier: * * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * * Sofern nicht durch anwendbare Rechtsvorschriften * gefordert oder in schriftlicher Form vereinbart, wird * die unter der Lizenz verbreitete Software "so wie sie * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - * ausdrücklich oder stillschweigend - verbreitet. * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ import { BlobWithFileName, createEmptyStateResource, createErrorStateResource, createStateResource, EMPTY_ARRAY, EMPTY_STRING, getMessageForInvalidParam, HttpError, HttpHeader, isNotNil, isUnprocessableEntity, isValidationFieldFileSizeExceedError, sanitizeFileName, StateResource, } from '@alfa-client/tech-shared'; import { SnackBarService } from '@alfa-client/ui'; import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { faker } from '@faker-js/faker'; import { getUrl, Resource, ResourceUri } from '@ngxp/rest'; import { saveAs } from 'file-saver'; import { isNil } from 'lodash-es'; import { BehaviorSubject, forkJoin, Observable, of, throwError } from 'rxjs'; import { catchError, map, mergeMap, startWith, switchMap } from 'rxjs/operators'; import { BinaryFileListResource, BinaryFileResource, FileUploadType, ToUploadFile, UploadFile, UploadFilesByType, } from './binary-file.model'; import { BinaryFileRepository } from './binary-file.repository'; @Injectable({ providedIn: 'root' }) export class BinaryFileService { uploadFiles: BehaviorSubject<UploadFilesByType> = new BehaviorSubject({}); constructor( private repository: BinaryFileRepository, private snackbarService: SnackBarService, ) {} public isUploadInProgress(type: FileUploadType): Observable<boolean> { return this.uploadFiles.asObservable().pipe( map((files: UploadFilesByType) => files[type].map((file: UploadFile) => file.uploadedFile)), switchMap((files: Observable<StateResource<BinaryFileResource>>[]) => forkJoin(files)), map((files: StateResource<BinaryFileResource>[]) => files.some((s) => s.loading)), ); } public getUploadedFiles(type: FileUploadType): Observable<UploadFile[]> { return this.uploadFiles.asObservable().pipe(map((files: UploadFilesByType) => files[type])); } public deleteUploadedFile(type: FileUploadType, key: string): void { //Implement me } public clearUploadedFiles(type: FileUploadType): void { //Implement me } //TODO Rename public uploadFileNew(toUploadFile: ToUploadFile): void { if (!(toUploadFile.uploadUrl in this.uploadFiles.value)) this.uploadFiles.value[toUploadFile.type] = EMPTY_ARRAY; this.uploadFiles.value[toUploadFile.type].push(this._buildUploadFile(toUploadFile)); } _buildUploadFile(toUploadFile: ToUploadFile): UploadFile { return { key: faker.string.uuid(), fileToUpload: toUploadFile.file, uploadedFile: this._handleUpload(toUploadFile), }; } _handleUpload(toUploadFile: ToUploadFile): Observable<StateResource<BinaryFileResource>> { return this.repository.uploadFileNew(toUploadFile.uploadUrl, toUploadFile.file).pipe( map((binaryFileResource: BinaryFileResource) => createStateResource(binaryFileResource)), catchError((response: any) => of(createErrorStateResource(<HttpError>response.error))), startWith(createEmptyStateResource<BinaryFileResource>(true)), ); } //TODO Rename to uploadFileOld OR refactor all use cases to uploadFileNew public uploadFile( resource: Resource, linkRel: string, file: File, showValidationErrorSnackBar: boolean = true, ): Observable<StateResource<BinaryFileResource>> { return this.repository.uploadFile(getUrl(resource, linkRel), file).pipe( mergeMap((response: HttpResponse<Object>) => this.getFile(response.headers.get(HttpHeader.LOCATION))), catchError((errorResponse) => this.handleError(errorResponse.error, showValidationErrorSnackBar)), startWith(createEmptyStateResource<BinaryFileResource>(true)), ); } private handleError(errorResponse: HttpErrorResponse, showValidationErrorSnackBar: boolean): Observable<StateResource<any>> { return of(this.handleErrorByStatus(errorResponse, showValidationErrorSnackBar)); } handleErrorByStatus(error: HttpErrorResponse, showValidationErrorSnackBar: boolean): StateResource<any> { if (isUnprocessableEntity(error.status)) { this.handleSnackBar(error, showValidationErrorSnackBar); return createErrorStateResource(error.error); } throwError({ error }); } handleSnackBar(error: HttpErrorResponse, showValidationErrorSnackBar: boolean) { if (showValidationErrorSnackBar && isValidationFieldFileSizeExceedError(error.error)) { this.snackbarService.showError(getMessageForInvalidParam(EMPTY_STRING, error.error.invalidParams[0])); } } public downloadFile(file: BinaryFileResource, fileNamePrefix: string): Observable<StateResource<Blob>> { return this.repository.download(file).pipe( map((data) => this.saveBinaryFile(data, file, fileNamePrefix)), startWith(createEmptyStateResource<Blob>(true)), catchError(() => this.handleDownloadError()), ); } handleDownloadError(): Observable<StateResource<Blob>> { this.snackbarService.showError('Die Datei konnte nicht heruntergeladen werden.'); return of(createEmptyStateResource<Blob>()); } saveBinaryFile(data: any, file: BinaryFileResource, fileNamePrefix: string): StateResource<Blob> { if (isNil(data)) { return createEmptyStateResource(true); } this.save(data, this.buildFileName(file, fileNamePrefix)); return createStateResource(data); } private buildFileName(file: BinaryFileResource, fileNamePrefix: string): string { if (isNotNil(fileNamePrefix)) { return sanitizeFileName(`${fileNamePrefix}_${file.name}`); } return file.name; } public downloadArchive(uri: ResourceUri): Observable<StateResource<Blob>> { return this.repository.downloadArchive(uri).pipe( map((data: BlobWithFileName) => this.saveData(data)), startWith(createEmptyStateResource<Blob>(true)), ); } saveData(dataWithFileName: BlobWithFileName): StateResource<Blob> { const data: Blob = dataWithFileName.blob; if (isNil(data)) { return createEmptyStateResource(true); } this.save(data, dataWithFileName.fileName); return createStateResource(data); } save(data: any, fileName: string): void { saveAs(data, fileName); } public getFile(uri: ResourceUri): Observable<StateResource<BinaryFileResource>> { return this.repository.getFile(uri).pipe( map((fileList: BinaryFileResource) => createStateResource(fileList)), startWith(createEmptyStateResource<BinaryFileResource>(true)), ); } public getFiles(resource: Resource, linkRel: string): Observable<StateResource<BinaryFileListResource>> { return this.repository.getFiles(resource, linkRel).pipe( map((fileList: BinaryFileListResource) => createStateResource(fileList)), startWith(createEmptyStateResource<BinaryFileListResource>(true)), ); } }