From 79f9f9bb33c24cd218d1f80c4ffd0c62b9410986 Mon Sep 17 00:00:00 2001 From: Tyrrellion Date: Mon, 19 May 2025 10:33:01 +0100 Subject: [PATCH 1/7] feat: initial docker file --- compose.yaml | 30 ++++++++++++++++++++++++++++++ src/ServiceLayer.Mesh/DockerFile | 19 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/ServiceLayer.Mesh/DockerFile diff --git a/compose.yaml b/compose.yaml index a838b53..8c87fae 100644 --- a/compose.yaml +++ b/compose.yaml @@ -29,6 +29,36 @@ services: networks: - backend + serviceLayerMesh: + container_name: "serviceLayerMesh" + build: + context: ./Src + dockerfile: ServiceLayer.MESH/Dockerfile + platform: linux/amd64 + restart: always + environment: + FUNCTIONS_WORKER_RUNTIME: "dotnet-isolated" + AzureWebJobsStorage: "${AZURE_WEB_JOBS_STORAGE}" + AzureWebJobsSecretStorageType: "files" + DatabaseConnectionString: "${DatabaseConnectionString}" + EVENT_GRID_TOPIC_URL: "${EVENT_GRID_TOPIC_URL}" + EVENT_GRID_TOPIC_KEY: "${EVENT_GRID_TOPIC_KEY}" + ports: + - "${API_PORT}:80" + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:80/api/health || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + depends_on: + azurite: + condition: service_healthy + db: + condition: service_healthy + networks: + - backend + azurite: container_name: "azurite" image: mcr.microsoft.com/azure-storage/azurite:latest diff --git a/src/ServiceLayer.Mesh/DockerFile b/src/ServiceLayer.Mesh/DockerFile new file mode 100644 index 0000000..162c540 --- /dev/null +++ b/src/ServiceLayer.Mesh/DockerFile @@ -0,0 +1,19 @@ +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS installer-env +WORKDIR /src/dotnet-function-app + +COPY ./ServiceLayer.MESH/ServiceLayer.MESH.csproj . +RUN dotnet restore + +COPY ./ServiceLayer.MESH/ . + +RUN dotnet publish -c Release -o /home/site/wwwroot + +FROM mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated9.0 AS production +ENV AzureWebJobsScriptRoot=/home/site/wwwroot \ + AzureFunctionsJobHost__Logging__Console__IsEnabled=true \ + ASPNETCORE_ENVIRONMENT=Production + +RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser +USER appuser + +COPY --from=installer-env ["/home/site/wwwroot", "/home/site/wwwroot"] From 92212a86fd41423d095b7a413f26e3f56b430823 Mon Sep 17 00:00:00 2001 From: Tyrrellion Date: Mon, 19 May 2025 13:51:02 +0100 Subject: [PATCH 2/7] feat: fixing image builds --- .env.example | 3 ++- compose.yaml | 28 +++++++++++++++++++++++----- src/ServiceLayer.Mesh/DockerFile | 8 ++++++-- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/.env.example b/.env.example index e3478f6..c445638 100644 --- a/.env.example +++ b/.env.example @@ -22,6 +22,7 @@ StaleHours=12 # API Configuration API_PORT=7071 +MESH_PORT=7072 # Event Grid Configuration EVENT_GRID_TOPIC_URL=https://localhost:60101/api/events @@ -29,7 +30,7 @@ EVENT_GRID_TOPIC_KEY=TheLocal+DevelopmentKey= # Azurite Configuration AZURITE_ACCOUNT_KEY=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw== -AZURITE_CONNECTION_STRING=DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1 +AZURITE_CONNECTION_STRING=DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite:10000/devstoreaccount1;QueueEndpoint=http://azurite:10001/devstoreaccount1;TableEndpoint=http://azurite/devstoreaccount1 AZURITE_BLOB_PORT=10000 AZURITE_QUEUE_PORT=10001 AZURITE_TABLE_PORT=10002 diff --git a/compose.yaml b/compose.yaml index f3d8926..5ad7a98 100644 --- a/compose.yaml +++ b/compose.yaml @@ -29,8 +29,8 @@ services: networks: - backend - serviceLayerMesh: - container_name: "serviceLayerMesh" + service-layer-mesh: + container_name: "service-layer-mesh" build: context: ./Src dockerfile: ServiceLayer.MESH/Dockerfile @@ -41,10 +41,20 @@ services: AzureWebJobsStorage: "${AZURE_WEB_JOBS_STORAGE}" AzureWebJobsSecretStorageType: "files" DatabaseConnectionString: "${DatabaseConnectionString}" - EVENT_GRID_TOPIC_URL: "${EVENT_GRID_TOPIC_URL}" - EVENT_GRID_TOPIC_KEY: "${EVENT_GRID_TOPIC_KEY}" + FileDiscoveryTimerExpression: "${FileDiscoveryTimerExpression}" + MeshHandshakeTimerExpression: "${MeshHandshakeTimerExpression}" + FileRetryTimerExpression: "${FileRetryTimerExpression}" + FileExtractQueueName: "${FileExtractQueueName}" + FileTransformQueueName: "${FileTransformQueueName}" + StaleHours: "${StaleHours}" + MeshApiBaseUrl: "${MeshApiBaseUrl}" + NbssMailboxId: "${NbssMailboxId}" + MeshPassword: "${MeshPassword}" + MeshSharedKey: "${MeshSharedKey}" + AZURITE_CONNECTION_STRING: "${AZURITE_CONNECTION_STRING}" + ASPNETCORE_ENVIRONMENT: "Production" ports: - - "${API_PORT}:80" + - "${MESH_PORT}:80" healthcheck: test: ["CMD-SHELL", "curl -f http://localhost:80/api/health || exit 1"] interval: 30s @@ -79,6 +89,14 @@ services: networks: - backend + tester: + image: curlimages/curl + depends_on: + - azurite + command: ["sh", "-c", "sleep 10 && curl http://azurite:10000/devstoreaccount1"] + networks: + - backend + db: container_name: "db" image: mcr.microsoft.com/mssql/server:2022-latest diff --git a/src/ServiceLayer.Mesh/DockerFile b/src/ServiceLayer.Mesh/DockerFile index 162c540..2257ba7 100644 --- a/src/ServiceLayer.Mesh/DockerFile +++ b/src/ServiceLayer.Mesh/DockerFile @@ -1,10 +1,14 @@ FROM mcr.microsoft.com/dotnet/sdk:9.0 AS installer-env WORKDIR /src/dotnet-function-app -COPY ./ServiceLayer.MESH/ServiceLayer.MESH.csproj . +COPY ./dotnet-mesh-client/application/DotNetMeshClient/NHS.Mesh.Client/NHS.Mesh.Client.csproj ../dotnet-mesh-client/application/DotNetMeshClient/NHS.Mesh.Client/ +COPY ./ServiceLayer.Common/ServiceLayer.Common.csproj ../ServiceLayer.Common/ +COPY ./ServiceLayer.Mesh/ServiceLayer.Mesh.csproj . RUN dotnet restore -COPY ./ServiceLayer.MESH/ . +COPY ./dotnet-mesh-client/ ../dotnet-mesh-client/ +COPY ./ServiceLayer.Common/ ../ServiceLayer.Common/ +COPY ./ServiceLayer.Mesh/ . RUN dotnet publish -c Release -o /home/site/wwwroot From ca55dfd44e19ee02cd6615bd2d69aa6c3cd4d41a Mon Sep 17 00:00:00 2001 From: Tyrrellion Date: Thu, 22 May 2025 15:22:28 +0100 Subject: [PATCH 3/7] feat: now can comunicate with mesh sandbox and dbcontext threading issue resolved Signed-off-by: Tyrrellion --- compose.yaml | 19 +++-- .../Functions/FileDiscoveryFunction.cs | 76 +++++++++---------- .../Functions/FileExtractFunction.cs | 27 +++---- .../Functions/FileRetryFunction.cs | 13 ++-- .../Functions/FileTransformFunction.cs | 7 +- src/ServiceLayer.Mesh/Program.cs | 2 +- 6 files changed, 73 insertions(+), 71 deletions(-) diff --git a/compose.yaml b/compose.yaml index 5ad7a98..43c8f1a 100644 --- a/compose.yaml +++ b/compose.yaml @@ -8,15 +8,16 @@ services: restart: always environment: FUNCTIONS_WORKER_RUNTIME: "dotnet-isolated" - AzureWebJobsStorage: "${AZURE_WEB_JOBS_STORAGE}" + AzureWebJobsStorage: "${AZURITE_CONNECTION_STRING}" AzureWebJobsSecretStorageType: "files" DatabaseConnectionString: "${DatabaseConnectionString}" EVENT_GRID_TOPIC_URL: "${EVENT_GRID_TOPIC_URL}" EVENT_GRID_TOPIC_KEY: "${EVENT_GRID_TOPIC_KEY}" + ASPNETCORE_URLS: "http://0.0.0.0:8080" ports: - - "${API_PORT}:80" + - "${API_PORT}:8080" healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:80/api/health || exit 1"] + test: ["CMD-SHELL", "curl -f http://localhost:8080/api/health || exit 1"] interval: 30s timeout: 10s retries: 3 @@ -38,7 +39,7 @@ services: restart: always environment: FUNCTIONS_WORKER_RUNTIME: "dotnet-isolated" - AzureWebJobsStorage: "${AZURE_WEB_JOBS_STORAGE}" + AzureWebJobsStorage: "${AZURITE_CONNECTION_STRING}" AzureWebJobsSecretStorageType: "files" DatabaseConnectionString: "${DatabaseConnectionString}" FileDiscoveryTimerExpression: "${FileDiscoveryTimerExpression}" @@ -47,16 +48,18 @@ services: FileExtractQueueName: "${FileExtractQueueName}" FileTransformQueueName: "${FileTransformQueueName}" StaleHours: "${StaleHours}" - MeshApiBaseUrl: "${MeshApiBaseUrl}" + MeshApiBaseUrl: "http://mesh_sandbox:80/messageexchange" NbssMailboxId: "${NbssMailboxId}" MeshPassword: "${MeshPassword}" MeshSharedKey: "${MeshSharedKey}" AZURITE_CONNECTION_STRING: "${AZURITE_CONNECTION_STRING}" - ASPNETCORE_ENVIRONMENT: "Production" + MeshStorageAccountUrl: "${AZURITE_CONNECTION_STRING}" + ASPNETCORE_ENVIRONMENT: "${ASPNETCORE_ENVIRONMENT}" + ASPNETCORE_URLS: "http://0.0.0.0:8080" ports: - - "${MESH_PORT}:80" + - "${MESH_PORT}:8080" healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:80/api/health || exit 1"] + test: ["CMD-SHELL", "curl -f http://localhost:8080/api/health || exit 1"] interval: 30s timeout: 10s retries: 3 diff --git a/src/ServiceLayer.Mesh/Functions/FileDiscoveryFunction.cs b/src/ServiceLayer.Mesh/Functions/FileDiscoveryFunction.cs index 704186b..501a730 100644 --- a/src/ServiceLayer.Mesh/Functions/FileDiscoveryFunction.cs +++ b/src/ServiceLayer.Mesh/Functions/FileDiscoveryFunction.cs @@ -7,52 +7,52 @@ using ServiceLayer.Mesh.Configuration; using ServiceLayer.Mesh.Messaging; -namespace ServiceLayer.Mesh.Functions +namespace ServiceLayer.Mesh.Functions; + +public class FileDiscoveryFunction( + ILogger logger, + IFileDiscoveryFunctionConfiguration configuration, + IMeshInboxService meshInboxService, + IDbContextFactory dbContextFactory, + IFileExtractQueueClient fileExtractQueueClient) { - public class FileDiscoveryFunction( - ILogger logger, - IFileDiscoveryFunctionConfiguration configuration, - IMeshInboxService meshInboxService, - ServiceLayerDbContext serviceLayerDbContext, - IFileExtractQueueClient fileExtractQueueClient) + [Function("FileDiscoveryFunction")] + public async Task Run([TimerTrigger("%FileDiscoveryTimerExpression%")] TimerInfo myTimer) { - [Function("FileDiscoveryFunction")] - public async Task Run([TimerTrigger("%FileDiscoveryTimerExpression%")] TimerInfo myTimer) + logger.LogInformation("{functionName} started at: {time}", nameof(FileDiscoveryFunction), DateTime.UtcNow); + + var response = await meshInboxService.GetMessagesAsync(configuration.NbssMeshMailboxId); + + foreach (var messageId in response.Response.Messages) { - logger.LogInformation("{functionName} started at: {time}", nameof(FileDiscoveryFunction), DateTime.UtcNow); + await using var serviceLayerDbContext = dbContextFactory.CreateDbContext(); + await using var transaction = await serviceLayerDbContext.Database.BeginTransactionAsync(); - var response = await meshInboxService.GetMessagesAsync(configuration.NbssMeshMailboxId); + var existing = await serviceLayerDbContext.MeshFiles + .AnyAsync(f => f.FileId == messageId); - foreach (var messageId in response.Response.Messages) + if (!existing) { - await using var transaction = await serviceLayerDbContext.Database.BeginTransactionAsync(); + var file = new MeshFile + { + FileId = messageId, + FileType = MeshFileType.NbssAppointmentEvents, + MailboxId = configuration.NbssMeshMailboxId, + Status = MeshFileStatus.Discovered, + FirstSeenUtc = DateTime.UtcNow, + LastUpdatedUtc = DateTime.UtcNow + }; - var existing = await serviceLayerDbContext.MeshFiles - .AnyAsync(f => f.FileId == messageId); + serviceLayerDbContext.MeshFiles.Add(file); - if (!existing) - { - var file = new MeshFile - { - FileId = messageId, - FileType = MeshFileType.NbssAppointmentEvents, - MailboxId = configuration.NbssMeshMailboxId, - Status = MeshFileStatus.Discovered, - FirstSeenUtc = DateTime.UtcNow, - LastUpdatedUtc = DateTime.UtcNow - }; - - serviceLayerDbContext.MeshFiles.Add(file); - - await serviceLayerDbContext.SaveChangesAsync(); - await transaction.CommitAsync(); - - await fileExtractQueueClient.EnqueueFileExtractAsync(file); - } - else - { - await transaction.RollbackAsync(); - } + await serviceLayerDbContext.SaveChangesAsync(); + await transaction.CommitAsync(); + + await fileExtractQueueClient.EnqueueFileExtractAsync(file); + } + else + { + await transaction.RollbackAsync(); } } } diff --git a/src/ServiceLayer.Mesh/Functions/FileExtractFunction.cs b/src/ServiceLayer.Mesh/Functions/FileExtractFunction.cs index 75505e9..d66d6cd 100644 --- a/src/ServiceLayer.Mesh/Functions/FileExtractFunction.cs +++ b/src/ServiceLayer.Mesh/Functions/FileExtractFunction.cs @@ -2,6 +2,7 @@ using Microsoft.Azure.Functions.Worker; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Microsoft.VisualBasic; using NHS.MESH.Client.Contracts.Services; using ServiceLayer.Data; using ServiceLayer.Data.Models; @@ -15,7 +16,7 @@ public class FileExtractFunction( ILogger logger, IFileExtractFunctionConfiguration configuration, IMeshInboxService meshInboxService, - ServiceLayerDbContext serviceLayerDbContext, + IDbContextFactory dbContextFactory, IFileTransformQueueClient fileTransformQueueClient, IFileExtractQueueClient fileExtractQueueClient, IMeshFilesBlobStore meshFileBlobStore) @@ -25,33 +26,29 @@ public async Task Run([QueueTrigger("%FileExtractQueueName%")] FileExtractQueueM { logger.LogInformation("{functionName} started at: {time}", nameof(FileDiscoveryFunction), DateTime.UtcNow); + await using var serviceLayerDbContext = dbContextFactory.CreateDbContext(); await using var transaction = await serviceLayerDbContext.Database.BeginTransactionAsync(); - var file = await GetFileAsync(message.FileId); - if (file == null) - { - return; - } - - if (!IsFileSuitableForExtraction(file)) + var file = await GetFileAsync(serviceLayerDbContext, message.FileId); + if (file == null || !IsFileSuitableForExtraction(file)) { return; } - await UpdateFileStatusForExtraction(file); + await UpdateFileStatusForExtraction(serviceLayerDbContext, file); await transaction.CommitAsync(); try { - await ProcessFileExtraction(file, message); + await ProcessFileExtraction(serviceLayerDbContext, file, message); } catch (Exception ex) { - await HandleExtractionError(file, message, ex); + await HandleExtractionError(serviceLayerDbContext, file, message, ex); } } - private async Task GetFileAsync(string fileId) + private async Task GetFileAsync(ServiceLayerDbContext serviceLayerDbContext, string fileId) { var file = await serviceLayerDbContext.MeshFiles .FirstOrDefaultAsync(f => f.FileId == fileId); @@ -82,14 +79,14 @@ private bool IsFileSuitableForExtraction(MeshFile file) return true; } - private async Task UpdateFileStatusForExtraction(MeshFile file) + private async Task UpdateFileStatusForExtraction(ServiceLayerDbContext serviceLayerDbContext, MeshFile file) { file.Status = MeshFileStatus.Extracting; file.LastUpdatedUtc = DateTime.UtcNow; await serviceLayerDbContext.SaveChangesAsync(); } - private async Task ProcessFileExtraction(MeshFile file, FileExtractQueueMessage message) + private async Task ProcessFileExtraction(ServiceLayerDbContext serviceLayerDbContext, MeshFile file, FileExtractQueueMessage message) { var meshResponse = await meshInboxService.GetMessageByIdAsync(configuration.NbssMeshMailboxId, file.FileId); if (!meshResponse.IsSuccessful) @@ -113,7 +110,7 @@ private async Task ProcessFileExtraction(MeshFile file, FileExtractQueueMessage await fileTransformQueueClient.EnqueueFileTransformAsync(file); } - private async Task HandleExtractionError(MeshFile file, FileExtractQueueMessage message, Exception ex) + private async Task HandleExtractionError(ServiceLayerDbContext serviceLayerDbContext, MeshFile file, FileExtractQueueMessage message, Exception ex) { logger.LogError(ex, "An exception occurred during file extraction for fileId: {fileId}", message.FileId); file.Status = MeshFileStatus.FailedExtract; diff --git a/src/ServiceLayer.Mesh/Functions/FileRetryFunction.cs b/src/ServiceLayer.Mesh/Functions/FileRetryFunction.cs index 7595af6..8558dd1 100644 --- a/src/ServiceLayer.Mesh/Functions/FileRetryFunction.cs +++ b/src/ServiceLayer.Mesh/Functions/FileRetryFunction.cs @@ -10,7 +10,7 @@ namespace ServiceLayer.Mesh.Functions; public class FileRetryFunction( ILogger logger, - ServiceLayerDbContext serviceLayerDbContext, + IDbContextFactory dbContextFactory, IFileExtractQueueClient fileExtractQueueClient, IFileTransformQueueClient fileTransformQueueClient, IFileRetryFunctionConfiguration configuration) @@ -22,12 +22,13 @@ public async Task Run([TimerTrigger("%FileRetryTimerExpression%")] TimerInfo myT var staleDateTimeUtc = DateTime.UtcNow.AddHours(-configuration.StaleHours); - await Task.WhenAll( - RetryStaleExtractions(staleDateTimeUtc), - RetryStaleTransformations(staleDateTimeUtc)); + await using var serviceLayerDbContext = dbContextFactory.CreateDbContext(); + + await RetryStaleExtractions(serviceLayerDbContext, staleDateTimeUtc); + await RetryStaleTransformations(serviceLayerDbContext, staleDateTimeUtc); } - private async Task RetryStaleExtractions(DateTime staleDateTimeUtc) + private async Task RetryStaleExtractions(ServiceLayerDbContext serviceLayerDbContext, DateTime staleDateTimeUtc) { var staleFiles = await serviceLayerDbContext.MeshFiles .Where(f => @@ -46,7 +47,7 @@ private async Task RetryStaleExtractions(DateTime staleDateTimeUtc) } } - private async Task RetryStaleTransformations(DateTime staleDateTimeUtc) + private async Task RetryStaleTransformations(ServiceLayerDbContext serviceLayerDbContext, DateTime staleDateTimeUtc) { var staleFiles = await serviceLayerDbContext.MeshFiles .Where(f => diff --git a/src/ServiceLayer.Mesh/Functions/FileTransformFunction.cs b/src/ServiceLayer.Mesh/Functions/FileTransformFunction.cs index 60606e8..d615861 100644 --- a/src/ServiceLayer.Mesh/Functions/FileTransformFunction.cs +++ b/src/ServiceLayer.Mesh/Functions/FileTransformFunction.cs @@ -12,13 +12,14 @@ namespace ServiceLayer.Mesh.Functions; public class FileTransformFunction( ILogger logger, - ServiceLayerDbContext serviceLayerDbContext, + IDbContextFactory dbContextFactory, IMeshFilesBlobStore meshFileBlobStore, IFileTransformFunctionConfiguration configuration) { [Function("FileTransformFunction")] public async Task Run([QueueTrigger("%FileTransformQueueName%")] FileTransformQueueMessage message) { + await using var serviceLayerDbContext = dbContextFactory.CreateDbContext(); await using var transaction = await serviceLayerDbContext.Database.BeginTransactionAsync(); var file = await serviceLayerDbContext.MeshFiles.FirstOrDefaultAsync(f => f.FileId == message.FileId); @@ -34,7 +35,7 @@ public async Task Run([QueueTrigger("%FileTransformQueueName%")] FileTransformQu return; } - await UpdateFileStatusForTransformation(file); + await UpdateFileStatusForTransformation(serviceLayerDbContext, file); await transaction.CommitAsync(); var fileContent = await meshFileBlobStore.DownloadAsync(file); @@ -43,7 +44,7 @@ public async Task Run([QueueTrigger("%FileTransformQueueName%")] FileTransformQu // After initial common checks against database, find the appropriate implementation of IFileTransformer to handle the functionality that differs between file type. } - private async Task UpdateFileStatusForTransformation(MeshFile file) + private async Task UpdateFileStatusForTransformation(ServiceLayerDbContext serviceLayerDbContext, MeshFile file) { file.Status = MeshFileStatus.Transforming; file.LastUpdatedUtc = DateTime.UtcNow; diff --git a/src/ServiceLayer.Mesh/Program.cs b/src/ServiceLayer.Mesh/Program.cs index 03e705a..4fd8769 100644 --- a/src/ServiceLayer.Mesh/Program.cs +++ b/src/ServiceLayer.Mesh/Program.cs @@ -26,7 +26,7 @@ }).Build(); // EF Core DbContext - services.AddDbContext(options => + services.AddDbContextFactory(options => { var connectionString = Environment.GetEnvironmentVariable("DatabaseConnectionString"); if (string.IsNullOrEmpty(connectionString)) From 3351f87f34621f534d97e39a88cab8f183c2da63 Mon Sep 17 00:00:00 2001 From: Tyrrellion Date: Fri, 23 May 2025 15:12:51 +0100 Subject: [PATCH 4/7] feat: fixing issue with dequeueing messages --- .env.example | 1 + compose.yaml | 1 + src/ServiceLayer.Mesh/Program.cs | 13 +++++++++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index c445638..5f73c2b 100644 --- a/.env.example +++ b/.env.example @@ -19,6 +19,7 @@ QueueUrl=http://127.0.0.1:10001 FileExtractQueueName=file-extract FileTransformQueueName=file-transform StaleHours=12 +BlobContainerName=blob-container # API Configuration API_PORT=7071 diff --git a/compose.yaml b/compose.yaml index 43c8f1a..9711321 100644 --- a/compose.yaml +++ b/compose.yaml @@ -56,6 +56,7 @@ services: MeshStorageAccountUrl: "${AZURITE_CONNECTION_STRING}" ASPNETCORE_ENVIRONMENT: "${ASPNETCORE_ENVIRONMENT}" ASPNETCORE_URLS: "http://0.0.0.0:8080" + BlobContainerName: "${BlobContainerName}" ports: - "${MESH_PORT}:8080" healthcheck: diff --git a/src/ServiceLayer.Mesh/Program.cs b/src/ServiceLayer.Mesh/Program.cs index 4fd8769..7f9e339 100644 --- a/src/ServiceLayer.Mesh/Program.cs +++ b/src/ServiceLayer.Mesh/Program.cs @@ -8,6 +8,7 @@ using ServiceLayer.Mesh.Configuration; using ServiceLayer.Mesh.Messaging; using ServiceLayer.Data; +using ServiceLayer.Mesh.Storage; var host = new HostBuilder() .ConfigureFunctionsWebApplication() @@ -41,11 +42,17 @@ if (isLocalEnvironment) { var connectionString = Environment.GetEnvironmentVariable("AzureWebJobsStorage"); - return new QueueServiceClient(connectionString); + return new QueueServiceClient(connectionString, new QueueClientOptions + { + MessageEncoding = QueueMessageEncoding.Base64 + }); } var meshStorageAccountUrl = Environment.GetEnvironmentVariable("MeshStorageAccountUrl"); - return new QueueServiceClient(new Uri(meshStorageAccountUrl), new DefaultAzureCredential()); + return new QueueServiceClient(new Uri(meshStorageAccountUrl), new DefaultAzureCredential(), new QueueClientOptions + { + MessageEncoding = QueueMessageEncoding.Base64 + }); }); services.AddSingleton(); @@ -58,6 +65,8 @@ Environment.GetEnvironmentVariable("BlobContainerName")); }); + services.AddSingleton(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); From ae98719c525a0221c592519b85e5b49209b28fc6 Mon Sep 17 00:00:00 2001 From: Tyrrellion Date: Fri, 23 May 2025 15:19:34 +0100 Subject: [PATCH 5/7] feat: removing chatGPTs stupid recomended changes around AddDbcontext --- src/ServiceLayer.Mesh/Functions/FileDiscoveryFunction.cs | 3 +-- src/ServiceLayer.Mesh/Functions/FileExtractFunction.cs | 5 ++--- src/ServiceLayer.Mesh/Functions/FileRetryFunction.cs | 4 +--- src/ServiceLayer.Mesh/Functions/FileTransformFunction.cs | 6 +++--- src/ServiceLayer.Mesh/Program.cs | 2 +- 5 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/ServiceLayer.Mesh/Functions/FileDiscoveryFunction.cs b/src/ServiceLayer.Mesh/Functions/FileDiscoveryFunction.cs index 501a730..ffddeac 100644 --- a/src/ServiceLayer.Mesh/Functions/FileDiscoveryFunction.cs +++ b/src/ServiceLayer.Mesh/Functions/FileDiscoveryFunction.cs @@ -13,7 +13,7 @@ public class FileDiscoveryFunction( ILogger logger, IFileDiscoveryFunctionConfiguration configuration, IMeshInboxService meshInboxService, - IDbContextFactory dbContextFactory, + ServiceLayerDbContext serviceLayerDbContext, IFileExtractQueueClient fileExtractQueueClient) { [Function("FileDiscoveryFunction")] @@ -25,7 +25,6 @@ public async Task Run([TimerTrigger("%FileDiscoveryTimerExpression%")] TimerInfo foreach (var messageId in response.Response.Messages) { - await using var serviceLayerDbContext = dbContextFactory.CreateDbContext(); await using var transaction = await serviceLayerDbContext.Database.BeginTransactionAsync(); var existing = await serviceLayerDbContext.MeshFiles diff --git a/src/ServiceLayer.Mesh/Functions/FileExtractFunction.cs b/src/ServiceLayer.Mesh/Functions/FileExtractFunction.cs index d66d6cd..1aa8dee 100644 --- a/src/ServiceLayer.Mesh/Functions/FileExtractFunction.cs +++ b/src/ServiceLayer.Mesh/Functions/FileExtractFunction.cs @@ -16,7 +16,7 @@ public class FileExtractFunction( ILogger logger, IFileExtractFunctionConfiguration configuration, IMeshInboxService meshInboxService, - IDbContextFactory dbContextFactory, + ServiceLayerDbContext serviceLayerDbContext, IFileTransformQueueClient fileTransformQueueClient, IFileExtractQueueClient fileExtractQueueClient, IMeshFilesBlobStore meshFileBlobStore) @@ -24,9 +24,8 @@ public class FileExtractFunction( [Function("FileExtractFunction")] public async Task Run([QueueTrigger("%FileExtractQueueName%")] FileExtractQueueMessage message) { - logger.LogInformation("{functionName} started at: {time}", nameof(FileDiscoveryFunction), DateTime.UtcNow); + logger.LogInformation("{functionName} started at: {time}", nameof(FileExtractFunction), DateTime.UtcNow); - await using var serviceLayerDbContext = dbContextFactory.CreateDbContext(); await using var transaction = await serviceLayerDbContext.Database.BeginTransactionAsync(); var file = await GetFileAsync(serviceLayerDbContext, message.FileId); diff --git a/src/ServiceLayer.Mesh/Functions/FileRetryFunction.cs b/src/ServiceLayer.Mesh/Functions/FileRetryFunction.cs index 8558dd1..074e6c6 100644 --- a/src/ServiceLayer.Mesh/Functions/FileRetryFunction.cs +++ b/src/ServiceLayer.Mesh/Functions/FileRetryFunction.cs @@ -10,7 +10,7 @@ namespace ServiceLayer.Mesh.Functions; public class FileRetryFunction( ILogger logger, - IDbContextFactory dbContextFactory, + ServiceLayerDbContext serviceLayerDbContext, IFileExtractQueueClient fileExtractQueueClient, IFileTransformQueueClient fileTransformQueueClient, IFileRetryFunctionConfiguration configuration) @@ -22,8 +22,6 @@ public async Task Run([TimerTrigger("%FileRetryTimerExpression%")] TimerInfo myT var staleDateTimeUtc = DateTime.UtcNow.AddHours(-configuration.StaleHours); - await using var serviceLayerDbContext = dbContextFactory.CreateDbContext(); - await RetryStaleExtractions(serviceLayerDbContext, staleDateTimeUtc); await RetryStaleTransformations(serviceLayerDbContext, staleDateTimeUtc); } diff --git a/src/ServiceLayer.Mesh/Functions/FileTransformFunction.cs b/src/ServiceLayer.Mesh/Functions/FileTransformFunction.cs index d615861..9402061 100644 --- a/src/ServiceLayer.Mesh/Functions/FileTransformFunction.cs +++ b/src/ServiceLayer.Mesh/Functions/FileTransformFunction.cs @@ -12,14 +12,13 @@ namespace ServiceLayer.Mesh.Functions; public class FileTransformFunction( ILogger logger, - IDbContextFactory dbContextFactory, + ServiceLayerDbContext serviceLayerDbContext, IMeshFilesBlobStore meshFileBlobStore, IFileTransformFunctionConfiguration configuration) { [Function("FileTransformFunction")] public async Task Run([QueueTrigger("%FileTransformQueueName%")] FileTransformQueueMessage message) { - await using var serviceLayerDbContext = dbContextFactory.CreateDbContext(); await using var transaction = await serviceLayerDbContext.Database.BeginTransactionAsync(); var file = await serviceLayerDbContext.MeshFiles.FirstOrDefaultAsync(f => f.FileId == message.FileId); @@ -41,7 +40,8 @@ public async Task Run([QueueTrigger("%FileTransformQueueName%")] FileTransformQu var fileContent = await meshFileBlobStore.DownloadAsync(file); // TODO - take dependency on IEnumerable. - // After initial common checks against database, find the appropriate implementation of IFileTransformer to handle the functionality that differs between file type. + // After initial common checks against database, find the appropriate implementation of IFileTransformer + // to handle the functionality that differs between file types. } private async Task UpdateFileStatusForTransformation(ServiceLayerDbContext serviceLayerDbContext, MeshFile file) diff --git a/src/ServiceLayer.Mesh/Program.cs b/src/ServiceLayer.Mesh/Program.cs index 7f9e339..878496b 100644 --- a/src/ServiceLayer.Mesh/Program.cs +++ b/src/ServiceLayer.Mesh/Program.cs @@ -27,7 +27,7 @@ }).Build(); // EF Core DbContext - services.AddDbContextFactory(options => + services.AddDbContext(options => { var connectionString = Environment.GetEnvironmentVariable("DatabaseConnectionString"); if (string.IsNullOrEmpty(connectionString)) From 69285b828c7a8bde77af915088f229b8d68fcf93 Mon Sep 17 00:00:00 2001 From: Tyrrellion Date: Fri, 23 May 2025 15:53:24 +0100 Subject: [PATCH 6/7] feat: fixing blob container doesn't exist issue --- src/ServiceLayer.Mesh/Program.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ServiceLayer.Mesh/Program.cs b/src/ServiceLayer.Mesh/Program.cs index 878496b..00ad4df 100644 --- a/src/ServiceLayer.Mesh/Program.cs +++ b/src/ServiceLayer.Mesh/Program.cs @@ -60,9 +60,12 @@ services.AddSingleton(provider => { - return new BlobContainerClient( + var client = new BlobContainerClient( Environment.GetEnvironmentVariable("AzureWebJobsStorage"), Environment.GetEnvironmentVariable("BlobContainerName")); + client.CreateIfNotExistsAsync(); + // might wanna make this async + return client; }); services.AddSingleton(); From 0d380e2532874b62617878c841f8f1d7ef374857 Mon Sep 17 00:00:00 2001 From: Tyrrellion Date: Tue, 27 May 2025 10:33:18 +0100 Subject: [PATCH 7/7] feat: responding to comments --- .env.example | 5 ++- compose.yaml | 14 ++----- .../EnvironmentVariables.cs | 22 ++++++++++ .../Configuration/AppConfiguration.cs | 9 ++-- .../Functions/FileExtractFunction.cs | 19 ++++----- .../Functions/FileRetryFunction.cs | 8 ++-- .../Functions/FileTransformFunction.cs | 4 +- src/ServiceLayer.Mesh/Program.cs | 41 +++++++++---------- .../Storage/MeshFilesBlobStore.cs | 19 +++++++-- 9 files changed, 81 insertions(+), 60 deletions(-) create mode 100644 src/ServiceLayer.Common/EnvironmentVariables.cs diff --git a/.env.example b/.env.example index 5f73c2b..07bec7a 100644 --- a/.env.example +++ b/.env.example @@ -19,11 +19,12 @@ QueueUrl=http://127.0.0.1:10001 FileExtractQueueName=file-extract FileTransformQueueName=file-transform StaleHours=12 -BlobContainerName=blob-container +BlobContainerName=incoming-mesh-files + # API Configuration API_PORT=7071 -MESH_PORT=7072 +MESH_INGEST_PORT=7072 # Event Grid Configuration EVENT_GRID_TOPIC_URL=https://localhost:60101/api/events diff --git a/compose.yaml b/compose.yaml index 9711321..ae785a8 100644 --- a/compose.yaml +++ b/compose.yaml @@ -30,8 +30,8 @@ services: networks: - backend - service-layer-mesh: - container_name: "service-layer-mesh" + mesh-ingest: + container_name: "mesh-ingest" build: context: ./Src dockerfile: ServiceLayer.MESH/Dockerfile @@ -58,7 +58,7 @@ services: ASPNETCORE_URLS: "http://0.0.0.0:8080" BlobContainerName: "${BlobContainerName}" ports: - - "${MESH_PORT}:8080" + - "${MESH_INGEST_PORT}:8080" healthcheck: test: ["CMD-SHELL", "curl -f http://localhost:8080/api/health || exit 1"] interval: 30s @@ -93,14 +93,6 @@ services: networks: - backend - tester: - image: curlimages/curl - depends_on: - - azurite - command: ["sh", "-c", "sleep 10 && curl http://azurite:10000/devstoreaccount1"] - networks: - - backend - db: container_name: "db" image: mcr.microsoft.com/mssql/server:2022-latest diff --git a/src/ServiceLayer.Common/EnvironmentVariables.cs b/src/ServiceLayer.Common/EnvironmentVariables.cs new file mode 100644 index 0000000..52a26c1 --- /dev/null +++ b/src/ServiceLayer.Common/EnvironmentVariables.cs @@ -0,0 +1,22 @@ +namespace ServiceLayer.Common; + +public static class EnvironmentVariables +{ + /// + /// Gets an environment variable by name. Throws if not found. + /// + /// The name of the environment variable. + /// The value of the environment variable. + /// Thrown when the variable is not found or is empty. + public static string GetRequired(string key) + { + var value = Environment.GetEnvironmentVariable(key); + + if (string.IsNullOrEmpty(value)) + { + throw new InvalidOperationException($"Environment variable '{key}' is not set or is empty."); + } + + return value; + } +} diff --git a/src/ServiceLayer.Mesh/Configuration/AppConfiguration.cs b/src/ServiceLayer.Mesh/Configuration/AppConfiguration.cs index 0b2d0d0..08799cd 100644 --- a/src/ServiceLayer.Mesh/Configuration/AppConfiguration.cs +++ b/src/ServiceLayer.Mesh/Configuration/AppConfiguration.cs @@ -1,3 +1,5 @@ +using ServiceLayer.Common; + namespace ServiceLayer.Mesh.Configuration; public class AppConfiguration : @@ -19,12 +21,7 @@ public class AppConfiguration : private static string GetRequired(string key) { - var value = Environment.GetEnvironmentVariable(key); - - if (string.IsNullOrEmpty(value)) - { - throw new InvalidOperationException($"Environment variable '{key}' is not set or is empty."); - } + var value = EnvironmentVariables.GetRequired(key); return value; } diff --git a/src/ServiceLayer.Mesh/Functions/FileExtractFunction.cs b/src/ServiceLayer.Mesh/Functions/FileExtractFunction.cs index 1aa8dee..e0d7ed4 100644 --- a/src/ServiceLayer.Mesh/Functions/FileExtractFunction.cs +++ b/src/ServiceLayer.Mesh/Functions/FileExtractFunction.cs @@ -2,7 +2,6 @@ using Microsoft.Azure.Functions.Worker; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -using Microsoft.VisualBasic; using NHS.MESH.Client.Contracts.Services; using ServiceLayer.Data; using ServiceLayer.Data.Models; @@ -24,30 +23,30 @@ public class FileExtractFunction( [Function("FileExtractFunction")] public async Task Run([QueueTrigger("%FileExtractQueueName%")] FileExtractQueueMessage message) { - logger.LogInformation("{functionName} started at: {time}", nameof(FileExtractFunction), DateTime.UtcNow); + logger.LogInformation("{functionName} started.", nameof(FileExtractFunction)); await using var transaction = await serviceLayerDbContext.Database.BeginTransactionAsync(); - var file = await GetFileAsync(serviceLayerDbContext, message.FileId); + var file = await GetFileAsync(message.FileId); if (file == null || !IsFileSuitableForExtraction(file)) { return; } - await UpdateFileStatusForExtraction(serviceLayerDbContext, file); + await UpdateFileStatusForExtraction(file); await transaction.CommitAsync(); try { - await ProcessFileExtraction(serviceLayerDbContext, file, message); + await ProcessFileExtraction(file, message); } catch (Exception ex) { - await HandleExtractionError(serviceLayerDbContext, file, message, ex); + await HandleExtractionError(file, message, ex); } } - private async Task GetFileAsync(ServiceLayerDbContext serviceLayerDbContext, string fileId) + private async Task GetFileAsync(string fileId) { var file = await serviceLayerDbContext.MeshFiles .FirstOrDefaultAsync(f => f.FileId == fileId); @@ -78,14 +77,14 @@ private bool IsFileSuitableForExtraction(MeshFile file) return true; } - private async Task UpdateFileStatusForExtraction(ServiceLayerDbContext serviceLayerDbContext, MeshFile file) + private async Task UpdateFileStatusForExtraction(MeshFile file) { file.Status = MeshFileStatus.Extracting; file.LastUpdatedUtc = DateTime.UtcNow; await serviceLayerDbContext.SaveChangesAsync(); } - private async Task ProcessFileExtraction(ServiceLayerDbContext serviceLayerDbContext, MeshFile file, FileExtractQueueMessage message) + private async Task ProcessFileExtraction(MeshFile file, FileExtractQueueMessage message) { var meshResponse = await meshInboxService.GetMessageByIdAsync(configuration.NbssMeshMailboxId, file.FileId); if (!meshResponse.IsSuccessful) @@ -109,7 +108,7 @@ private async Task ProcessFileExtraction(ServiceLayerDbContext serviceLayerDbCon await fileTransformQueueClient.EnqueueFileTransformAsync(file); } - private async Task HandleExtractionError(ServiceLayerDbContext serviceLayerDbContext, MeshFile file, FileExtractQueueMessage message, Exception ex) + private async Task HandleExtractionError(MeshFile file, FileExtractQueueMessage message, Exception ex) { logger.LogError(ex, "An exception occurred during file extraction for fileId: {fileId}", message.FileId); file.Status = MeshFileStatus.FailedExtract; diff --git a/src/ServiceLayer.Mesh/Functions/FileRetryFunction.cs b/src/ServiceLayer.Mesh/Functions/FileRetryFunction.cs index 074e6c6..439bd4a 100644 --- a/src/ServiceLayer.Mesh/Functions/FileRetryFunction.cs +++ b/src/ServiceLayer.Mesh/Functions/FileRetryFunction.cs @@ -22,11 +22,11 @@ public async Task Run([TimerTrigger("%FileRetryTimerExpression%")] TimerInfo myT var staleDateTimeUtc = DateTime.UtcNow.AddHours(-configuration.StaleHours); - await RetryStaleExtractions(serviceLayerDbContext, staleDateTimeUtc); - await RetryStaleTransformations(serviceLayerDbContext, staleDateTimeUtc); + await RetryStaleExtractions(staleDateTimeUtc); + await RetryStaleTransformations(staleDateTimeUtc); } - private async Task RetryStaleExtractions(ServiceLayerDbContext serviceLayerDbContext, DateTime staleDateTimeUtc) + private async Task RetryStaleExtractions(DateTime staleDateTimeUtc) { var staleFiles = await serviceLayerDbContext.MeshFiles .Where(f => @@ -45,7 +45,7 @@ private async Task RetryStaleExtractions(ServiceLayerDbContext serviceLayerDbCon } } - private async Task RetryStaleTransformations(ServiceLayerDbContext serviceLayerDbContext, DateTime staleDateTimeUtc) + private async Task RetryStaleTransformations(DateTime staleDateTimeUtc) { var staleFiles = await serviceLayerDbContext.MeshFiles .Where(f => diff --git a/src/ServiceLayer.Mesh/Functions/FileTransformFunction.cs b/src/ServiceLayer.Mesh/Functions/FileTransformFunction.cs index 9402061..2f31afd 100644 --- a/src/ServiceLayer.Mesh/Functions/FileTransformFunction.cs +++ b/src/ServiceLayer.Mesh/Functions/FileTransformFunction.cs @@ -34,7 +34,7 @@ public async Task Run([QueueTrigger("%FileTransformQueueName%")] FileTransformQu return; } - await UpdateFileStatusForTransformation(serviceLayerDbContext, file); + await UpdateFileStatusForTransformation(file); await transaction.CommitAsync(); var fileContent = await meshFileBlobStore.DownloadAsync(file); @@ -44,7 +44,7 @@ public async Task Run([QueueTrigger("%FileTransformQueueName%")] FileTransformQu // to handle the functionality that differs between file types. } - private async Task UpdateFileStatusForTransformation(ServiceLayerDbContext serviceLayerDbContext, MeshFile file) + private async Task UpdateFileStatusForTransformation(MeshFile file) { file.Status = MeshFileStatus.Transforming; file.LastUpdatedUtc = DateTime.UtcNow; diff --git a/src/ServiceLayer.Mesh/Program.cs b/src/ServiceLayer.Mesh/Program.cs index 00ad4df..21f470c 100644 --- a/src/ServiceLayer.Mesh/Program.cs +++ b/src/ServiceLayer.Mesh/Program.cs @@ -9,50 +9,50 @@ using ServiceLayer.Mesh.Messaging; using ServiceLayer.Data; using ServiceLayer.Mesh.Storage; +using ServiceLayer.Common; var host = new HostBuilder() .ConfigureFunctionsWebApplication() .ConfigureServices(services => { - var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); + var environment = EnvironmentVariables.GetRequired("ASPNETCORE_ENVIRONMENT"); var isLocalEnvironment = environment == "Development"; // MESH Client config services - .AddMeshClient(_ => _.MeshApiBaseUrl = Environment.GetEnvironmentVariable("MeshApiBaseUrl")) - .AddMailbox(Environment.GetEnvironmentVariable("NbssMailboxId"), new NHS.MESH.Client.Configuration.MailboxConfiguration + .AddMeshClient(_ => _.MeshApiBaseUrl = EnvironmentVariables.GetRequired("MeshApiBaseUrl")) + .AddMailbox(EnvironmentVariables.GetRequired("NbssMailboxId"), new NHS.MESH.Client.Configuration.MailboxConfiguration { - Password = Environment.GetEnvironmentVariable("MeshPassword"), - SharedKey = Environment.GetEnvironmentVariable("MeshSharedKey"), + Password = EnvironmentVariables.GetRequired("MeshPassword"), + SharedKey = EnvironmentVariables.GetRequired("MeshSharedKey"), }).Build(); // EF Core DbContext services.AddDbContext(options => { - var connectionString = Environment.GetEnvironmentVariable("DatabaseConnectionString"); + var connectionString = EnvironmentVariables.GetRequired("DatabaseConnectionString"); if (string.IsNullOrEmpty(connectionString)) throw new InvalidOperationException("The connection string has not been initialized."); options.UseSqlServer(connectionString); }); + var queueClientOptions = new QueueClientOptions + { + MessageEncoding = QueueMessageEncoding.Base64 + }; + // Register QueueClients as singletons services.AddSingleton(provider => { if (isLocalEnvironment) { - var connectionString = Environment.GetEnvironmentVariable("AzureWebJobsStorage"); - return new QueueServiceClient(connectionString, new QueueClientOptions - { - MessageEncoding = QueueMessageEncoding.Base64 - }); + var connectionString = EnvironmentVariables.GetRequired("AzureWebJobsStorage"); + return new QueueServiceClient(connectionString, queueClientOptions); } - var meshStorageAccountUrl = Environment.GetEnvironmentVariable("MeshStorageAccountUrl"); - return new QueueServiceClient(new Uri(meshStorageAccountUrl), new DefaultAzureCredential(), new QueueClientOptions - { - MessageEncoding = QueueMessageEncoding.Base64 - }); + var meshStorageAccountUrl = EnvironmentVariables.GetRequired("MeshStorageAccountUrl"); + return new QueueServiceClient(new Uri(meshStorageAccountUrl), new DefaultAzureCredential(), queueClientOptions); }); services.AddSingleton(); @@ -60,12 +60,9 @@ services.AddSingleton(provider => { - var client = new BlobContainerClient( - Environment.GetEnvironmentVariable("AzureWebJobsStorage"), - Environment.GetEnvironmentVariable("BlobContainerName")); - client.CreateIfNotExistsAsync(); - // might wanna make this async - return client; + return new BlobContainerClient( + EnvironmentVariables.GetRequired("AzureWebJobsStorage"), + EnvironmentVariables.GetRequired("BlobContainerName")); }); services.AddSingleton(); diff --git a/src/ServiceLayer.Mesh/Storage/MeshFilesBlobStore.cs b/src/ServiceLayer.Mesh/Storage/MeshFilesBlobStore.cs index 8a18e8f..ee707a2 100644 --- a/src/ServiceLayer.Mesh/Storage/MeshFilesBlobStore.cs +++ b/src/ServiceLayer.Mesh/Storage/MeshFilesBlobStore.cs @@ -3,18 +3,26 @@ namespace ServiceLayer.Mesh.Storage; -public class MeshFilesBlobStore(BlobContainerClient blobContainerClient) : IMeshFilesBlobStore +public class MeshFilesBlobStore : IMeshFilesBlobStore { + private BlobContainerClient _blobContainerClient; + + public MeshFilesBlobStore(BlobContainerClient blobContainerClient) + { + _blobContainerClient = blobContainerClient; + EnsureContainerExists(); + } + public async Task DownloadAsync(MeshFile file) { - var blobClient = blobContainerClient.GetBlobClient(file.BlobPath); + var blobClient = _blobContainerClient.GetBlobClient(file.BlobPath); return (await blobClient.DownloadAsync()).Value.Content; } public async Task UploadAsync(MeshFile file, byte[] data) { var blobPath = $"{file.FileType}/{file.FileId}"; - var blobClient = blobContainerClient.GetBlobClient(blobPath); + var blobClient = _blobContainerClient.GetBlobClient(blobPath); var dataStream = new MemoryStream(data); @@ -22,4 +30,9 @@ public async Task UploadAsync(MeshFile file, byte[] data) return blobPath; } + + private void EnsureContainerExists() + { + _blobContainerClient.CreateIfNotExists(); + } }