Skip to content
This repository was archived by the owner on Jul 28, 2025. It is now read-only.

Commit 0d380e2

Browse files
committed
feat: responding to comments
1 parent 69285b8 commit 0d380e2

9 files changed

Lines changed: 81 additions & 60 deletions

File tree

.env.example

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@ QueueUrl=http://127.0.0.1:10001
1919
FileExtractQueueName=file-extract
2020
FileTransformQueueName=file-transform
2121
StaleHours=12
22-
BlobContainerName=blob-container
22+
BlobContainerName=incoming-mesh-files
23+
2324

2425
# API Configuration
2526
API_PORT=7071
26-
MESH_PORT=7072
27+
MESH_INGEST_PORT=7072
2728

2829
# Event Grid Configuration
2930
EVENT_GRID_TOPIC_URL=https://localhost:60101/api/events

compose.yaml

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ services:
3030
networks:
3131
- backend
3232

33-
service-layer-mesh:
34-
container_name: "service-layer-mesh"
33+
mesh-ingest:
34+
container_name: "mesh-ingest"
3535
build:
3636
context: ./Src
3737
dockerfile: ServiceLayer.MESH/Dockerfile
@@ -58,7 +58,7 @@ services:
5858
ASPNETCORE_URLS: "http://0.0.0.0:8080"
5959
BlobContainerName: "${BlobContainerName}"
6060
ports:
61-
- "${MESH_PORT}:8080"
61+
- "${MESH_INGEST_PORT}:8080"
6262
healthcheck:
6363
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/health || exit 1"]
6464
interval: 30s
@@ -93,14 +93,6 @@ services:
9393
networks:
9494
- backend
9595

96-
tester:
97-
image: curlimages/curl
98-
depends_on:
99-
- azurite
100-
command: ["sh", "-c", "sleep 10 && curl http://azurite:10000/devstoreaccount1"]
101-
networks:
102-
- backend
103-
10496
db:
10597
container_name: "db"
10698
image: mcr.microsoft.com/mssql/server:2022-latest
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace ServiceLayer.Common;
2+
3+
public static class EnvironmentVariables
4+
{
5+
/// <summary>
6+
/// Gets an environment variable by name. Throws if not found.
7+
/// </summary>
8+
/// <param name="key">The name of the environment variable.</param>
9+
/// <returns>The value of the environment variable.</returns>
10+
/// <exception cref="InvalidOperationException">Thrown when the variable is not found or is empty.</exception>
11+
public static string GetRequired(string key)
12+
{
13+
var value = Environment.GetEnvironmentVariable(key);
14+
15+
if (string.IsNullOrEmpty(value))
16+
{
17+
throw new InvalidOperationException($"Environment variable '{key}' is not set or is empty.");
18+
}
19+
20+
return value;
21+
}
22+
}

src/ServiceLayer.Mesh/Configuration/AppConfiguration.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using ServiceLayer.Common;
2+
13
namespace ServiceLayer.Mesh.Configuration;
24

35
public class AppConfiguration :
@@ -19,12 +21,7 @@ public class AppConfiguration :
1921

2022
private static string GetRequired(string key)
2123
{
22-
var value = Environment.GetEnvironmentVariable(key);
23-
24-
if (string.IsNullOrEmpty(value))
25-
{
26-
throw new InvalidOperationException($"Environment variable '{key}' is not set or is empty.");
27-
}
24+
var value = EnvironmentVariables.GetRequired(key);
2825

2926
return value;
3027
}

