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

Commit 28bdb0a

Browse files
feat: Created an endpoint for BSSelect to post data to
1 parent bc5d2a2 commit 28bdb0a

File tree

9 files changed

+212
-0
lines changed

9 files changed

+212
-0
lines changed

src/.gitkeep

Whitespace-only changes.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using System.Text.Json;
3+
using Azure.Messaging;
4+
using Azure.Messaging.EventGrid;
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.AspNetCore.Mvc;
7+
using Microsoft.Azure.Functions.Worker;
8+
using Microsoft.Extensions.Logging;
9+
using ServiceLayer.API.Models;
10+
using ServiceLayer.API.Shared;
11+
12+
namespace ServiceLayer.API.Functions;
13+
14+
public class BSSelectFunctions(ILogger<BSSelectFunctions> logger, EventGridPublisherClient eventGridPublisherClient)
15+
{
16+
[Function("CreateEpisodeEvent")]
17+
public async Task<IActionResult> CreateEpisodeEvent([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "bsselect/episodes")] HttpRequest req)
18+
{
19+
BSSelectEpisodeEvent? bssEpisodeEvent;
20+
21+
try
22+
{
23+
bssEpisodeEvent = await JsonSerializer.DeserializeAsync<BSSelectEpisodeEvent>(req.Body);
24+
25+
if (bssEpisodeEvent == null)
26+
{
27+
return new BadRequestObjectResult("Deserialization resulted in null.");
28+
}
29+
30+
var validationResults = new List<ValidationResult>();
31+
var validationContext = new ValidationContext(bssEpisodeEvent);
32+
33+
Validator.ValidateObject(bssEpisodeEvent, validationContext, true);
34+
}
35+
catch (Exception ex)
36+
{
37+
logger.LogError(ex, "An error occured when reading request body");
38+
return new BadRequestObjectResult(ex.Message);
39+
}
40+
41+
try
42+
{
43+
var createPathwayEnrolment = new CreatePathwayParticipantDto
44+
{
45+
NhsNumber = bssEpisodeEvent.NhsNumber,
46+
DOB = bssEpisodeEvent.DateOfBirth,
47+
Name = $"{bssEpisodeEvent.FirstGivenName} {bssEpisodeEvent.FamilyName}",
48+
ScreeningName = "Breast Screening",
49+
PathwayTypeName = "Breast Screening Routine"
50+
};
51+
52+
var cloudEvent = new CloudEvent(
53+
"ServiceLayer",
54+
"CreateBrestScreeningPathwayEnrolment",
55+
createPathwayEnrolment
56+
);
57+
58+
var response = await eventGridPublisherClient.SendEventAsync(cloudEvent);
59+
60+
if (response.IsError)
61+
{
62+
logger.LogError(
63+
"Failed to send event to Event Grid.\nSource: {source}\nType: {type}\n Response status code: {code}",
64+
cloudEvent.Source, cloudEvent.Type, response.Status);
65+
return new StatusCodeResult(500);
66+
}
67+
68+
return new OkResult();
69+
}
70+
catch (Exception ex)
71+
{
72+
logger.LogError(ex, "Failed to send CreateBrestScreeningPathwayEnrolment event");
73+
return new StatusCodeResult(500);
74+
}
75+
}
76+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using System.Text.Json.Serialization;
3+
4+
namespace ServiceLayer.API.Models;
5+
6+
public class BSSelectEpisodeEvent
7+
{
8+
[JsonPropertyName("nhs_number")]
9+
[RegularExpression(@"^\d{10}$", ErrorMessage = "nhs_number must be exactly 10 digits.")]
10+
public required string NhsNumber { get; set; }
11+
12+
[JsonPropertyName("date_of_birth")]
13+
public required DateOnly DateOfBirth { get; set; }
14+
15+
[JsonPropertyName("first_given_name")]
16+
public required string FirstGivenName { get; set; }
17+
18+
[JsonPropertyName("family_name")]
19+
public required string FamilyName { get; set; }
20+
}

