diff --git a/LearningHub.Nhs.MessagingService/Interfaces/IGovNotifyService.cs b/LearningHub.Nhs.MessagingService/Interfaces/IGovNotifyService.cs new file mode 100644 index 000000000..23d0d3738 --- /dev/null +++ b/LearningHub.Nhs.MessagingService/Interfaces/IGovNotifyService.cs @@ -0,0 +1,29 @@ +namespace LearningHub.Nhs.MessagingService.Interfaces +{ + using System.Collections.Generic; + using System.Threading.Tasks; + + /// + /// IMessageServices. + /// + public interface IGovNotifyService + { + /// + /// Send EmailAsync. + /// + /// email. + /// templateId. + /// personalisation. + /// The . + Task SendEmailAsync(string email, string templateId, Dictionary personalisation); + + /// + /// Send SmsAsync. + /// + /// phoneNumber. + /// templateId. + /// personalisation. + /// The . + Task SendSmsAsync(string phoneNumber, string templateId, Dictionary personalisation); + } +} diff --git a/LearningHub.Nhs.MessagingService/LearningHub.Nhs.MessagingService.csproj b/LearningHub.Nhs.MessagingService/LearningHub.Nhs.MessagingService.csproj new file mode 100644 index 000000000..d21593e42 --- /dev/null +++ b/LearningHub.Nhs.MessagingService/LearningHub.Nhs.MessagingService.csproj @@ -0,0 +1,16 @@ + + + + net8.0 + enable + true + + + + + + + + + + diff --git a/LearningHub.Nhs.MessagingService/Model/MessagingServiceModel.cs b/LearningHub.Nhs.MessagingService/Model/MessagingServiceModel.cs new file mode 100644 index 000000000..66a3f210e --- /dev/null +++ b/LearningHub.Nhs.MessagingService/Model/MessagingServiceModel.cs @@ -0,0 +1,21 @@ +namespace LearningHub.Nhs.MessagingService.Model +{ + /// + /// MessagingServiceModel. + /// + public class MessagingServiceModel + { + /// + /// Initializes a new instance of the class. + /// + public MessagingServiceModel() + { + // Current = this; + } + + /// + /// Gets or Sets ApiKey. + /// + public string GovNotifyApiKey { get; set; } = string.Empty; + } +} diff --git a/LearningHub.Nhs.MessagingService/Model/SendEmailRequest.cs b/LearningHub.Nhs.MessagingService/Model/SendEmailRequest.cs new file mode 100644 index 000000000..da0044425 --- /dev/null +++ b/LearningHub.Nhs.MessagingService/Model/SendEmailRequest.cs @@ -0,0 +1,28 @@ +namespace LearningHub.Nhs.MessagingService.Model +{ + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + + /// + /// Defines the . + /// + public class SendEmailRequest + { + /// + /// Gets or Sets the Email. + /// + [Required] + public string Email { get; set; } + + /// + /// Gets or Sets the TemplateId. + /// + [Required] + public string TemplateId { get; set; } + + /// + /// Gets or Sets the Personalisation. + /// + public Dictionary Personalisation { get; set; } + } +} diff --git a/LearningHub.Nhs.MessagingService/Model/SendSmsRequest.cs b/LearningHub.Nhs.MessagingService/Model/SendSmsRequest.cs new file mode 100644 index 000000000..cb2481cfe --- /dev/null +++ b/LearningHub.Nhs.MessagingService/Model/SendSmsRequest.cs @@ -0,0 +1,28 @@ +namespace LearningHub.Nhs.MessagingService.Model +{ + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + + /// + /// Defines the . + /// + public class SendSmsRequest + { + /// + /// Gets or Sets the PhoneNumber. + /// + [Required] + public string PhoneNumber { get; set; } + + /// + /// Gets or Sets the TemplateId. + /// + [Required] + public string TemplateId { get; set; } + + /// + /// Gets or Sets the Personalisation. + /// + public Dictionary Personalisation { get; set; } + } +} diff --git a/LearningHub.Nhs.MessagingService/Services/GovNotifyService.cs b/LearningHub.Nhs.MessagingService/Services/GovNotifyService.cs new file mode 100644 index 000000000..b81c8e7b2 --- /dev/null +++ b/LearningHub.Nhs.MessagingService/Services/GovNotifyService.cs @@ -0,0 +1,91 @@ +namespace LearningHub.Nhs.MessagingService.Services +{ + using System; + using System.Collections.Generic; + using System.Text.Json; + using System.Threading.Tasks; + using LearningHub.Nhs.MessagingService.Interfaces; + using LearningHub.Nhs.MessagingService.Model; + using Microsoft.Extensions.Options; + using Notify.Client; + + /// + /// GovNotify Service class. + /// + public class GovNotifyService : IGovNotifyService + { + private readonly NotificationClient client; + + /// + /// Initializes a new instance of the class. + /// GovNotifyService. + /// + /// The Messaging Service Model. + public GovNotifyService(IOptions options) + { + this.client = new NotificationClient(options.Value.GovNotifyApiKey); + } + + /// + /// Sends an email via the UK Gov.Notify service. + /// + /// The recipient's email address. + /// The ID of the Gov.Notify template to be used for the email. + /// + /// A dictionary containing key-value pairs for personalising the email template. + /// Keys should match the placeholders in the Gov.Notify template. + /// + /// + /// A unique message ID representing the queued email. + /// + public async Task SendEmailAsync(string email, string templateId, Dictionary personalisation) + { + var normalisedPersonlisation = new Dictionary(); + foreach (var item in personalisation) + { + if (item.Value is JsonElement element) + { + normalisedPersonlisation[item.Key] = element.ToString(); + } + else + { + normalisedPersonlisation[item.Key] = item.Value; + } + } + + var response = await this.client.SendEmailAsync(email, templateId, normalisedPersonlisation); + return response.id; + } + + /// + /// Sends an SMS via the UK Gov.Notify service. + /// + /// The recipient's phone number. + /// The ID of the Gov.Notify template to be used for the SMS. + /// + /// A dictionary containing key-value pairs for personalising the SMS template. + /// Keys should match the placeholders in the Gov.Notify template. + /// + /// + /// A unique message ID representing the queued SMS. + /// + public async Task SendSmsAsync(string phoneNumber, string templateId, Dictionary personalisation) + { + var normalisedPersonlisation = new Dictionary(); + foreach (var item in personalisation) + { + if (item.Value is JsonElement element) + { + normalisedPersonlisation[item.Key] = element.ToString(); + } + else + { + normalisedPersonlisation[item.Key] = item.Value; + } + } + + var response = await this.client.SendSmsAsync(phoneNumber, templateId, normalisedPersonlisation); + return response.id; + } + } +} diff --git a/LearningHub.Nhs.MessagingService/Startup.cs b/LearningHub.Nhs.MessagingService/Startup.cs new file mode 100644 index 000000000..522b98bda --- /dev/null +++ b/LearningHub.Nhs.MessagingService/Startup.cs @@ -0,0 +1,25 @@ +namespace LearningHub.Nhs.MessagingService +{ + using LearningHub.Nhs.MessagingService.Interfaces; + using LearningHub.Nhs.MessagingService.Model; + using LearningHub.Nhs.MessagingService.Services; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + + /// + /// Contains methods to configure the project to be called in an API startup class. + /// + public static class Startup + { + /// + /// Registers the implementations in the project with ASP.NET DI. + /// + /// The IServiceCollection. + /// The IConfiguration. + public static void AddMessagingServices(this IServiceCollection services, IConfiguration configuration) + { + services.Configure(configuration.GetSection("GovNotify")); + services.AddScoped(); + } + } +} diff --git a/LearningHub.Nhs.WebUI.sln b/LearningHub.Nhs.WebUI.sln index 5aea6885f..e4ca88071 100644 --- a/LearningHub.Nhs.WebUI.sln +++ b/LearningHub.Nhs.WebUI.sln @@ -81,6 +81,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LearningHub.Nhs.ReportApi.S EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LearningHub.Nhs.WebUI.AutomatedUiTests", "LearningHub.Nhs.WebUI.AutomatedUiTests\LearningHub.Nhs.WebUI.AutomatedUiTests.csproj", "{A84EC50B-2B01-4819-A2B1-BD867B7595CA}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MessagingService", "MessagingService", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LearningHub.Nhs.MessagingService", "LearningHub.Nhs.MessagingService\LearningHub.Nhs.MessagingService.csproj", "{713B0099-60E3-4D28-980F-448FC68BC7EE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -345,6 +349,14 @@ Global {A84EC50B-2B01-4819-A2B1-BD867B7595CA}.Release|Any CPU.Build.0 = Release|Any CPU {A84EC50B-2B01-4819-A2B1-BD867B7595CA}.Release|x64.ActiveCfg = Release|Any CPU {A84EC50B-2B01-4819-A2B1-BD867B7595CA}.Release|x64.Build.0 = Release|Any CPU + {713B0099-60E3-4D28-980F-448FC68BC7EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {713B0099-60E3-4D28-980F-448FC68BC7EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {713B0099-60E3-4D28-980F-448FC68BC7EE}.Debug|x64.ActiveCfg = Debug|Any CPU + {713B0099-60E3-4D28-980F-448FC68BC7EE}.Debug|x64.Build.0 = Debug|Any CPU + {713B0099-60E3-4D28-980F-448FC68BC7EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {713B0099-60E3-4D28-980F-448FC68BC7EE}.Release|Any CPU.Build.0 = Release|Any CPU + {713B0099-60E3-4D28-980F-448FC68BC7EE}.Release|x64.ActiveCfg = Release|Any CPU + {713B0099-60E3-4D28-980F-448FC68BC7EE}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -380,6 +392,7 @@ Global {E585A74A-F358-4446-B10E-0FF07B4FF601} = {A4209011-1740-4902-B889-2F2FAF98383F} {AAC8306E-1DEB-460D-84C7-40166D189B88} = {A4209011-1740-4902-B889-2F2FAF98383F} {6167F037-166C-4C5A-81BE-55618E77D4E8} = {A4209011-1740-4902-B889-2F2FAF98383F} + {713B0099-60E3-4D28-980F-448FC68BC7EE} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1ECA38C8-7C69-4DE6-8293-852C603F4217} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/GovNotifyMessagingController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/GovNotifyMessagingController.cs new file mode 100644 index 000000000..05066a292 --- /dev/null +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/GovNotifyMessagingController.cs @@ -0,0 +1,78 @@ +namespace LearningHub.NHS.OpenAPI.Controllers +{ + using System; + using System.Threading.Tasks; + using LearningHub.Nhs.MessagingService.Interfaces; + using LearningHub.Nhs.MessagingService.Model; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + + /// + /// GovNotify Messaging Controller. + /// + [Route("GovNotifyMessage")] + [Authorize] + public class GovNotifyMessagingController : OpenApiControllerBase + { + private readonly IGovNotifyService messageService; + + /// + /// Initializes a new instance of the class. + /// + /// The catalogue service. + public GovNotifyMessagingController(IGovNotifyService messageService) + { + this.messageService = messageService; + } + + /// + /// Sends an email using UK Gov.Notify. + /// + /// personalisation. + /// The . + [Route("sendemail")] + [HttpPost] + public async Task SendEmailAsync([FromBody] SendEmailRequest request) + { + try + { + if (string.IsNullOrWhiteSpace(request.Email) || string.IsNullOrWhiteSpace(request.TemplateId)) + { + return this.BadRequest("Email and template ID are required"); + } + + var response = await this.messageService.SendEmailAsync(request.Email, request.TemplateId, request.Personalisation); + return this.Ok(response); + } + catch (Exception ex) + { + return this.Ok(ex.Message); + } + } + + /// + /// Sends a sms using UK Gov.Notify. + /// + /// SendSmsRequest. + /// The . + [Route("sendsms")] + [HttpPost] + public async Task SendSmsAsync([FromBody] SendSmsRequest request) + { + try + { + if (string.IsNullOrWhiteSpace(request.PhoneNumber) || string.IsNullOrWhiteSpace(request.TemplateId)) + { + return this.BadRequest("phoneNumber and template ID are required"); + } + + var response = await this.messageService.SendSmsAsync(request.PhoneNumber, request.TemplateId, request.Personalisation); + return this.Ok(response); + } + catch (Exception ex) + { + return this.Ok(ex.Message); + } + } + } +} diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj index 83639e186..6ed354944 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj @@ -31,6 +31,7 @@ + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs index 627401b12..f3dc3f51b 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs @@ -30,6 +30,7 @@ namespace LearningHub.NHS.OpenAPI using System.Configuration; using System; using LearningHub.Nhs.Models.Extensions; + using LearningHub.Nhs.MessagingService; /// /// The Startup class. @@ -84,6 +85,7 @@ public void ConfigureServices(IServiceCollection services) services.AddApplicationInsightsTelemetry(); services.AddControllers(options => options.Filters.Add(new HttpResponseExceptionFilter())); services.AddControllers(opt => { opt.Filters.Add(new AuthorizeFilter()); }); + services.AddMessagingServices(this.Configuration); services.AddSwaggerGen( c => { diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json b/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json index 0cde7cef1..9633b6117 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json +++ b/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json @@ -6033,6 +6033,85 @@ } } } + }, + "/GovNotifyMessage/sendsms": { + "post": { + "tags": [ + "SendSms" + ], + "summary": "Send an SMS.", + "required": [ "mobileNumber", "templateId" ], + "requestBody": { + "description": "The Sends an SMS using the UK Gov.Notify service.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SendSmsRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SendSmsRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/SendSmsRequest" + } + } + } + }, + "responses": { + "200": { + "description": "SMS successfully sent." + }, + "400": { + "description": "Bad request, invalid input." + }, + "500": { + "description": "Internal server error." + } + } + } + }, + "/GovNotifyMessage/sendemail": { + "post": { + "tags": [ + "SendEmail" + ], + "summary": "Send an Email.", + "requestBody": { + "description": "Sends an email using the UK Gov.Notify service.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SendEmailRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SendEmailRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/SendEmailRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Email successfully sent." + }, + "400": { + "description": "Bad request, invalid input." + }, + "500": { + "description": "Internal server error." + } + } + } } }, "components": { @@ -14138,6 +14217,51 @@ } }, "additionalProperties": false + }, + "SendSmsRequest": { + "type": "object", + "required": [ "mobileNumber", "templateId" ], + "properties": { + "mobileNumber": { + "type": "string", + "nullable": false, + "description": "The recipient's mobile number." + }, + "templateId": { + "type": "string", + "nullable": false, + "description": "The ID of the template to be used for the email." + }, + "personalisation": { + "type": "object", + "additionalProperties:": { + "type": "string" + }, + "description": "Key-value pairs for personalising the sms template." + } + } + }, + "SendEmailRequest": { + "type": "object", + "properties": { + "emailAddress": { + "type": "string", + "nullable": false, + "description": "The recipient's email address." + }, + "templateId": { + "type": "string", + "nullable": false, + "description": "The ID of the template to be used for the email." + }, + "personalisation": { + "type": "object", + "additionalProperties:": { + "type": "string" + }, + "description": "Key-value pairs for personalising the email template." + } + } } }, "securitySchemes": { diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json index 92f23e650..56c507cff 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json +++ b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json @@ -84,5 +84,8 @@ "ResponseType": "code id_token", "AuthSecret": "", "AuthTimeout": 20 + }, + "GovNotify": { + "GovNotifyApiKey": "" } }