add file storage to ui

This commit is contained in:
amentis 2024-04-10 17:38:39 +03:00
parent 906cee7621
commit cf5333c788
9 changed files with 179 additions and 75 deletions

View File

@ -497,6 +497,9 @@ public class DescriptionServiceImpl implements DescriptionService {
StorageFile storageFile = this.storageFileService.copyToStorage(fileId, StorageType.Main, true, new BaseFieldSet().ensure(StorageFile._id));
this.storageFileService.updatePurgeAt(storageFile.getId(), null);
}
if (this.conventionService.isValidUUID(data.getTextValue()) && !UUID.fromString(data.getTextValue()).equals(fileId)) {
this.storageFileService.updatePurgeAt(UUID.fromString(data.getTextValue()), Instant.now().minusSeconds(60));
}
data.setTextValue(fileId == null ? null : fileId.toString());
} else {
data.setTextValue(persist.getTextValue());

View File

@ -86,7 +86,7 @@ public class StorageFileServiceImpl implements StorageFileService {
StorageFileEntity storageFile = this.buildDataEntry(model);
File file = new File(this.filePath(storageFile.getFileRef(), storageFile.getStorageType()));
if (!file.exists()) throw new FileAlreadyExistsException(storageFile.getName());
if (file.exists()) throw new FileAlreadyExistsException(storageFile.getName());
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(payload);

View File

@ -71,6 +71,23 @@ public class StorageFileController {
this.validatorFactory = validatorFactory;
}
@GetMapping("/name/{id}")
public String getName(@PathVariable("id") UUID id) throws MyApplicationException, MyForbiddenException, MyNotFoundException {
logger.debug(new MapLogEntry("get name" ).And("id", id));
this.authorizationService.authorizeForce(Permission.BrowseStorageFile);
StorageFileEntity storageFile = this.queryFactory.query(StorageFileQuery.class).ids(id).firstAs(new BaseFieldSet().ensure(StorageFile._fullName));
if (storageFile == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{id, StorageFile.class.getSimpleName()}, LocaleContextHolder.getLocale()));
this.auditService.track(AuditableAction.StorageFile_Download, Map.ofEntries(
new AbstractMap.SimpleEntry<String, Object>("id", id)
));
return storageFile.getName();
}
@PostMapping("upload-temp-files")
@Transactional
@ -82,9 +99,9 @@ public class StorageFileController {
List<StorageFile> addedFiles = new ArrayList<>();
for (MultipartFile file : files) {
StorageFilePersist storageFilePersist = new StorageFilePersist();
storageFilePersist.setName(FilenameUtils.removeExtension(file.getName()));
storageFilePersist.setExtension(FilenameUtils.getExtension(file.getName()));
storageFilePersist.setMimeType(URLConnection.guessContentTypeFromName(file.getName()));
storageFilePersist.setName(FilenameUtils.removeExtension(file.getOriginalFilename()));
storageFilePersist.setExtension(FilenameUtils.getExtension(file.getOriginalFilename()));
storageFilePersist.setMimeType(URLConnection.guessContentTypeFromName(file.getOriginalFilename()));
storageFilePersist.setOwnerId(this.userScope.getUserIdSafe());
storageFilePersist.setStorageType(StorageType.Temp);
storageFilePersist.setLifetime(Duration.ofSeconds(this.config.getTempStoreLifetimeSeconds()));
@ -104,7 +121,7 @@ public class StorageFileController {
this.authorizationService.authorizeForce(Permission.BrowseStorageFile);
StorageFileEntity storageFile = this.queryFactory.query(StorageFileQuery.class).ids(id).firstAs(new BaseFieldSet().ensure(StorageFile._createdAt, StorageFile._fullName, StorageFile._mimeType));
StorageFileEntity storageFile = this.queryFactory.query(StorageFileQuery.class).ids(id).firstAs(new BaseFieldSet().ensure(StorageFile._createdAt, StorageFile._fullName, StorageFile._mimeType, StorageFile._extension));
if (storageFile == null) throw new MyNotFoundException(messageSource.getMessage("General_ItemNotFound", new Object[]{id, StorageFile.class.getSimpleName()}, LocaleContextHolder.getLocale()));
byte[] file = this.storageFileService.readAsBytesSafe(id);

View File

@ -48,6 +48,7 @@ import { NotificationService } from './services/notification/notification-servic
import { SemanticsService } from './services/semantic/semantics.service';
import { PrefillingSourceService } from './services/prefilling-source/prefilling-source.service';
import { VisibilityRulesService } from '@app/ui/description/editor/description-form/visibility-rules/visibility-rules.service';
import { StorageFileService } from './services/storage-file/storage-file.service';
//
//
// This is shared module that provides all the services. Its imported only once on the AppModule.
@ -114,7 +115,8 @@ export class CoreServiceModule {
NotificationService,
SemanticsService,
PrefillingSourceService,
VisibilityRulesService
VisibilityRulesService,
StorageFileService
],
};
}

View File

@ -0,0 +1,13 @@
import { BaseEntity } from "@common/base/base-entity.model";
import { User } from "../user/user";
export interface StorageFile extends BaseEntity {
fileRef: string;
name: string;
fullName: string;
extension: string;
mimeType: string;
purgeAt: Date;
purgedAt: Date;
owner: User
}

View File

@ -0,0 +1,56 @@
import { HttpClient, HttpHeaders, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { DescriptionTemplate } from '@app/core/model/description-template/description-template';
import { BaseHttpParams } from '@common/http/base-http-params';
import { InterceptorType } from '@common/http/interceptors/interceptor-type';
import { FilterService } from '@common/modules/text-filter/filter-service';
import { Guid } from '@common/types/guid';
import { Observable, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import { ConfigurationService } from '../configuration/configuration.service';
import { BaseHttpV2Service } from '../http/base-http-v2.service';
import { StorageFile } from '@app/core/model/storage-file/storage-file';
@Injectable()
export class StorageFileService {
private headers = new HttpHeaders();
constructor(private http: BaseHttpV2Service, private httpClient: HttpClient, private configurationService: ConfigurationService, private filterService: FilterService) {
}
private get apiBase(): string { return `${this.configurationService.server}storage-file`; }
getName(id: Guid ): Observable<string> {
const url = `${this.apiBase}/name/${id}`;
return this.http
.get<string>(url).pipe(
catchError((error: any) => throwError(error)));
}
download(id: Guid ): Observable<HttpResponse<Blob>> {
const url = `${this.apiBase}/${id}`;
return this.http
.get<HttpResponse<Blob>>(url).pipe(
catchError((error: any) => throwError(error)));
}
uploadTempFiles(item: File): Observable<StorageFile[]> {
const url = `${this.apiBase}/upload-temp-files`;
const formData: FormData = new FormData();
formData.append('files', item);
const params = new BaseHttpParams();
params.interceptorContext = {
excludedInterceptors: [InterceptorType.JSONContentType]
};
return this.http
.post<StorageFile[]>(url, formData, { params: params }).pipe(
catchError((error: any) => throwError(error)));
}
}

View File

@ -132,6 +132,7 @@ export class DescriptionEditorResolver extends BaseEditorResolver {
(prefix ? prefix + '.' : '') + [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.pages), nameof<DescriptionTemplatePage>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.fields), nameof<DescriptionTemplateField>(x => x.data), nameof<DescriptionTemplateExternalDatasetData>(x => x.type)].join('.'),
(prefix ? prefix + '.' : '') + [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.pages), nameof<DescriptionTemplatePage>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.fields), nameof<DescriptionTemplateField>(x => x.data), nameof<DescriptionTemplateUploadData>(x => x.maxFileSizeInMB)].join('.'),
(prefix ? prefix + '.' : '') + [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.pages), nameof<DescriptionTemplatePage>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.fields), nameof<DescriptionTemplateField>(x => x.data), nameof<DescriptionTemplateUploadData>(x => x.types), nameof<DescriptionTemplateUploadOption>(x => x.label)].join('.'),
(prefix ? prefix + '.' : '') + [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.pages), nameof<DescriptionTemplatePage>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.fields), nameof<DescriptionTemplateField>(x => x.data), nameof<DescriptionTemplateUploadData>(x => x.types), nameof<DescriptionTemplateUploadOption>(x => x.value)].join('.'),
(prefix ? prefix + '.' : '') + [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.pages), nameof<DescriptionTemplatePage>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.fields), nameof<DescriptionTemplateField>(x => x.data), nameof<DescriptionTemplateReferenceTypeData>(x => x.referenceType), nameof<ReferenceType>(x => x.id)].join('.'),
(prefix ? prefix + '.' : '') + [nameof<DescriptionTemplate>(x => x.definition), nameof<DescriptionTemplateDefinition>(x => x.pages), nameof<DescriptionTemplatePage>(x => x.sections), nameof<DescriptionTemplateSection>(x => x.fieldSets), nameof<DescriptionTemplateFieldSet>(x => x.fields), nameof<DescriptionTemplateField>(x => x.data), nameof<DescriptionTemplateReferenceTypeData>(x => x.referenceType), nameof<ReferenceType>(x => x.name)].join('.'),
]