src/ServiceLayer.API/Program.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using Azure.Identity;
2+
using Azure.Messaging.EventGrid;
3+
using Microsoft.Extensions.DependencyInjection;
4+
using Microsoft.Extensions.Hosting;
5+
6+
var eventGridTopicUrl = Environment.GetEnvironmentVariable("EVENT_GRID_TOPIC_URL")
7+
?? throw new InvalidOperationException($"Environment variable 'EVENT_GRID_TOPIC_URL' is not set or is empty.");
8+
var eventGridTopicKey = Environment.GetEnvironmentVariable("EVENT_GRID_TOPIC_KEY")
9+
?? throw new InvalidOperationException($"Environment variable 'EVENT_GRID_TOPIC_KEY' is not set or is empty.");
10+
11+
var host = new HostBuilder()
12+
.ConfigureFunctionsWebApplication()
13+
.ConfigureServices((context, services) =>
14+
{
15+
services.AddSingleton(sp =>
16+
{
17+
var endpoint = new Uri(eventGridTopicUrl);
18+
if (context.HostingEnvironment.IsDevelopment())
19+
{
20+
var credentials = new Azure.AzureKeyCredential(eventGridTopicKey);
21+
return new EventGridPublisherClient(endpoint, credentials);
22+
}
23+
24+
return new EventGridPublisherClient(endpoint, new ManagedIdentityCredential());
25+
});
26+
})
27+
.Build();
28+
29+
await host.RunAsync();
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"profiles": {
3+
"ServiceLayer.API": {
4+
"commandName": "Project",
5+
"commandLineArgs": "--port 7065",
6+
"launchBrowser": false
7+
}
8+
}
9+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net9.0</TargetFramework>
4+
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
5+
<OutputType>Exe</OutputType>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
<ItemGroup>
10+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
11+
<PackageReference Include="Azure.Identity" Version="1.13.2" />
12+
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="2.0.0" />
13+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.EventGrid" Version="3.4.2" />
14+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.2.0" />
15+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="2.0.1" />
16+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.0" />
17+
</ItemGroup>
18+
<ItemGroup>
19+
<None Update="host.json">
20+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
21+
</None>
22+
<None Update="local.settings.json">
23+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
24+
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
25+
</None>
26+
</ItemGroup>
27+
<ItemGroup>
28+
<Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext" />
29+
</ItemGroup>
30+
</Project>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace ServiceLayer.API.Shared;
2+
3+
public class CreatePathwayParticipantDto
4+
{
5+
public required string PathwayTypeName { get; set; }
6+
public required string ScreeningName { get; set; }
7+
public DateOnly DOB { get; set; }
8+
public required string NhsNumber { get; set; }
9+
public required string Name { get; set; }
10+
}

src/ServiceLayer.API/host.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"version": "2.0",
3+
"logging": {
4+
"applicationInsights": {
5+
"samplingSettings": {
6+
"isEnabled": true,
7+
"excludedTypes": "Request"
8+
},
9+
"enableLiveMetricsFilters": true
10+
}
11+
}
12+
}

src/ServiceLayer.sln

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Microsoft Visual Studio Solution File, Format Version 12.00
2+
# Visual Studio Version 17
3+
VisualStudioVersion = 17.5.2.0
4+
MinimumVisualStudioVersion = 10.0.40219.1
5+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceLayer.API", "ServiceLayer.API\ServiceLayer.API.csproj", "{B56B41FF-FA39-0FDE-E266-6EC09B268DFB}"
6+
EndProject
7+
Global
8+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
9+
Debug|Any CPU = Debug|Any CPU
10+
Release|Any CPU = Release|Any CPU
11+
EndGlobalSection
12+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
13+
{B56B41FF-FA39-0FDE-E266-6EC09B268DFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
14+
{B56B41FF-FA39-0FDE-E266-6EC09B268DFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
15+
{B56B41FF-FA39-0FDE-E266-6EC09B268DFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
16+
{B56B41FF-FA39-0FDE-E266-6EC09B268DFB}.Release|Any CPU.Build.0 = Release|Any CPU
17+
EndGlobalSection
18+
GlobalSection(SolutionProperties) = preSolution
19+
HideSolutionNode = FALSE
20+
EndGlobalSection
21+
GlobalSection(NestedProjects) = preSolution
22+
EndGlobalSection
23+
GlobalSection(ExtensibilityGlobals) = postSolution
24+
SolutionGuid = {EEE06B13-019F-4618-A6EB-FD834B6EA7D7}
25+
EndGlobalSection
26+
EndGlobal

0 commit comments

Comments
 (0)