src/ServiceLayer.Mesh/Functions/FileExtractFunction.cs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using Microsoft.Azure.Functions.Worker;
33
using Microsoft.EntityFrameworkCore;
44
using Microsoft.Extensions.Logging;
5-
using Microsoft.VisualBasic;
65
using NHS.MESH.Client.Contracts.Services;
76
using ServiceLayer.Data;
87
using ServiceLayer.Data.Models;
@@ -24,30 +23,30 @@ public class FileExtractFunction(
2423
[Function("FileExtractFunction")]
2524
public async Task Run([QueueTrigger("%FileExtractQueueName%")] FileExtractQueueMessage message)
2625
{
27-
logger.LogInformation("{functionName} started at: {time}", nameof(FileExtractFunction), DateTime.UtcNow);
26+
logger.LogInformation("{functionName} started.", nameof(FileExtractFunction));
2827

2928
await using var transaction = await serviceLayerDbContext.Database.BeginTransactionAsync();
3029

31-
var file = await GetFileAsync(serviceLayerDbContext, message.FileId);
30+
var file = await GetFileAsync(message.FileId);
3231
if (file == null || !IsFileSuitableForExtraction(file))
3332
{
3433
return;
3534
}
3635

37-
await UpdateFileStatusForExtraction(serviceLayerDbContext, file);
36+
await UpdateFileStatusForExtraction(file);
3837
await transaction.CommitAsync();
3938

4039
try
4140
{
42-
await ProcessFileExtraction(serviceLayerDbContext, file, message);
41+
await ProcessFileExtraction(file, message);
4342
}
4443
catch (Exception ex)
4544
{
46-
await HandleExtractionError(serviceLayerDbContext, file, message, ex);
45+
await HandleExtractionError(file, message, ex);
4746
}
4847
}
4948

50-
private async Task<MeshFile?> GetFileAsync(ServiceLayerDbContext serviceLayerDbContext, string fileId)
49+
private async Task<MeshFile?> GetFileAsync(string fileId)
5150
{
5251
var file = await serviceLayerDbContext.MeshFiles
5352
.FirstOrDefaultAsync(f => f.FileId == fileId);
@@ -78,14 +77,14 @@ private bool IsFileSuitableForExtraction(MeshFile file)
7877
return true;
7978
}
8079

81-
private async Task UpdateFileStatusForExtraction(ServiceLayerDbContext serviceLayerDbContext, MeshFile file)
80+
private async Task UpdateFileStatusForExtraction(MeshFile file)
8281
{
8382
file.Status = MeshFileStatus.Extracting;
8483
file.LastUpdatedUtc = DateTime.UtcNow;
8584
await serviceLayerDbContext.SaveChangesAsync();
8685
}
8786

88-
private async Task ProcessFileExtraction(ServiceLayerDbContext serviceLayerDbContext, MeshFile file, FileExtractQueueMessage message)
87+
private async Task ProcessFileExtraction(MeshFile file, FileExtractQueueMessage message)
8988
{
9089
var meshResponse = await meshInboxService.GetMessageByIdAsync(configuration.NbssMeshMailboxId, file.FileId);
9190
if (!meshResponse.IsSuccessful)
@@ -109,7 +108,7 @@ private async Task ProcessFileExtraction(ServiceLayerDbContext serviceLayerDbCon
109108
await fileTransformQueueClient.EnqueueFileTransformAsync(file);
110109
}
111110

112-
private async Task HandleExtractionError(ServiceLayerDbContext serviceLayerDbContext, MeshFile file, FileExtractQueueMessage message, Exception ex)
111+
private async Task HandleExtractionError(MeshFile file, FileExtractQueueMessage message, Exception ex)
113112
{
114113
logger.LogError(ex, "An exception occurred during file extraction for fileId: {fileId}", message.FileId);
115114
file.Status = MeshFileStatus.FailedExtract;

src/ServiceLayer.Mesh/Functions/FileRetryFunction.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ public async Task Run([TimerTrigger("%FileRetryTimerExpression%")] TimerInfo myT
2222

2323
var staleDateTimeUtc = DateTime.UtcNow.AddHours(-configuration.StaleHours);
2424

25-
await RetryStaleExtractions(serviceLayerDbContext, staleDateTimeUtc);
26-
await RetryStaleTransformations(serviceLayerDbContext, staleDateTimeUtc);
25+
await RetryStaleExtractions(staleDateTimeUtc);
26+
await RetryStaleTransformations(staleDateTimeUtc);
2727
}
2828

29-
private async Task RetryStaleExtractions(ServiceLayerDbContext serviceLayerDbContext, DateTime staleDateTimeUtc)
29+
private async Task RetryStaleExtractions(DateTime staleDateTimeUtc)
3030
{
3131
var staleFiles = await serviceLayerDbContext.MeshFiles
3232
.Where(f =>
@@ -45,7 +45,7 @@ private async Task RetryStaleExtractions(ServiceLayerDbContext serviceLayerDbCon
4545
}
4646
}
4747

48-
private async Task RetryStaleTransformations(ServiceLayerDbContext serviceLayerDbContext, DateTime staleDateTimeUtc)
48+
private async Task RetryStaleTransformations(DateTime staleDateTimeUtc)
4949
{
5050
var staleFiles = await serviceLayerDbContext.MeshFiles
5151
.Where(f =>

src/ServiceLayer.Mesh/Functions/FileTransformFunction.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public async Task Run([QueueTrigger("%FileTransformQueueName%")] FileTransformQu
3434
return;
3535
}
3636

37-
await UpdateFileStatusForTransformation(serviceLayerDbContext, file);
37+
await UpdateFileStatusForTransformation(file);
3838
await transaction.CommitAsync();
3939

4040
var fileContent = await meshFileBlobStore.DownloadAsync(file);
@@ -44,7 +44,7 @@ public async Task Run([QueueTrigger("%FileTransformQueueName%")] FileTransformQu
4444
// to handle the functionality that differs between file types.
4545
}
4646

47-
private async Task UpdateFileStatusForTransformation(ServiceLayerDbContext serviceLayerDbContext, MeshFile file)
47+
private async Task UpdateFileStatusForTransformation(MeshFile file)
4848
{
4949
file.Status = MeshFileStatus.Transforming;
5050
file.LastUpdatedUtc = DateTime.UtcNow;

src/ServiceLayer.Mesh/Program.cs

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,63 +9,60 @@
99
using ServiceLayer.Mesh.Messaging;
1010
using ServiceLayer.Data;
1111
using ServiceLayer.Mesh.Storage;
12+
using ServiceLayer.Common;
1213

1314
var host = new HostBuilder()
1415
.ConfigureFunctionsWebApplication()
1516
.ConfigureServices(services =>
1617
{
17-
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
18+
var environment = EnvironmentVariables.GetRequired("ASPNETCORE_ENVIRONMENT");
1819
var isLocalEnvironment = environment == "Development";
1920

2021
// MESH Client config
2122
services
22-
.AddMeshClient(_ => _.MeshApiBaseUrl = Environment.GetEnvironmentVariable("MeshApiBaseUrl"))
23-
.AddMailbox(Environment.GetEnvironmentVariable("NbssMailboxId"), new NHS.MESH.Client.Configuration.MailboxConfiguration
23+
.AddMeshClient(_ => _.MeshApiBaseUrl = EnvironmentVariables.GetRequired("MeshApiBaseUrl"))
24+
.AddMailbox(EnvironmentVariables.GetRequired("NbssMailboxId"), new NHS.MESH.Client.Configuration.MailboxConfiguration
2425
{
25-
Password = Environment.GetEnvironmentVariable("MeshPassword"),
26-
SharedKey = Environment.GetEnvironmentVariable("MeshSharedKey"),
26+
Password = EnvironmentVariables.GetRequired("MeshPassword"),
27+
SharedKey = EnvironmentVariables.GetRequired("MeshSharedKey"),
2728
}).Build();
2829

2930
// EF Core DbContext
3031
services.AddDbContext<ServiceLayerDbContext>(options =>
3132
{
32-
var connectionString = Environment.GetEnvironmentVariable("DatabaseConnectionString");
33+
var connectionString = EnvironmentVariables.GetRequired("DatabaseConnectionString");
3334
if (string.IsNullOrEmpty(connectionString))
3435
throw new InvalidOperationException("The connection string has not been initialized.");
3536

3637
options.UseSqlServer(connectionString);
3738
});
3839

40+
var queueClientOptions = new QueueClientOptions
41+
{
42+
MessageEncoding = QueueMessageEncoding.Base64
43+
};
44+
3945
// Register QueueClients as singletons
4046
services.AddSingleton(provider =>
4147
{
4248
if (isLocalEnvironment)
4349
{
44-
var connectionString = Environment.GetEnvironmentVariable("AzureWebJobsStorage");
45-
return new QueueServiceClient(connectionString, new QueueClientOptions
46-
{
47-
MessageEncoding = QueueMessageEncoding.Base64
48-
});
50+
var connectionString = EnvironmentVariables.GetRequired("AzureWebJobsStorage");
51+
return new QueueServiceClient(connectionString, queueClientOptions);
4952
}
5053

51-
var meshStorageAccountUrl = Environment.GetEnvironmentVariable("MeshStorageAccountUrl");
52-
return new QueueServiceClient(new Uri(meshStorageAccountUrl), new DefaultAzureCredential(), new QueueClientOptions
53-
{
54-
MessageEncoding = QueueMessageEncoding.Base64
55-
});
54+
var meshStorageAccountUrl = EnvironmentVariables.GetRequired("MeshStorageAccountUrl");
55+
return new QueueServiceClient(new Uri(meshStorageAccountUrl), new DefaultAzureCredential(), queueClientOptions);
5656
});
5757

5858
services.AddSingleton<IFileExtractQueueClient, FileExtractQueueClient>();
5959
services.AddSingleton<IFileTransformQueueClient, FileTransformQueueClient>();
6060

6161
services.AddSingleton(provider =>
6262
{
63-
var client = new BlobContainerClient(
64-
Environment.GetEnvironmentVariable("AzureWebJobsStorage"),
65-
Environment.GetEnvironmentVariable("BlobContainerName"));
66-
client.CreateIfNotExistsAsync();
67-
// might wanna make this async
68-
return client;
63+
return new BlobContainerClient(
64+
EnvironmentVariables.GetRequired("AzureWebJobsStorage"),
65+
EnvironmentVariables.GetRequired("BlobContainerName"));
6966
});
7067

7168
services.AddSingleton<IMeshFilesBlobStore, MeshFilesBlobStore>();

src/ServiceLayer.Mesh/Storage/MeshFilesBlobStore.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,36 @@
33

44
namespace ServiceLayer.Mesh.Storage;
55

6-
public class MeshFilesBlobStore(BlobContainerClient blobContainerClient) : IMeshFilesBlobStore
6+
public class MeshFilesBlobStore : IMeshFilesBlobStore
77
{
8+
private BlobContainerClient _blobContainerClient;
9+
10+
public MeshFilesBlobStore(BlobContainerClient blobContainerClient)
11+
{
12+
_blobContainerClient = blobContainerClient;
13+
EnsureContainerExists();
14+
}
15+
816
public async Task<Stream> DownloadAsync(MeshFile file)
917
{
10-
var blobClient = blobContainerClient.GetBlobClient(file.BlobPath);
18+
var blobClient = _blobContainerClient.GetBlobClient(file.BlobPath);
1119
return (await blobClient.DownloadAsync()).Value.Content;
1220
}
1321

1422
public async Task<string> UploadAsync(MeshFile file, byte[] data)
1523
{
1624
var blobPath = $"{file.FileType}/{file.FileId}";
17-
var blobClient = blobContainerClient.GetBlobClient(blobPath);
25+
var blobClient = _blobContainerClient.GetBlobClient(blobPath);
1826

1927
var dataStream = new MemoryStream(data);
2028

2129
await blobClient.UploadAsync(dataStream, overwrite: true);
2230

2331
return blobPath;
2432
}
33+
34+
private void EnsureContainerExists()
35+
{
36+
_blobContainerClient.CreateIfNotExists();
37+
}
2538
}

0 commit comments

Comments
 (0)