From 768dad57e3799fd9125a454d91898a60def4b461 Mon Sep 17 00:00:00 2001 From: MiraGeowerkstatt Date: Mon, 20 Jan 2025 16:01:29 +0100 Subject: [PATCH 1/2] Refactor BoreholeFileCloudService.cs --- src/api/BoreholeFileCloudService.cs | 32 ++++++++++++------- src/api/Controllers/BoreholeFileController.cs | 2 +- src/api/Controllers/ImportController.cs | 31 ++++-------------- tests/api/BoreholeFileCloudServiceTest.cs | 12 +++---- .../Controllers/BoreholeFileControllerTest.cs | 8 ++--- tests/api/Controllers/ExportControllerTest.cs | 3 +- 6 files changed, 39 insertions(+), 49 deletions(-) diff --git a/src/api/BoreholeFileCloudService.cs b/src/api/BoreholeFileCloudService.cs index d7fea3e22..95330bc4b 100644 --- a/src/api/BoreholeFileCloudService.cs +++ b/src/api/BoreholeFileCloudService.cs @@ -31,9 +31,11 @@ public BoreholeFileCloudService(BdmsContext context, IConfiguration configuratio /// /// Uploads a file to the cloud storage and links it to the borehole. /// - /// The file to upload and link to the . - /// The to link the uploaded to. - public async Task UploadFileAndLinkToBorehole(IFormFile formFile, int boreholeId) + /// The file stream for the file to upload and link to the . + /// The name of the file to upload. + /// The content type of the file. + /// The to link the uploaded file to. + public async Task UploadFileAndLinkToBoreholeAsync(Stream fileStream, string fileName, string contentType, int boreholeId) { // Use transaction to ensure data is only stored to db if the file upload was sucessful. Only create a transaction if there is not already one from the calling method. using var transaction = context.Database.CurrentTransaction == null ? await context.Database.BeginTransactionAsync().ConfigureAwait(false) : null; @@ -49,10 +51,10 @@ public async Task UploadFileAndLinkToBorehole(IFormFile formFile, if (user == null || subjectId == null) throw new InvalidOperationException($"No user with subject_id <{subjectId}> found."); // Register the new file in the boreholes database. - var fileExtension = Path.GetExtension(formFile.FileName); + var fileExtension = Path.GetExtension(fileName); var fileNameGuid = $"{Guid.NewGuid()}{fileExtension}"; - var file = new Models.File { Name = formFile.FileName, NameUuid = fileNameGuid, Type = formFile.ContentType }; + var file = new Models.File { Name = fileName, NameUuid = fileNameGuid, Type = contentType }; await context.Files.AddAsync(file).ConfigureAwait(false); await context.UpdateChangeInformationAndSaveChangesAsync(httpContextAccessor.HttpContext!).ConfigureAwait(false); @@ -60,11 +62,11 @@ public async Task UploadFileAndLinkToBorehole(IFormFile formFile, var fileId = file.Id; // Upload the file to the cloud storage. - await UploadObject(formFile, fileNameGuid).ConfigureAwait(false); + await UploadObject(fileStream, fileNameGuid, contentType).ConfigureAwait(false); // If file is already linked to the borehole, throw an exception. if (await context.BoreholeFiles.AnyAsync(bf => bf.BoreholeId == boreholeId && bf.FileId == fileId).ConfigureAwait(false)) - throw new InvalidOperationException($"File <{formFile.FileName}> is already attached to borehole with Id <{boreholeId}>."); + throw new InvalidOperationException($"File <{fileName}> is already attached to borehole with Id <{boreholeId}>."); // Link file to the borehole. var boreholeFile = new BoreholeFile { FileId = fileId, BoreholeId = boreholeId, UserId = user.Id, Attached = DateTime.UtcNow }; @@ -77,7 +79,7 @@ public async Task UploadFileAndLinkToBorehole(IFormFile formFile, } catch (Exception ex) { - logger.LogError(ex, "Error attaching file <{FileName}> to borehole with Id <{BoreholeId}>.", formFile.FileName, boreholeId); + logger.LogError(ex, "Error attaching file <{FileName}> to borehole with Id <{BoreholeId}>.", fileName, boreholeId); throw; } } @@ -85,14 +87,20 @@ public async Task UploadFileAndLinkToBorehole(IFormFile formFile, /// /// Uploads a file to the cloud storage. /// - /// The file to upload. + /// The file stream to upload. /// The name of the file in the storage. - internal async Task UploadObject(IFormFile file, string objectName) + /// The content type of the file. + internal async Task UploadObject(Stream fileStream, string objectName, string contentType) { try { - // Upload file - var putObjectRequest = new PutObjectRequest { BucketName = bucketName, Key = objectName, InputStream = file.OpenReadStream(), ContentType = file.ContentType }; + var putObjectRequest = new PutObjectRequest + { + BucketName = bucketName, + Key = objectName, + InputStream = fileStream, + ContentType = contentType, + }; await s3Client.PutObjectAsync(putObjectRequest).ConfigureAwait(false); } catch (AmazonS3Exception ex) diff --git a/src/api/Controllers/BoreholeFileController.cs b/src/api/Controllers/BoreholeFileController.cs index bfe711ccc..0f372f64c 100644 --- a/src/api/Controllers/BoreholeFileController.cs +++ b/src/api/Controllers/BoreholeFileController.cs @@ -45,7 +45,7 @@ public async Task Upload(IFormFile file, [Range(1, int.MaxValue)] try { - var boreholeFile = await boreholeFileCloudService.UploadFileAndLinkToBorehole(file, boreholeId).ConfigureAwait(false); + var boreholeFile = await boreholeFileCloudService.UploadFileAndLinkToBoreholeAsync(file.OpenReadStream(), file.FileName, file.ContentType, boreholeId).ConfigureAwait(false); return Ok(boreholeFile); } catch (InvalidOperationException ex) diff --git a/src/api/Controllers/ImportController.cs b/src/api/Controllers/ImportController.cs index 6f8b0cfa0..7903711e4 100644 --- a/src/api/Controllers/ImportController.cs +++ b/src/api/Controllers/ImportController.cs @@ -309,45 +309,28 @@ private async Task UploadAttachmentsAsync(ZipArchive zipArchive, List e.FullName == fileName); using var memoryStream = new MemoryStream(); - FormFile formFile = await CreateFormFileFromAttachmentAsync(fileToProcess, attachment, memoryStream).ConfigureAwait(false); + await attachment.Open().CopyToAsync(memoryStream).ConfigureAwait(false); + memoryStream.Position = 0; // Remove original file information from borehole object borehole.value.BoreholeFiles.Remove(fileToProcess); - - await UploadFormFileAsync(borehole.value, borehole.index, fileName, formFile).ConfigureAwait(false); + await UploadFormFileAsync(memoryStream, fileToProcess.File.Name, GetContentType(attachment.Name), borehole).ConfigureAwait(false); } } } } - private async Task UploadFormFileAsync(BoreholeImport borehole, int boreholeIndex, string fileName, FormFile formFile) + private async Task UploadFormFileAsync(Stream fileStream, string fileName, string contentType, (BoreholeImport Value, int Index) borehole) { try { - await boreholeFileCloudService.UploadFileAndLinkToBorehole(formFile, borehole.Id).ConfigureAwait(false); + await boreholeFileCloudService.UploadFileAndLinkToBoreholeAsync(fileStream, fileName, contentType, borehole.Value.Id).ConfigureAwait(false); } catch (Exception ex) { - logger.LogError(ex, "An error occurred while uploading the file: {FileName}", formFile.FileName); - AddValidationErrorToModelState(boreholeIndex, string.Format(CultureInfo.InvariantCulture, $"An error occurred while uploading the file: <{fileName}>", "upload"), ValidationErrorType.Attachment); - } - } - - private static async Task CreateFormFileFromAttachmentAsync(BoreholeFile? boreholeFile, ZipArchiveEntry? attachment, MemoryStream memoryStream) - { - using (var entryStream = attachment.Open()) - { - await entryStream.CopyToAsync(memoryStream).ConfigureAwait(false); + logger.LogError(ex, "An error occurred while uploading the file: {FileName}", fileName); + AddValidationErrorToModelState(borehole.Index, string.Format(CultureInfo.InvariantCulture, $"An error occurred while uploading the file: <{fileName}>", "upload"), ValidationErrorType.Attachment); } - - memoryStream.Position = 0; - - var formFile = new FormFile(memoryStream, 0, memoryStream.Length, boreholeFile.File.Name, boreholeFile.File.Name) - { - Headers = new HeaderDictionary(), - ContentType = GetContentType(attachment.Name), - }; - return formFile; } private static string GetContentType(string fileName) diff --git a/tests/api/BoreholeFileCloudServiceTest.cs b/tests/api/BoreholeFileCloudServiceTest.cs index 2751e6269..5f7d67104 100644 --- a/tests/api/BoreholeFileCloudServiceTest.cs +++ b/tests/api/BoreholeFileCloudServiceTest.cs @@ -63,7 +63,7 @@ public async Task UploadFileAndLinkToBoreholeShouldStoreFileInCloudStorageAndLin var firstPdfFormFile = GetFormFileByContent(content, fileName); // Upload file - await boreholeFileUploadService.UploadFileAndLinkToBorehole(firstPdfFormFile, minBoreholeId); + await boreholeFileUploadService.UploadFileAndLinkToBoreholeAsync(firstPdfFormFile.OpenReadStream(), firstPdfFormFile.FileName, firstPdfFormFile.ContentType, minBoreholeId).ConfigureAwait(false); // Get borehole with file linked from db var borehole = GetBoreholesWithIncludes(context.Boreholes).Single(b => b.Id == minBoreholeId); @@ -85,7 +85,7 @@ public async Task UploadObjectShouldStoreFileInCloudStorage() var pdfFormFile = GetFormFileByContent(Guid.NewGuid().ToString(), fileName); // Upload file - await boreholeFileUploadService.UploadObject(pdfFormFile, pdfFormFile.FileName); + await boreholeFileUploadService.UploadObject(pdfFormFile.OpenReadStream(), pdfFormFile.FileName, pdfFormFile.ContentType); // Get all objects in the bucket with provided name var listObjectsRequest = new ListObjectsV2Request { BucketName = bucketName, MaxKeys = 1000, Prefix = fileName }; @@ -105,7 +105,7 @@ public async Task UploadObjectSameFileTwiceShouldReplaceFileInCloudStorage() var pdfFormFile = GetFormFileByContent(content, "file_1.pdf"); // First Upload file - await boreholeFileUploadService.UploadObject(pdfFormFile, pdfFormFile.FileName); + await boreholeFileUploadService.UploadObject(pdfFormFile.OpenReadStream(), pdfFormFile.FileName, pdfFormFile.ContentType); // Get all files with same key after upload var listObjectsRequest = new ListObjectsV2Request { BucketName = bucketName, MaxKeys = 1000, Prefix = pdfFormFile.FileName }; @@ -119,7 +119,7 @@ public async Task UploadObjectSameFileTwiceShouldReplaceFileInCloudStorage() var uploadDate = files.First().LastModified; // Second Upload file - await boreholeFileUploadService.UploadObject(pdfFormFile, pdfFormFile.FileName); + await boreholeFileUploadService.UploadObject(pdfFormFile.OpenReadStream(), pdfFormFile.FileName, pdfFormFile.ContentType); // Get all objects in the bucket with provided name listObjectResponse = await s3Client.ListObjectsV2Async(listObjectsRequest).ConfigureAwait(false); @@ -146,7 +146,7 @@ public async Task GetObjectShouldReturnFileBytes() var pdfFormFile = GetFormFileByContent(content, "file_1.pdf"); // Upload file - await boreholeFileUploadService.UploadObject(pdfFormFile, pdfFormFile.FileName); + await boreholeFileUploadService.UploadObject(pdfFormFile.OpenReadStream(), pdfFormFile.FileName, pdfFormFile.ContentType); // Download file var result = await boreholeFileUploadService.GetObject(pdfFormFile.FileName); @@ -161,7 +161,7 @@ public async Task DeleteObjectShouldDeleteObjectFromStorage() var pdfFormFile = GetFormFileByContent(content, "file_1.pdf"); // Upload file - await boreholeFileUploadService.UploadObject(pdfFormFile, pdfFormFile.FileName); + await boreholeFileUploadService.UploadObject(pdfFormFile.OpenReadStream(), pdfFormFile.FileName, pdfFormFile.ContentType); // Ensure file exists await boreholeFileUploadService.GetObject(pdfFormFile.FileName); diff --git a/tests/api/Controllers/BoreholeFileControllerTest.cs b/tests/api/Controllers/BoreholeFileControllerTest.cs index 75f432c63..07f3adcaf 100644 --- a/tests/api/Controllers/BoreholeFileControllerTest.cs +++ b/tests/api/Controllers/BoreholeFileControllerTest.cs @@ -356,11 +356,11 @@ public async Task GetDataExtractionInfo() var fileUuid = file.File.NameUuid.Replace(".pdf", ""); var image1 = GetFormFileByExistingFile("labeling_attachment-1.png"); - await boreholeFileCloudService.UploadObject(image1, $"dataextraction/{fileUuid}-1.png"); + await boreholeFileCloudService.UploadObject(image1.OpenReadStream(), $"dataextraction/{fileUuid}-1.png", image1.ContentType); var image2 = GetFormFileByExistingFile("labeling_attachment-2.png"); - await boreholeFileCloudService.UploadObject(image2, $"dataextraction/{fileUuid}-2.png"); + await boreholeFileCloudService.UploadObject(image2.OpenReadStream(), $"dataextraction/{fileUuid}-2.png", image2.ContentType); var image3 = GetFormFileByExistingFile("labeling_attachment-3.png"); - await boreholeFileCloudService.UploadObject(image3, $"dataextraction/{fileUuid}-3.png"); + await boreholeFileCloudService.UploadObject(image3.OpenReadStream(), $"dataextraction/{fileUuid}-3.png", image3.ContentType); // Test var result = await controller.GetDataExtractionFileInfo(file.FileId, 1); @@ -436,7 +436,7 @@ public async Task GetDataExtractionImage() var fileUuid = file.File.NameUuid.Replace(".pdf", ""); var image1 = GetFormFileByExistingFile("labeling_attachment-1.png"); - await boreholeFileCloudService.UploadObject(image1, $"dataextraction/{fileUuid}-1.png"); + await boreholeFileCloudService.UploadObject(image1.OpenReadStream(), $"dataextraction/{fileUuid}-1.png", image1.ContentType); // Test var response = await controller.GetDataExtractionImage($"{fileUuid}-1.png"); diff --git a/tests/api/Controllers/ExportControllerTest.cs b/tests/api/Controllers/ExportControllerTest.cs index eae326272..221007c30 100644 --- a/tests/api/Controllers/ExportControllerTest.cs +++ b/tests/api/Controllers/ExportControllerTest.cs @@ -177,8 +177,7 @@ public async Task ExportJsonWithAttachments() var fileName = $"{Guid.NewGuid()}.pdf"; var content = Guid.NewGuid().ToString(); var fileBytes = Encoding.UTF8.GetBytes(content); - var formFile = new FormFile(new MemoryStream(fileBytes), 0, fileBytes.Length, null, fileName) { Headers = new HeaderDictionary(), ContentType = "application/pdf" }; - var boreholeFile = await boreholeFileCloudService.UploadFileAndLinkToBorehole(formFile, newBorehole.Id).ConfigureAwait(false); + var boreholeFile = await boreholeFileCloudService.UploadFileAndLinkToBoreholeAsync(new MemoryStream(fileBytes), fileName, "application/pdf", newBorehole.Id).ConfigureAwait(false); context.BoreholeFiles.Add(boreholeFile); var result = await controller.ExportJsonWithAttachmentsAsync([newBorehole.Id]).ConfigureAwait(false); From d5c3398d27b06936ec66471e2756b72f8b4dba0f Mon Sep 17 00:00:00 2001 From: MiraGeowerkstatt Date: Mon, 20 Jan 2025 16:48:23 +0100 Subject: [PATCH 2/2] Fix sonar cloud issue --- src/api/Controllers/ImportController.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/api/Controllers/ImportController.cs b/src/api/Controllers/ImportController.cs index 7903711e4..cb9ab5ab0 100644 --- a/src/api/Controllers/ImportController.cs +++ b/src/api/Controllers/ImportController.cs @@ -307,6 +307,11 @@ private async Task UploadAttachmentsAsync(ZipArchive zipArchive, List e.FullName == fileName); + if (attachment == null) + { + AddValidationErrorToModelState(borehole.index, $"Attachment with the name <{fileName}> is referenced in JSON file but was not not found in ZIP archive.", ValidationErrorType.Attachment); + continue; + } using var memoryStream = new MemoryStream(); await attachment.Open().CopyToAsync(memoryStream).ConfigureAwait(false);