Skip to content

Commit

Permalink
Refactor BoreholeFileCloudService.cs (#1816)
Browse files Browse the repository at this point in the history
  • Loading branch information
MiraGeowerkstatt authored Jan 27, 2025
2 parents 00e5783 + d5c3398 commit ce53eda
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 49 deletions.
32 changes: 20 additions & 12 deletions src/api/BoreholeFileCloudService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ public BoreholeFileCloudService(BdmsContext context, IConfiguration configuratio
/// <summary>
/// Uploads a file to the cloud storage and links it to the borehole.
/// </summary>
/// <param name="formFile">The file to upload and link to the <see cref="Borehole"/>.</param>
/// <param name="boreholeId">The <see cref="Borehole.Id"/> to link the uploaded <paramref name="formFile"/> to.</param>
public async Task<BoreholeFile> UploadFileAndLinkToBorehole(IFormFile formFile, int boreholeId)
/// <param name="fileStream">The file stream for the file to upload and link to the <see cref="Borehole"/>.</param>
/// <param name="fileName">The name of the file to upload.</param>
/// <param name="contentType">The content type of the file.</param>
/// <param name="boreholeId">The <see cref="Borehole.Id"/> to link the uploaded file to.</param>
public async Task<BoreholeFile> 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;
Expand All @@ -49,22 +51,22 @@ public async Task<BoreholeFile> 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);

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 };
Expand All @@ -77,22 +79,28 @@ public async Task<BoreholeFile> 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;
}
}

/// <summary>
/// Uploads a file to the cloud storage.
/// </summary>
/// <param name="file">The file to upload.</param>
/// <param name="fileStream">The file stream to upload.</param>
/// <param name="objectName">The name of the file in the storage.</param>
internal async Task UploadObject(IFormFile file, string objectName)
/// <param name="contentType">The content type of the file.</param>
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)
Expand Down
2 changes: 1 addition & 1 deletion src/api/Controllers/BoreholeFileController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public async Task<IActionResult> 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)
Expand Down
36 changes: 12 additions & 24 deletions src/api/Controllers/ImportController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -307,47 +307,35 @@ private async Task UploadAttachmentsAsync(ZipArchive zipArchive, List<BoreholeIm
{
var fileName = $"{fileToProcess.File.NameUuid}_{fileToProcess.File.Name}";
var attachment = zipArchive.Entries.FirstOrDefault(e => 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();
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<FormFile> 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)
Expand Down
12 changes: 6 additions & 6 deletions tests/api/BoreholeFileCloudServiceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 };
Expand All @@ -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 };
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down
8 changes: 4 additions & 4 deletions tests/api/Controllers/BoreholeFileControllerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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");
Expand Down
3 changes: 1 addition & 2 deletions tests/api/Controllers/ExportControllerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit ce53eda

Please sign in to comment.