Skip to content

Commit b17125a

Browse files
feat: 8380-NEMS-Unsubscribe-function (#927)
* feat: 8380-NEMS-unsubscribe with mesh fixed and review comment incorporation in continuatiion with the closed PR 916 * feat: 8380-added-compose-yaml-config * feat: terraform config for the new function * feat: terraform config for the new function - rename * feat: 8380-udating compose and docker fix * feat: 8380-removing dockerfile * feat: 8380-adding dockerfile again * feat: 8380-review comment updates * feat: 8380-review comments updates 2 * feat: 8380-removing un-necessary comments * feat: 8380-adding data service integration * feat: 8380-adding WIP comments * feat: 8380-removing http mock request and response dependencies in the unit tests * feat: 8380-added necessary comments --------- Co-authored-by: Maciej Murawski <[email protected]>
1 parent 8ff065e commit b17125a

File tree

15 files changed

+565
-0
lines changed

15 files changed

+565
-0
lines changed

application/CohortManager/compose.core.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,18 @@ services:
329329
- ExceptionFunctionURL=http://create-exception:7070/api/CreateException
330330
- RetrievePdsParticipantURL=https://sandbox.api.service.nhs.uk/personal-demographics/FHIR/R4/Patient
331331

332+
nems-unsubscribe:
333+
container_name: nems-unsubscribe
334+
image: cohort-manager-nems-unsubscribe
335+
networks: [cohman-network]
336+
build:
337+
context: ./src/Functions/
338+
dockerfile: DemographicServices/NEMSUnSubscription/Dockerfile
339+
profiles: [not-implemented]
340+
environment:
341+
- ASPNETCORE_URLS=http://*:9082
342+
- ExceptionFunctionURL=http://create-exception:7070/api/CreateException
343+
- ParticipantDemographicDataServiceURL=http://participant-demographic-data-service:7993/api/ParticipantDemographicDataService
332344
durable-demographic-function:
333345
container_name: durable-demographic-function
334346
image: cohort-manager-durable-demographic-function
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS base
2+
3+
COPY ./Shared /Shared
4+
WORKDIR /Shared
5+
6+
RUN mkdir -p /home/site/wwwroot && \
7+
dotnet publish ./Common/Common.csproj --output /home/site/wwwroot && \
8+
dotnet publish ./Model/Model.csproj --output /home/site/wwwroot && \
9+
dotnet publish ./Data/Data.csproj --output /home/site/wwwroot && \
10+
dotnet publish ./Utilities/Utilities.csproj --output /home/site/wwwroot && \
11+
dotnet publish ./DataServices.Client/DataServices.Client.csproj --output /home/site/wwwroot && \
12+
dotnet publish ./DataServices.Core/DataServices.Core.csproj --output /home/site/wwwroot && \
13+
dotnet publish ./DataServices.Database/DataServices.Database.csproj --output /home/site/wwwroot
14+
15+
FROM base AS function
16+
17+
COPY ./DemographicServices/NEMSUnSubscription /src/dotnet-function-app
18+
WORKDIR /src/dotnet-function-app
19+
20+
RUN dotnet publish *.csproj --output /home/site/wwwroot
21+
22+
# To enable ssh & remote debugging on app service change the base image to the one below
23+
# FROM mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0-appservice
24+
FROM mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated8.0
25+
ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
26+
AzureFunctionsJobHost__Logging__Console__IsEnabled=true
27+
28+
COPY --from=function ["/home/site/wwwroot", "/home/site/wwwroot"]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
namespace NHS.CohortManager.NEMSUnSubscription;
2+
3+
using Microsoft.Azure.Functions.Worker;
4+
using Microsoft.Azure.Functions.Worker.Http;
5+
using Microsoft.Extensions.Diagnostics.HealthChecks;
6+
using System.Threading.Tasks;
7+
using HealthChecks.Extensions;
8+
9+
public class HealthCheckFunction
10+
{
11+
private readonly HealthCheckService _healthCheckService;
12+
13+
public HealthCheckFunction(HealthCheckService healthCheckService)
14+
{
15+
_healthCheckService = healthCheckService;
16+
}
17+
18+
[Function("health")]
19+
public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequestData req)
20+
{
21+
return await HealthCheckServiceExtensions.CreateHealthCheckResponseAsync(req, _healthCheckService);
22+
}
23+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
namespace NHS.CohortManager.DemographicServices.NEMSUnSubscription;
2+
3+
using Microsoft.Azure.Functions.Worker;
4+
using Microsoft.Azure.Functions.Worker.Http;
5+
using Microsoft.Extensions.Logging;
6+
using System.Net;
7+
using System.Net.Http;
8+
using System.Text.Json;
9+
using Azure.Data.Tables;
10+
using Azure;
11+
using Model;
12+
using Common;
13+
using Microsoft.Extensions.Options;
14+
using NHS.Screening.NEMSUnSubscription;
15+
16+
public class NEMSUnSubscription
17+
{
18+
private const string TableName = "NemsSubscriptionTable";
19+
protected readonly TableClient _tableClient;
20+
protected readonly HttpClient _httpClient;
21+
22+
private readonly ILogger<NEMSUnSubscription> _logger;
23+
24+
private readonly ICreateResponse _createResponse;
25+
private readonly IExceptionHandler _handleException;
26+
private readonly NEMSUnSubscriptionConfig _config;
27+
28+
private readonly INemsSubscriptionService _nemsSubscriptionService;
29+
30+
31+
protected virtual async Task<HttpResponseData> HandleNotFoundAsync(HttpRequestData req, string message)
32+
{
33+
var response = req.CreateResponse(HttpStatusCode.NotFound);
34+
await response.WriteStringAsync(message);
35+
return response;
36+
}
37+
38+
public NEMSUnSubscription(ILogger<NEMSUnSubscription> logger,
39+
//IDataServiceClient<NemsSubscription> nemsSubscriptionClient, /* To Do Later */
40+
IHttpClientFactory httpClientFactory,
41+
IExceptionHandler handleException,
42+
ICreateResponse createResponse ,
43+
IOptions<NEMSUnSubscriptionConfig> nemsUnSubscriptionConfig,
44+
ICallFunction callFunction,
45+
INemsSubscriptionService nemsSubscriptionService)
46+
{
47+
_logger = logger;
48+
_httpClient = httpClientFactory.CreateClient();
49+
_handleException = handleException;
50+
_createResponse = createResponse;
51+
_config = nemsUnSubscriptionConfig.Value;
52+
_nemsSubscriptionService = nemsSubscriptionService;
53+
54+
}
55+
// Constructor for dependency injection (testability)
56+
public NEMSUnSubscription(TableClient tableClient, HttpClient httpClient)
57+
{
58+
_tableClient = tableClient;
59+
_httpClient = httpClient;
60+
}
61+
62+
[Function("NEMSUnsubscribe")]
63+
public async Task<HttpResponseData> Run(
64+
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
65+
FunctionContext executionContext)
66+
{
67+
_logger.LogInformation("Received unsubscribe request");
68+
69+
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
70+
71+
if (string.IsNullOrWhiteSpace(requestBody))
72+
{
73+
var badRequestResponse = req.CreateResponse(HttpStatusCode.BadRequest);
74+
await badRequestResponse.WriteStringAsync("Request body is empty.");
75+
return badRequestResponse;
76+
}
77+
78+
var request = JsonSerializer.Deserialize<UnsubscriptionRequest>(requestBody);
79+
80+
if (request == null || string.IsNullOrEmpty(request.NhsNumber))
81+
{
82+
var badRequest = req.CreateResponse(HttpStatusCode.BadRequest);
83+
await badRequest.WriteStringAsync("Invalid or missing NHS number.");
84+
return badRequest;
85+
}
86+
87+
string nhsNumber = request.NhsNumber;
88+
string? subscriptionId = await _nemsSubscriptionService.LookupSubscriptionIdAsync(nhsNumber);
89+
90+
if (string.IsNullOrEmpty(subscriptionId))
91+
{
92+
_logger.LogWarning("No subscription record found.");
93+
var notFoundResponse = req.CreateResponse(HttpStatusCode.NotFound);
94+
await notFoundResponse.WriteStringAsync("No subscription record found.");
95+
return notFoundResponse;
96+
}
97+
98+
bool isDeletedFromNems = await _nemsSubscriptionService.DeleteSubscriptionFromNems(subscriptionId);
99+
100+
if (!isDeletedFromNems)
101+
{
102+
_logger.LogError("Failed to delete subscription from NEMS.");
103+
var badGatewayResponse = req.CreateResponse(HttpStatusCode.BadGateway);
104+
await badGatewayResponse.WriteStringAsync("Failed to delete subscription from NEMS.");
105+
return badGatewayResponse;
106+
}
107+
108+
await _nemsSubscriptionService.DeleteSubscriptionFromTableAsync(nhsNumber);
109+
_logger.LogInformation("Subscription deleted successfully.");
110+
111+
var successResponse = req.CreateResponse(HttpStatusCode.OK);
112+
await successResponse.WriteStringAsync("Successfully unsubscribed.");
113+
return successResponse;
114+
}
115+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net8.0</TargetFramework>
4+
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
5+
<OutputType>Exe</OutputType>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
<ItemGroup>
10+
<PackageReference Include="Azure.Data.Tables" Version="12.10.0" />
11+
<PackageReference Include="Hl7.Fhir.R4" Version="5.11.4" />
12+
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="2.0.0" />
13+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.2.0" />
14+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="2.0.0" />
15+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.2" />
16+
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.4" />
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+
<ItemGroup>
31+
<ProjectReference Include="..\..\Shared\Common\Common.csproj" />
32+
<ProjectReference Include="..\..\Shared\Data\Data.csproj" />
33+
</ItemGroup>
34+
<ItemGroup>
35+
<ProjectReference Include="..\..\Shared\HealthChecks\HealthChecks.csproj" />
36+
<ProjectReference Include="..\..\DataServices.Core\DataServices.Core.csproj" />
37+
<ProjectReference Include="..\..\Shared\Model\Model.csproj" />
38+
<ProjectReference Include="..\..\Shared\Common\Common.csproj" />
39+
</ItemGroup>
40+
</Project>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace NHS.Screening.NEMSUnSubscription;
2+
3+
using System.ComponentModel.DataAnnotations;
4+
5+
public class NEMSUnSubscriptionConfig
6+
{
7+
[Required]
8+
public string NhsNumber { get; set; }
9+
public string NemsDeleteEndpoint { get; set; }
10+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
namespace NHS.CohortManager.DemographicServices.NEMSUnSubscription;
2+
3+
using System;
4+
using System.Linq;
5+
using System.Threading.Tasks;
6+
using Azure;
7+
using Azure.Data.Tables;
8+
using Microsoft.Extensions.Configuration;
9+
using Microsoft.Extensions.Logging;
10+
using System.Net.Http;
11+
using DataServices.Client;
12+
using Microsoft.Extensions.Options;
13+
using Model;
14+
using NHS.Screening.NEMSUnSubscription;
15+
16+
public interface INemsSubscriptionService
17+
{
18+
Task<string?> LookupSubscriptionIdAsync(string nhsNumber);
19+
Task<bool> DeleteSubscriptionFromNems(string subscriptionId);
20+
Task<bool> DeleteSubscriptionFromTableAsync(string nhsNumber);
21+
}
22+
23+
public class NemsSubscriptionService : INemsSubscriptionService
24+
{
25+
private readonly TableClient _tableClient;
26+
private readonly HttpClient _httpClient;
27+
private readonly ILogger<NemsSubscriptionService> _logger;
28+
private readonly NEMSUnSubscriptionConfig _config;
29+
private readonly IDataServiceClient<NemsSubscription> _nemsSubscriptionClient;
30+
31+
public NemsSubscriptionService(
32+
TableClient tableClient,
33+
HttpClient httpClient,
34+
IOptions<NEMSUnSubscriptionConfig> nemsUnSubscriptionConfig,
35+
ILogger<NemsSubscriptionService> logger,
36+
IDataServiceClient<NemsSubscription> nemsSubscriptionClient)
37+
{
38+
_tableClient = tableClient;
39+
_httpClient = httpClient;
40+
_config = nemsUnSubscriptionConfig.Value;
41+
_logger = logger;
42+
_nemsSubscriptionClient = nemsSubscriptionClient;
43+
}
44+
45+
//WIP as there would be changes in the method after we gets proper NEMS API endpoints
46+
public async Task<string?> LookupSubscriptionIdAsync(string nhsNumber)
47+
{
48+
try
49+
{
50+
Pageable<TableEntity> queryResults = _tableClient.Query<TableEntity>(e => e.RowKey == nhsNumber);
51+
var entity = queryResults.FirstOrDefault();
52+
return entity?.GetString("SubscriptionId");
53+
}
54+
catch (RequestFailedException ex)
55+
{
56+
_logger.LogError(ex, "Error querying table for NHS number {NhsNumber}", nhsNumber);
57+
return null;
58+
}
59+
}
60+
61+
//WIP as there would be changes in the method after we gets proper NEMS API endpoints
62+
public async Task<bool> DeleteSubscriptionFromNems(string subscriptionId)
63+
{
64+
try
65+
{
66+
string nemsEndpoint = _config.NemsDeleteEndpoint;
67+
var response = await _httpClient.DeleteAsync($"{nemsEndpoint}/{subscriptionId}");
68+
return response.IsSuccessStatusCode;
69+
}
70+
catch (Exception ex)
71+
{
72+
_logger.LogError(ex, "Error calling NEMS API to delete subscription ID {SubscriptionId}", subscriptionId);
73+
return false;
74+
}
75+
}
76+
77+
public async Task<bool> DeleteSubscriptionFromTableAsync(string nhsNumber)
78+
{
79+
try
80+
{
81+
_logger.LogInformation("Attempting to delete subscription for NHS number {NhsNumber}", nhsNumber);
82+
83+
var deleted = await _nemsSubscriptionClient.Delete(nhsNumber);
84+
85+
if (deleted)
86+
{
87+
_logger.LogInformation("Successfully deleted the subscription");
88+
return true;
89+
}
90+
91+
_logger.LogError("Failed to delete the subscription");
92+
return false;
93+
}
94+
catch (Exception ex)
95+
{
96+
_logger.LogError(ex, "Exception occurred while deleting the subscription for NHS number {NhsNumber}", nhsNumber);
97+
return false;
98+
}
99+
}
100+
101+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using Common;
2+
using Microsoft.Azure.Functions.Worker.Builder;
3+
using Microsoft.Extensions.DependencyInjection;
4+
using Microsoft.Extensions.Hosting;
5+
using Model;
6+
using NHS.Screening.NEMSUnSubscription;
7+
8+
var host = new HostBuilder()
9+
.AddConfiguration<NEMSUnSubscriptionConfig>(out NEMSUnSubscriptionConfig config)
10+
.ConfigureFunctionsWebApplication()
11+
.ConfigureServices(services =>
12+
{
13+
services.AddSingleton<ICreateResponse, CreateResponse>();
14+
services.AddHttpClient();
15+
})
16+
.Build();
17+
18+
await host.RunAsync();
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Model;
2+
3+
public class UnsubscriptionRequest
4+
{
5+
public string NhsNumber { get; set; }
6+
}
7+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"version": "2.0",
3+
"functionTimeout": "01:00:00",
4+
"logging": {
5+
"applicationInsights": {
6+
"samplingExcludedTypes": "Request",
7+
"samplingSettings": {
8+
"isEnabled": true
9+
}
10+
}
11+
}
12+
}

0 commit comments

Comments
 (0)