This repository was archived by the owner on Jul 28, 2025. It is now read-only.
generated from nhs-england-tools/repository-template
-
Notifications
You must be signed in to change notification settings - Fork 1
feat: DTOSS-8116 - Created BS-Select post endpoint #2
Merged
+733
−11
Merged
Changes from 15 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
28bdb0a
feat: Created an endpoint for BSSelect to post data to
alex-clayton-1 67bec63
test: Added unit tests for BSSelectFunctions
alex-clayton-1 4816b4f
refactor: Renamed method/function and modified error messages
alex-clayton-1 7994374
docs: Added OpenAPI spec and API request collection
alex-clayton-1 5129981
docs: Updated readme
alex-clayton-1 08a85b9
chore: Fix file formatting issues and made port number consistent
alex-clayton-1 bcf190f
style: Fixed file formatting issue
alex-clayton-1 9300393
fix solution name
ianfnelson 8956787
fix solution name again
ianfnelson 714b196
fix solution file name
ianfnelson 97dd6ad
no web to be analysed
ianfnelson 5fec2b3
refactor: Removed unused variable
alex-clayton-1 48e068c
feat: Introduced validation attribute for DateOnly to have better con…
alex-clayton-1 3595704
refactor: specify CultureInfo when parsing DateOnly and added Attribu…
alex-clayton-1 7f7d0fa
ci: Added Dockerfile
alex-clayton-1 67b9fd0
refactor: added gitleaks-report.json to ignore file and switched log …
alex-clayton-1 f9f0e50
tests: Refactored tests to improve maintainability
alex-clayton-1 e3c6736
test: Reused class level episode in remaining test methods
alex-clayton-1 dd3e61b
chore: Updated NuGet package versions
alex-clayton-1 08e1d21
test: Improved test to assert cloudevent contents
alex-clayton-1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
59 changes: 59 additions & 0 deletions
59
api/api-request-collection/servicelayer-api-2025-04-04.har
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| { | ||
| "log": { | ||
| "version": "1.2", | ||
| "creator": { | ||
| "name": "Insomnia REST Client", | ||
| "version": "insomnia.desktop.app:v11.0.1" | ||
| }, | ||
| "entries": [ | ||
| { | ||
| "startedDateTime": "2025-04-04T09:29:36.496Z", | ||
| "time": 0, | ||
| "request": { | ||
| "method": "POST", | ||
| "url": "http://localhost:7001/api/bsselect/episodes/ingress", | ||
| "httpVersion": "HTTP/1.1", | ||
| "cookies": [], | ||
| "headers": [ | ||
| { | ||
| "name": "Content-Type", | ||
| "value": "application/json" | ||
| } | ||
| ], | ||
| "queryString": [], | ||
| "postData": { | ||
| "mimeType": "application/json", | ||
| "text": "{\n\t\"episode_id\": \"123\",\n\t\"nhs_number\": \"9990000000\",\n\t\"date_of_birth\": \"1970-01-01\",\n\t\"first_given_name\": \"Test\",\n\t\"family_name\": \"User\"\n}" | ||
| }, | ||
| "headersSize": -1, | ||
| "bodySize": -1 | ||
| }, | ||
| "response": { | ||
| "status": 0, | ||
| "statusText": "", | ||
| "httpVersion": "HTTP/1.1", | ||
| "cookies": [], | ||
| "headers": [], | ||
| "content": { | ||
| "size": 0, | ||
| "mimeType": "" | ||
| }, | ||
| "redirectURL": "", | ||
| "headersSize": -1, | ||
| "bodySize": -1 | ||
| }, | ||
| "cache": {}, | ||
| "timings": { | ||
| "blocked": -1, | ||
| "dns": -1, | ||
| "connect": -1, | ||
| "send": 0, | ||
| "wait": 0, | ||
| "receive": 0, | ||
| "ssl": -1 | ||
| }, | ||
| "comment": "BS Select Episode Ingress" | ||
| } | ||
| ] | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| openapi: 3.0.3 | ||
| info: | ||
| title: Service Layer API | ||
| version: 1.0.0 | ||
| description: API used to ingest episodes from screening services into NSP | ||
|
|
||
| paths: | ||
|
|
||
| /bsselect/episodes/ingress: | ||
| post: | ||
| summary: BS Select Episode Ingress | ||
| description: Validates the incoming BS Select episode and enqueues it for further processing within the NSP | ||
| operationId: BS Select Episode Ingress | ||
| tags: | ||
| - Episodes | ||
| requestBody: | ||
| required: true | ||
| content: | ||
| application/json: | ||
| schema: | ||
| $ref: "#/components/schemas/BSSelectEpisode" | ||
| responses: | ||
| '200': | ||
| description: Episode accepted | ||
| content: | ||
| application/json: | ||
| schema: | ||
| $ref: "#/components/schemas/BSSelectEpisode" | ||
| '400': | ||
| description: Bad request. Supplied episode payload invalid. | ||
| content: | ||
| text/plain: | ||
| schema: | ||
| type: string | ||
| example: "nhs_number is required" | ||
| '500': | ||
| description: Internal server error. This indicates an unexpected failure in the service. | ||
|
|
||
| components: | ||
| schemas: | ||
| BSSelectEpisode: | ||
| type: object | ||
| required: | ||
| - episode_id | ||
| - nhs_number | ||
| - date_of_birth | ||
| - first_given_name | ||
| - family_name | ||
| properties: | ||
| episode_id: | ||
| type: string | ||
| description: Unique identifier for the Episode | ||
| nhs_number: | ||
| type: string | ||
| pattern: '^\d{10}$' | ||
| description: NHS Number (exactly 10 digits) | ||
| date_of_birth: | ||
| type: string | ||
| format: date | ||
| description: Date of birth of the Participant | ||
| first_given_name: | ||
| type: string | ||
| maxLength: 100 | ||
| description: First name of the Participant | ||
| family_name: | ||
| type: string | ||
| maxLength: 100 | ||
| description: Surname of the Participant |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| [] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| FROM mcr.microsoft.com/dotnet/sdk:9.0 AS installer-env | ||
| WORKDIR /src/dotnet-function-app | ||
|
|
||
| COPY ./ServiceLayer.API/ServiceLayer.API.csproj . | ||
| RUN dotnet restore | ||
|
|
||
| COPY ./ServiceLayer.API/ . | ||
|
|
||
| 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"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| using System.ComponentModel.DataAnnotations; | ||
| using System.Globalization; | ||
| using System.Text.Json; | ||
| using Azure.Messaging; | ||
| using Azure.Messaging.EventGrid; | ||
| using Microsoft.AspNetCore.Mvc; | ||
| using Microsoft.Azure.Functions.Worker; | ||
| using Microsoft.Azure.Functions.Worker.Http; | ||
| using Microsoft.Extensions.Logging; | ||
| using ServiceLayer.API.Models; | ||
| using ServiceLayer.API.Shared; | ||
|
|
||
| namespace ServiceLayer.API.Functions; | ||
|
|
||
| public class BSSelectFunctions(ILogger<BSSelectFunctions> logger, EventGridPublisherClient eventGridPublisherClient) | ||
| { | ||
| [Function("BSSelectIngressEpisode")] | ||
| public async Task<IActionResult> IngressEpisode([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "bsselect/episodes/ingress")] HttpRequestData req) | ||
| { | ||
| BSSelectEpisode? bssEpisodeEvent; | ||
|
|
||
| try | ||
| { | ||
| bssEpisodeEvent = await JsonSerializer.DeserializeAsync<BSSelectEpisode>(req.Body); | ||
|
|
||
| if (bssEpisodeEvent == null) | ||
| { | ||
| logger.LogError("Deserialization returned null"); | ||
| return new BadRequestObjectResult("Deserialization returned null"); | ||
| } | ||
|
|
||
| var validationContext = new ValidationContext(bssEpisodeEvent); | ||
|
|
||
| Validator.ValidateObject(bssEpisodeEvent, validationContext, true); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| logger.LogError(ex, "An error occured when reading request body"); | ||
| return new BadRequestObjectResult(ex.Message); | ||
| } | ||
|
|
||
| try | ||
| { | ||
| var createPathwayEnrolment = new CreatePathwayParticipantDto | ||
| { | ||
| PathwayTypeId = new Guid("11111111-1111-1111-1111-111111111113"), | ||
| PathwayTypeName = "Breast Screening Routine", | ||
| ScreeningName = "Breast Screening", | ||
| NhsNumber = bssEpisodeEvent.NhsNumber!, | ||
| DOB = DateOnly.Parse(bssEpisodeEvent.DateOfBirth!, CultureInfo.CurrentCulture), | ||
| Name = $"{bssEpisodeEvent.FirstGivenName} {bssEpisodeEvent.FamilyName}", | ||
| }; | ||
|
|
||
| var cloudEvent = new CloudEvent( | ||
| "ServiceLayer", | ||
| "EpisodeEvent", | ||
| createPathwayEnrolment | ||
| ); | ||
|
|
||
| var response = await eventGridPublisherClient.SendEventAsync(cloudEvent); | ||
|
|
||
| if (response.IsError) | ||
| { | ||
| logger.LogError( | ||
| "Failed to send event to Event Grid.\nSource: {source}\nType: {type}\n Response status code: {code}", | ||
|
Check warning on line 65 in src/ServiceLayer.API/Functions/BSSelectFunctions.cs
|
||
alex-clayton-1 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| cloudEvent.Source, cloudEvent.Type, response.Status); | ||
| return new StatusCodeResult(500); | ||
| } | ||
|
|
||
| return new OkResult(); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| logger.LogError(ex, "Failed to send event to Event Grid"); | ||
| return new StatusCodeResult(500); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| using System.ComponentModel.DataAnnotations; | ||
| using System.Text.Json.Serialization; | ||
| using ServiceLayer.API.Shared; | ||
|
|
||
| namespace ServiceLayer.API.Models; | ||
|
|
||
| public class BSSelectEpisode | ||
| { | ||
| [JsonPropertyName("episode_id")] | ||
| [Required(ErrorMessage = "episode_id is required")] | ||
| public string? EpisodeId { get; set; } | ||
|
|
||
| [JsonPropertyName("nhs_number")] | ||
| [Required(ErrorMessage = "nhs_number is required")] | ||
| [RegularExpression(@"^\d{10}$", ErrorMessage = "nhs_number must be exactly 10 digits")] | ||
| public string? NhsNumber { get; set; } | ||
|
|
||
| [JsonPropertyName("date_of_birth")] | ||
| [Required(ErrorMessage = "date_of_birth is required")] | ||
| [ValidDateOnly(ErrorMessage = "date_of_birth is invalid")] | ||
| public string? DateOfBirth { get; set; } | ||
|
|
||
| [JsonPropertyName("first_given_name")] | ||
| [Required(ErrorMessage = "first_given_name is required")] | ||
| public string? FirstGivenName { get; set; } | ||
|
|
||
| [JsonPropertyName("family_name")] | ||
| [Required(ErrorMessage = "family_name is required")] | ||
| public string? FamilyName { get; set; } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| using Azure.Identity; | ||
| using Azure.Messaging.EventGrid; | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using Microsoft.Extensions.Hosting; | ||
|
|
||
| var eventGridTopicUrl = Environment.GetEnvironmentVariable("EVENT_GRID_TOPIC_URL") | ||
| ?? throw new InvalidOperationException($"Environment variable 'EVENT_GRID_TOPIC_URL' is not set or is empty."); | ||
| var eventGridTopicKey = Environment.GetEnvironmentVariable("EVENT_GRID_TOPIC_KEY") | ||
| ?? throw new InvalidOperationException($"Environment variable 'EVENT_GRID_TOPIC_KEY' is not set or is empty."); | ||
|
|
||
| var host = new HostBuilder() | ||
| .ConfigureFunctionsWebApplication() | ||
| .ConfigureServices((context, services) => | ||
| { | ||
| services.AddSingleton(sp => | ||
| { | ||
| var endpoint = new Uri(eventGridTopicUrl); | ||
| if (context.HostingEnvironment.IsDevelopment()) | ||
| { | ||
| var credentials = new Azure.AzureKeyCredential(eventGridTopicKey); | ||
| return new EventGridPublisherClient(endpoint, credentials); | ||
| } | ||
|
|
||
| return new EventGridPublisherClient(endpoint, new ManagedIdentityCredential()); | ||
| }); | ||
| }) | ||
| .Build(); | ||
|
|
||
| await host.RunAsync(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "profiles": { | ||
| "ServiceLayer.API": { | ||
| "commandName": "Project", | ||
| "commandLineArgs": "--port 7001", | ||
| "launchBrowser": false | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.