View File

@ -108,8 +108,8 @@
<ng-container *ngSwitchCase="descriptionTemplateFieldTypeEnum.UPLOAD">
<div class="col-12 d-flex justify-content-center">
<ngx-dropzone #drop class="drop-file col-12" (change)="fileChangeEvent($event, true)" [multiple]="false" [accept]="typesToString()" [disabled]="propertiesFormGroup?.get(field.id).get('textValue').disabled">
<ngx-dropzone-preview *ngIf="propertiesFormGroup?.get(field.id).get('textValue').value && propertiesFormGroup?.get(field.id).get('textValue').value.name" class="file-preview" [removable]="true" (removed)="onRemove()">
<ngx-dropzone-label class="file-label">{{ propertiesFormGroup?.get(field.id).get('textValue').value.name }}</ngx-dropzone-label>
<ngx-dropzone-preview *ngIf="propertiesFormGroup?.get(field.id).get('textValue').value && fileNameDisplay" class="file-preview" [removable]="true" (removed)="onRemove()">
<ngx-dropzone-label class="file-label">{{ fileNameDisplay}}</ngx-dropzone-label>
</ngx-dropzone-preview>
</ngx-dropzone>
</div>

View File

@ -9,12 +9,16 @@ import { DescriptionTemplateField, DescriptionTemplateFieldSet, DescriptionTempl
import { DmpService } from '@app/core/services/dmp/dmp.service';
import { SnackBarNotificationLevel, UiNotificationService } from "@app/core/services/notification/ui-notification-service";
import { ReferenceService } from '@app/core/services/reference/reference.service';
import { StorageFileService } from '@app/core/services/storage-file/storage-file.service';
import { FileUtils } from '@app/core/services/utilities/file-utils.service';
import { MultipleAutoCompleteConfiguration } from '@app/library/auto-complete/multiple/multiple-auto-complete-configuration';
import { SingleAutoCompleteConfiguration } from '@app/library/auto-complete/single/single-auto-complete-configuration';
import { VisibilityRulesService } from '@app/ui/description/editor/description-form/visibility-rules/visibility-rules.service';
import { BaseComponent } from '@common/base/base.component';
import { FormValidationErrorsDialogComponent } from '@common/forms/form-validation-errors-dialog/form-validation-errors-dialog.component';
import { Guid } from '@common/types/guid';
import { TranslateService } from '@ngx-translate/core';
import * as FileSaver from 'file-saver';
import { Observable } from 'rxjs';
import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators';
@ -66,6 +70,7 @@ export class DescriptionFormFieldComponent extends BaseComponent implements OnIn
];
filesToUpload: FileList;
fileNameDisplay: string;
constructor(
private language: TranslateService,
@ -74,7 +79,8 @@ export class DescriptionFormFieldComponent extends BaseComponent implements OnIn
private uiNotificationService: UiNotificationService,
public dialog: MatDialog,
private fileUtils: FileUtils,
private referenceService: ReferenceService
private referenceService: ReferenceService,
private storageFileService: StorageFileService
) {
super();
}
@ -86,6 +92,8 @@ export class DescriptionFormFieldComponent extends BaseComponent implements OnIn
ngOnInit() {
if(this.field?.data?.fieldType == DescriptionTemplateFieldType.UPLOAD && this.field && this.field.id && (this.propertiesFormGroup?.get(this.field.id).get('textValue').value != undefined)) this.getUploadFileName();
this.isRequired = this.field.validations?.includes(DescriptionTemplateFieldValidationType.Required);
switch (this.field?.data?.fieldType) {
@ -267,15 +275,28 @@ export class DescriptionFormFieldComponent extends BaseComponent implements OnIn
}
private getUploadFileName(){
const id = Guid.parse((this.propertiesFormGroup?.get(this.field.id).get('textValue').value as string));
this.storageFileService.getName(id)
.pipe(takeUntil(this._destroyed)).subscribe((name) => {
this.fileNameDisplay = name;
}, error => {
this.fileNameDisplay = null;
})
}
public upload() {
//TODO: refactor this
// this.fileService.upload(this.filesToUpload[0], this.datasetProfileId.id, this.form.value.id).subscribe((fileId: string) => {
// this.form.get("value").patchValue(
// { "name": this.filesToUpload[0].name, "id": fileId + "", "type": this.filesToUpload[0].type });
// this.cdr.detectChanges();
// }, error => {
// this.onCallbackUploadFail(error.error);
// })
this.storageFileService.uploadTempFiles(this.filesToUpload[0])
.pipe(takeUntil(this._destroyed)).subscribe((response) => {
this.propertiesFormGroup?.get(this.field.id).get('textValue').patchValue(response[0].id.toString());
this.fileNameDisplay = response[0].name;
this.cdr.detectChanges();
}, error => {
this.onCallbackUploadFail(error.error);
})
}
@ -285,66 +306,53 @@ export class DescriptionFormFieldComponent extends BaseComponent implements OnIn
}
fileChangeEvent(fileInput: any, dropped: boolean = false) {
//TODO: refactor this
// if (this.form.value.value) {
// this.onRemove(false);
// }
// if (dropped) {
// this.filesToUpload = fileInput.addedFiles;
// } else {
// this.filesToUpload = fileInput.target.files;
// }
if (dropped) {
this.filesToUpload = fileInput.addedFiles;
} else {
this.filesToUpload = fileInput.target.files;
}
// let messages: string[] = [];
// if (this.filesToUpload.length == 0) {
// messages.push(this.language.instant('DATASET-WIZARD.MESSAGES.NO-FILES-SELECTED'));
// return;
// } else {
// let fileToUpload = this.filesToUpload[0];
// if (this.form.get("data") && this.form.get("data").value.types
// && this.form.get("data").value.types.map(type => type.value).includes(fileToUpload.type)
// && this.form.get("data").value.maxFileSizeInMB
// && this.form.get("data").value.maxFileSizeInMB * 1048576 >= fileToUpload.size) {
// this.upload();
// } else {
// this.filesToUpload = null;
// messages.push(this.language.instant('DATASET-WIZARD.MESSAGES.LARGE-FILE-OR-UNACCEPTED-TYPE'));
// messages.push(this.language.instant('DATASET-WIZARD.MESSAGES.MAX-FILE-SIZE', { 'maxfilesize': this.form.get("data").value.maxFileSizeInMB }));
// messages.push(this.language.instant('DATASET-WIZARD.MESSAGES.ACCEPTED-FILE-TRANSFOMER') + this.form.get("data").value.types.map(type => type.value).join(", "));
// }
let messages: string[] = [];
if (this.filesToUpload.length == 0) {
messages.push(this.language.instant('DATASET-WIZARD.MESSAGES.NO-FILES-SELECTED'));
return;
} else {
let fileToUpload = this.filesToUpload[0];
const data = this.field.data as DescriptionTemplateUploadData;
if (data && data.types
&& data.types.map(type => type.value).includes(fileToUpload.type)
&& data.maxFileSizeInMB
&& data.maxFileSizeInMB * 1048576 >= fileToUpload.size) {
this.upload();
} else {
this.filesToUpload = null;
messages.push(this.language.instant('DATASET-WIZARD.MESSAGES.LARGE-FILE-OR-UNACCEPTED-TYPE'));
messages.push(this.language.instant('DATASET-WIZARD.MESSAGES.MAX-FILE-SIZE', { 'maxfilesize': data.maxFileSizeInMB }));
messages.push(this.language.instant('DATASET-WIZARD.MESSAGES.ACCEPTED-FILE-TRANSFOMER') + data.types.map(type => type.value).join(", "));
}
// if (messages && messages.length > 0) {
// this.dialog.open(FormValidationErrorsDialogComponent, {
// data: {
// errorMessages: messages
// }
// })
// }
// }
if (messages && messages.length > 0) {
this.dialog.open(FormValidationErrorsDialogComponent, {
data: {
errorMessages: messages
}
})
}
}
}
onRemove(makeFilesNull: boolean = true) {
//TODO: refactor this
// delete from tmp folder - subscribe call
// this.fileService.deleteFromTempFolder(this.form.value.value.id).subscribe(res => {
// if (makeFilesNull) {
// this.makeFilesNull();
// }
// this.cdr.detectChanges();
// }, error => {
// if (makeFilesNull) {
// this.makeFilesNull();
// }
// })
this.makeFilesNull()
this.cdr.detectChanges();
}
makeFilesNull() {
//TODO: refactor this
// this.filesToUpload = null;
// this.form.value.value = null;
// this.form.get("value").patchValue(null);
this.filesToUpload = null;
this.propertiesFormGroup?.get(this.field.id).get('textValue').patchValue(null);
this.fileNameDisplay = null;
}
typesToString() {
@ -352,14 +360,18 @@ export class DescriptionFormFieldComponent extends BaseComponent implements OnIn
}
download(): void {
//TODO: refactor this
// this.fileService.download(this.form.value.value.id)
// .pipe(takeUntil(this._destroyed))
// .subscribe(response => {
// const blob = new Blob([response.body], { type: this.form.value.value.type });
// const filename = this.fileUtils.getFilenameFromContentDispositionHeader(response.headers.get('Content-Disposition'));
// FileSaver.saveAs(blob, filename);
// });
if (this.propertiesFormGroup?.get(this.field.id).get('textValue').value) {
const id = Guid.parse((this.propertiesFormGroup?.get(this.field.id).get('textValue').value as string));
this.storageFileService.download(id).subscribe((response) => {
const blob = new Blob([response.body]);
const filename = this.fileUtils.getFilenameFromContentDispositionHeader(response.headers.get('Content-Disposition'));
FileSaver.saveAs(blob, filename);
}, error => {
this.onCallbackUploadFail(error.error);
})
}
}
}