import { Injectable } from '@angular/core'; import { ApplicationFileUploadResponse, CompleteFileUploadEvent, FileStatus, FileUploadEvent } from '@core/typings/file.typing'; import { environment } from '@environment'; import { AddressRequestsResources } from '@features/platform-admin/address-requests/address-requests.resources'; import { FileService } from '@yourcause/common'; import { I18nService } from '@yourcause/common/i18n'; import { LogService } from '@yourcause/common/logging'; import { NotifierService } from '@yourcause/common/notifier'; import { catchError, filter, map, Observable, of, take } from 'rxjs'; import { HttpRestService } from './http-rest.service'; import { PortalDeterminationService } from './portal-determination.service'; interface FileUrlParams { applicationId: number; applicationFormId: number; fileId: number; fileName: string; } @Injectable({ providedIn: 'root' }) export class ApplicationFileService { constructor ( private logger: LogService, private portal: PortalDeterminationService, private http: HttpRestService, private fileService: FileService, private notifier: NotifierService, private i18n: I18nService, private addressRequestResources: AddressRequestsResources ) { } extractQueryParamsFromFileUrl ( url: string ): FileUrlParams { const splitArray = url.split('?'); if (splitArray[1]) { return splitArray[1].split('&').map(c => { return c.split('='); }).reduce((acc, [key, value]) => { return { ...acc, [key]: decodeURIComponent(value) }; }, {} as FileUrlParams); } return null; } private doUploadFile ( applicationId: number, applicationFormId: number, file: File, fileName: string, refFieldId: number ) { const forApplicant = this.portal.isApply; let endpoint = forApplicant ? `/api/portal/applications/${applicationId}/ApplicationForms/${applicationFormId}/Files` : `/api/manager/applications/${applicationId}/ApplicationForms/${applicationFormId}/Files`; if (!!refFieldId) { endpoint = `${endpoint}?referenceFieldId=${refFieldId}`; } const result = this.http.postFileWithProgress( endpoint, file, undefined, fileName ); return result.pipe(map((event) => { if (event.status === FileStatus.COMPLETE) { const fileUrl = this.convertParamsToApplicationFileUrl( applicationId, applicationFormId, event.result, fileName ); return { status: FileStatus.COMPLETE, result: fileUrl } as CompleteFileUploadEvent; } return event; })); } convertParamsToApplicationFileUrl ( applicationId: number, applicationFormId: number, fileId: number, fileName: string ) { const forApplicant = this.portal.isApply; const locationBase = environment.isLocalhost ? 'yourcausegrantsqa.com' : location.hostname; const queryParams = { applicationId, applicationFormId, fileId, fileName }; const query = Object.keys(queryParams) .map(key => key as keyof typeof queryParams) .map((key: keyof typeof queryParams) => `${key}=${encodeURIComponent(queryParams[key])}`) .join('&'); return `https://${locationBase}/${forApplicant ? 'apply' : 'management'}/download-file?${query}`; } uploadFile ( applicationId: number, applicationFormId: number, file: File, fileName: string, refFieldID: number ) { return this.doUploadFile( applicationId, applicationFormId, file, fileName, refFieldID ).pipe(filter(e => { return e.status === FileStatus.COMPLETE; })) .pipe(map(e => { return (e as CompleteFileUploadEvent).result; })) .pipe(take(1)) .toPromise(); } uploadFileWithProgress ( applicationId: number, applicationFormId: number, file: File, fileName: string, refFieldID: number ): Observable> { return this.doUploadFile( applicationId, applicationFormId, file, fileName, refFieldID ).pipe(catchError(e => { this.logger.error(e); this.notifier.error(this.i18n.translate( 'historical:textErrorUploadingFile', {}, 'There was an error uploading your file' )); return of({ status: FileStatus.FAILED_API } as FileUploadEvent); })) .pipe(filter(e => e !== null)); } breakDownloadUrlDownToObject (url: string) { if (url) { const applicableInfo = url.split('?')[1]; if (applicableInfo) { const info = applicableInfo.split('&') .map(keyValueString => keyValueString.split('=')) .reduce((acc, keyValue) => ({ ...acc, [keyValue[0]]: keyValue[1] }), { applicationId: '', applicationFormId: '', fileId: '', fileName: '' }); let fileName = info.fileName; try { const decoded = decodeURIComponent(info.fileName); fileName = decoded; } catch (e) { } return { ...info, fileName }; } } return null; } async openFile ( applicationId: number, fileId: number, applicationFormId?: number ) { const blob = await this.getFileFromFileInfo(applicationFormId, applicationId, fileId); const blobUrl = this.fileService.convertFileToUrl(blob); window.open(blobUrl, '_blank'); } async downloadFile ( applicationId: number, fileId: number, fileName: string, applicationFormId?: number ) { const blob = await this.getFileFromFileInfo(applicationFormId, applicationId, fileId); this.fileService.saveAs(blob, fileName); } async getFileFromFileInfo ( applicationFormId: number, applicationId: number, fileId: number ) { let url: string; if (applicationFormId) { const { accessUrl } = await this.getFile( applicationId, applicationFormId, fileId ); url = accessUrl; } else { // Only scenario is address request files const { accessUrl } = await this.addressRequestResources.getManagerUrlForAddressRequestFile( applicationId, fileId ); url = accessUrl; } const blob = await this.fileService.getBlob(url) as File; return blob; } private getFileApplicant ( applicationId: number, applicationFormId: number, applicationFormFileUploadId: number ) { return `/api/portal/applications/${applicationId}/ApplicationForms/${applicationFormId}/Files/${applicationFormFileUploadId}`; } private getFileManager ( applicationId: number, applicationFormId: number, applicationFormFileUploadId: number ) { return `/api/manager/applications/${applicationId}/ApplicationForms/${applicationFormId}/Files/${applicationFormFileUploadId}`; } async getFile ( applicationId: number, applicationFormId: number, applicationFormFileUploadId: number ) { return this.http.get( this.getFileUrl(applicationId, applicationFormId, applicationFormFileUploadId) ); } private getFileUrl ( applicationId: number, applicationFormId: number, applicationFormFileUploadId: number ): string { const func = this.portal.isApply ? 'getFileApplicant' : 'getFileManager'; return this[func]( applicationId, applicationFormId, applicationFormFileUploadId ); } }