diff --git a/.gitignore b/.gitignore index fcd06e014..5bc784140 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,5 @@ obj /AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj.user /WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj.user /ReportAPI/LearningHub.Nhs.ReportApi/web.config +/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/LearningHub.Nhs.MessageQueueing.Database.dbmdl +/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/LearningHub.Nhs.MessageQueueing.Database.jfm \ No newline at end of file diff --git a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj index eba6b6ffa..2038ce016 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj +++ b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj @@ -89,7 +89,7 @@ - + diff --git a/LearningHub.Nhs.MessagingService/Model/SendEmailRequest.cs b/LearningHub.Nhs.MessagingService/Model/SendEmailRequest.cs deleted file mode 100644 index da0044425..000000000 --- a/LearningHub.Nhs.MessagingService/Model/SendEmailRequest.cs +++ /dev/null @@ -1,28 +0,0 @@ -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 deleted file mode 100644 index cb2481cfe..000000000 --- a/LearningHub.Nhs.MessagingService/Model/SendSmsRequest.cs +++ /dev/null @@ -1,28 +0,0 @@ -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 deleted file mode 100644 index b81c8e7b2..000000000 --- a/LearningHub.Nhs.MessagingService/Services/GovNotifyService.cs +++ /dev/null @@ -1,91 +0,0 @@ -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.WebUI.sln b/LearningHub.Nhs.WebUI.sln index e4ca88071..43f5211d5 100644 --- a/LearningHub.Nhs.WebUI.sln +++ b/LearningHub.Nhs.WebUI.sln @@ -83,7 +83,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LearningHub.Nhs.WebUI.Autom 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}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MessageQueueing", "MessageQueueing", "{FC592E2B-861F-4C9A-BD1A-95CB97D36285}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LearningHub.Nhs.MessageQueueing", "MessageQueueing\LearningHub.Nhs.MessageQueueing\LearningHub.Nhs.MessageQueueing.csproj", "{534A145F-1FE4-B601-48FF-979744373E4B}" +EndProject +Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "LearningHub.Nhs.MessageQueueing.Database", "MessageQueueing\LearningHub.Nhs.MessageQueueing.Database\LearningHub.Nhs.MessageQueueing.Database.sqlproj", "{AFC1A740-BBA1-44BC-8A52-A65D2E506E69}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LearningHub.Nhs.MessagingService", "MessagingService\LearningHub.Nhs.MessagingService\LearningHub.Nhs.MessagingService.csproj", "{CCB52C7C-47B6-1AE7-7578-7A26B3FFEB71}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -349,14 +355,34 @@ 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 + {534A145F-1FE4-B601-48FF-979744373E4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {534A145F-1FE4-B601-48FF-979744373E4B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {534A145F-1FE4-B601-48FF-979744373E4B}.Debug|x64.ActiveCfg = Debug|Any CPU + {534A145F-1FE4-B601-48FF-979744373E4B}.Debug|x64.Build.0 = Debug|Any CPU + {534A145F-1FE4-B601-48FF-979744373E4B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {534A145F-1FE4-B601-48FF-979744373E4B}.Release|Any CPU.Build.0 = Release|Any CPU + {534A145F-1FE4-B601-48FF-979744373E4B}.Release|x64.ActiveCfg = Release|Any CPU + {534A145F-1FE4-B601-48FF-979744373E4B}.Release|x64.Build.0 = Release|Any CPU + {AFC1A740-BBA1-44BC-8A52-A65D2E506E69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AFC1A740-BBA1-44BC-8A52-A65D2E506E69}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AFC1A740-BBA1-44BC-8A52-A65D2E506E69}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {AFC1A740-BBA1-44BC-8A52-A65D2E506E69}.Debug|x64.ActiveCfg = Debug|Any CPU + {AFC1A740-BBA1-44BC-8A52-A65D2E506E69}.Debug|x64.Build.0 = Debug|Any CPU + {AFC1A740-BBA1-44BC-8A52-A65D2E506E69}.Debug|x64.Deploy.0 = Debug|Any CPU + {AFC1A740-BBA1-44BC-8A52-A65D2E506E69}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AFC1A740-BBA1-44BC-8A52-A65D2E506E69}.Release|Any CPU.Build.0 = Release|Any CPU + {AFC1A740-BBA1-44BC-8A52-A65D2E506E69}.Release|Any CPU.Deploy.0 = Release|Any CPU + {AFC1A740-BBA1-44BC-8A52-A65D2E506E69}.Release|x64.ActiveCfg = Release|Any CPU + {AFC1A740-BBA1-44BC-8A52-A65D2E506E69}.Release|x64.Build.0 = Release|Any CPU + {AFC1A740-BBA1-44BC-8A52-A65D2E506E69}.Release|x64.Deploy.0 = Release|Any CPU + {CCB52C7C-47B6-1AE7-7578-7A26B3FFEB71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CCB52C7C-47B6-1AE7-7578-7A26B3FFEB71}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CCB52C7C-47B6-1AE7-7578-7A26B3FFEB71}.Debug|x64.ActiveCfg = Debug|Any CPU + {CCB52C7C-47B6-1AE7-7578-7A26B3FFEB71}.Debug|x64.Build.0 = Debug|Any CPU + {CCB52C7C-47B6-1AE7-7578-7A26B3FFEB71}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CCB52C7C-47B6-1AE7-7578-7A26B3FFEB71}.Release|Any CPU.Build.0 = Release|Any CPU + {CCB52C7C-47B6-1AE7-7578-7A26B3FFEB71}.Release|x64.ActiveCfg = Release|Any CPU + {CCB52C7C-47B6-1AE7-7578-7A26B3FFEB71}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -392,7 +418,9 @@ 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} + {534A145F-1FE4-B601-48FF-979744373E4B} = {FC592E2B-861F-4C9A-BD1A-95CB97D36285} + {AFC1A740-BBA1-44BC-8A52-A65D2E506E69} = {FC592E2B-861F-4C9A-BD1A-95CB97D36285} + {CCB52C7C-47B6-1AE7-7578-7A26B3FFEB71} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1ECA38C8-7C69-4DE6-8293-852C603F4217} diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj index 1b4ac83d1..fa0dca0f8 100644 --- a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj +++ b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj @@ -113,7 +113,7 @@ - + diff --git a/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/LearningHub.Nhs.MessageQueueing.Database.sqlproj b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/LearningHub.Nhs.MessageQueueing.Database.sqlproj new file mode 100644 index 000000000..79c159525 --- /dev/null +++ b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/LearningHub.Nhs.MessageQueueing.Database.sqlproj @@ -0,0 +1,81 @@ + + + + Debug + AnyCPU + LearningHub.Nhs.MessageQueueing.Database + 2.0 + 4.1 + {afc1a740-bba1-44bc-8a52-a65d2e506e69} + Microsoft.Data.Tools.Schema.Sql.Sql160DatabaseSchemaProvider + Database + + + LearningHub.Nhs.MessageQueueing.Database + LearningHub.Nhs.MessageQueueing.Database + 1033, CI + BySchemaAndSchemaType + True + v4.7.2 + CS + Properties + False + True + True + + + bin\Release\ + $(MSBuildProjectName).sql + False + pdbonly + true + false + true + prompt + 4 + + + bin\Debug\ + $(MSBuildProjectName).sql + false + true + full + false + true + true + prompt + 4 + + + 11.0 + + True + 11.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/LearningHub.Nhs.MessageQueueing.Database.sqlproj.user b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/LearningHub.Nhs.MessageQueueing.Database.sqlproj.user new file mode 100644 index 000000000..0b07de1b2 --- /dev/null +++ b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/LearningHub.Nhs.MessageQueueing.Database.sqlproj.user @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Scripts/Post-Deploy/Scripts/RequestStatusData.sql b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Scripts/Post-Deploy/Scripts/RequestStatusData.sql new file mode 100644 index 000000000..d09b31f81 --- /dev/null +++ b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Scripts/Post-Deploy/Scripts/RequestStatusData.sql @@ -0,0 +1,6 @@ +INSERT [dbo].[RequestStatus] ([Id], [RequestStatus]) VALUES (1, N'Pending') +GO +INSERT [dbo].[RequestStatus] ([Id], [RequestStatus]) VALUES (2, N'Sent') +GO +INSERT [dbo].[RequestStatus] ([Id], [RequestStatus]) VALUES (3, N'Failed') +GO \ No newline at end of file diff --git a/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Scripts/Post-Deploy/Scripts/RequestTypeData.sql b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Scripts/Post-Deploy/Scripts/RequestTypeData.sql new file mode 100644 index 000000000..a933ca65a --- /dev/null +++ b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Scripts/Post-Deploy/Scripts/RequestTypeData.sql @@ -0,0 +1,6 @@ +INSERT [dbo].[RequestType] ([Id], [RequestType]) VALUES (1, N'Email') +GO +INSERT [dbo].[RequestType] ([Id], [RequestType]) VALUES (2, N'SMS') +GO +INSERT [dbo].[RequestType] ([Id], [RequestType]) VALUES (3, N'SingleEmail') +GO \ No newline at end of file diff --git a/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Stored Procedures/CreateQueueRequests.sql b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Stored Procedures/CreateQueueRequests.sql new file mode 100644 index 000000000..a7d8bb494 --- /dev/null +++ b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Stored Procedures/CreateQueueRequests.sql @@ -0,0 +1,20 @@ +------------------------------------------------------------------------------- +-- Author Arunima George +-- Created 27-05-2025 +-- Purpose Create email requests. +-- +-- Modification History +-- +-- 27-05-2025 Arunima George Initial Revision +------------------------------------------------------------------------------- + +Create PROCEDURE [dbo].[CreateQueueRequests] + @QueueRequests dbo.QueueRequestTableType READONLY +AS +BEGIN + + INSERT INTO QueueRequests (RequestTypeId, Recipient, TemplateId, Personalisation, Status, RetryCount, CreatedAt, DeliverAfter) + SELECT 1, Recipient, TemplateId, Personalisation, 1, 0, SYSDATETIMEOFFSET(), DeliverAfter + FROM @QueueRequests; +END +GO diff --git a/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Stored Procedures/GetQueueRequests.sql b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Stored Procedures/GetQueueRequests.sql new file mode 100644 index 000000000..7f2ac520c --- /dev/null +++ b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Stored Procedures/GetQueueRequests.sql @@ -0,0 +1,20 @@ +------------------------------------------------------------------------------- +-- Author Arunima George +-- Created 27-05-2025 +-- Purpose Fetch pending/failed email requests from QueueRequests table. +-- +-- Modification History +-- +-- 27-05-2025 Arunima George Initial Revision +------------------------------------------------------------------------------- + +CREATE PROCEDURE [dbo].[GetQueueRequests] +AS +BEGIN + + select Id,Recipient,TemplateId,Personalisation,Status,RetryCount + from dbo.QueueRequests +where RequestTypeId = 1 and Status in (1,3) and RetryCount < 3 and (DeliverAfter is null or DeliverAfter <= SYSDATETIMEOFFSET()) + +END +GO \ No newline at end of file diff --git a/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Stored Procedures/MessageDeliveryFailed.sql b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Stored Procedures/MessageDeliveryFailed.sql new file mode 100644 index 000000000..4d00ebac4 --- /dev/null +++ b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Stored Procedures/MessageDeliveryFailed.sql @@ -0,0 +1,24 @@ +------------------------------------------------------------------------------- +-- Author Arunima George +-- Created 27-05-2025 +-- Purpose Update message request status as failed. +-- +-- Modification History +-- +-- 27-05-2025 Arunima George Initial Revision +------------------------------------------------------------------------------- + +CREATE PROCEDURE [dbo].[MessageDeliveryFailed] + @Id int, + @ErrorMessage nvarchar(max) +AS +BEGIN + UPDATE [dbo].[QueueRequests] + SET + Status = 3, + RetryCount = RetryCount + 1, + ErrorMessage = @ErrorMessage, + LastAttemptAt = SYSDATETIMEOFFSET() + where Id = @Id; +END +GO \ No newline at end of file diff --git a/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Stored Procedures/MessageDeliverySuccess.sql b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Stored Procedures/MessageDeliverySuccess.sql new file mode 100644 index 000000000..b971d912f --- /dev/null +++ b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Stored Procedures/MessageDeliverySuccess.sql @@ -0,0 +1,26 @@ +------------------------------------------------------------------------------- +-- Author Arunima George +-- Created 27-05-2025 +-- Purpose Update message request status as success. +-- +-- Modification History +-- +-- 27-05-2025 Arunima George Initial Revision +------------------------------------------------------------------------------- + +CREATE PROCEDURE [dbo].[MessageDeliverySuccess] + @Id int, + @NotificationId nvarchar(100) + +AS +BEGIN + UPDATE [dbo].[QueueRequests] + SET + Status = 2, + NotificationId = @NotificationId, + RetryCount = RetryCount + 1, + SentAt = SYSDATETIMEOFFSET(), + LastAttemptAt = SYSDATETIMEOFFSET() + where Id = @Id; +END +GO \ No newline at end of file diff --git a/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Stored Procedures/SaveFailedSingleEmail.sql b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Stored Procedures/SaveFailedSingleEmail.sql new file mode 100644 index 000000000..32a4c6395 --- /dev/null +++ b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Stored Procedures/SaveFailedSingleEmail.sql @@ -0,0 +1,21 @@ +------------------------------------------------------------------------------- +-- Author Arunima George +-- Created 27-05-2025 +-- Purpose Save one-off(like otp emails) failed email request. +-- +-- Modification History +-- +-- 27-05-2025 Arunima George Initial Revision +------------------------------------------------------------------------------- + +CREATE PROCEDURE [dbo].[SaveFailedSingleEmail] + @Recipient nvarchar(255), + @TemplateId nvarchar(50), + @Personalisation nvarchar(max), + @ErrorMessage nvarchar(max) +AS +BEGIN + insert into [dbo].[QueueRequests] (RequestTypeId, Recipient, TemplateId, Personalisation, Status, CreatedAt, LastAttemptAt,ErrorMessage ) + values (3, @Recipient, @TemplateId, @Personalisation, 3, SYSDATETIMEOFFSET(), SYSDATETIMEOFFSET(), @ErrorMessage); +END +GO diff --git a/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Tables/QueueRequests.sql b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Tables/QueueRequests.sql new file mode 100644 index 000000000..fad33bf9d --- /dev/null +++ b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Tables/QueueRequests.sql @@ -0,0 +1,34 @@ +CREATE TABLE [dbo].[QueueRequests]( + [Id] [int] IDENTITY(1,1) NOT NULL, + [RequestTypeId] [int] NOT NULL, + [Recipient] [nvarchar](255) NOT NULL, + [TemplateId] [nvarchar](50) NOT NULL, + [Personalisation] [nvarchar](max) NULL, + [Status] [int] NOT NULL, + [NotificationId] [nvarchar](100) NULL, + [RetryCount] [int] NULL, + [CreatedAt] [datetimeoffset](7) NOT NULL, + [DeliverAfter] [datetimeoffset](7) NULL, + [SentAt] [datetimeoffset](7) NULL, + [LastAttemptAt] [datetimeoffset](7) NULL, + [ErrorMessage] [nvarchar](max) NULL, + CONSTRAINT [PK_QueueRequests] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY] +) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] +GO + +ALTER TABLE [dbo].[QueueRequests] WITH CHECK ADD CONSTRAINT [FK_QueueRequests_RequestStatus] FOREIGN KEY([Status]) +REFERENCES [dbo].[RequestStatus] ([Id]) +GO + +ALTER TABLE [dbo].[QueueRequests] CHECK CONSTRAINT [FK_QueueRequests_RequestStatus] +GO + +ALTER TABLE [dbo].[QueueRequests] WITH CHECK ADD CONSTRAINT [FK_QueueRequests_RequestType] FOREIGN KEY([RequestTypeId]) +REFERENCES [dbo].[RequestType] ([Id]) +GO + +ALTER TABLE [dbo].[QueueRequests] CHECK CONSTRAINT [FK_QueueRequests_RequestType] +GO \ No newline at end of file diff --git a/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Tables/RequestStatus.sql b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Tables/RequestStatus.sql new file mode 100644 index 000000000..ce9918dc3 --- /dev/null +++ b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Tables/RequestStatus.sql @@ -0,0 +1,9 @@ +CREATE TABLE [dbo].[RequestStatus]( + [Id] [int] NOT NULL, + [RequestStatus] [nvarchar](20) NOT NULL, + CONSTRAINT [PK_RequestStatus] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY] +) ON [PRIMARY] +GO \ No newline at end of file diff --git a/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Tables/RequestType.sql b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Tables/RequestType.sql new file mode 100644 index 000000000..a9be6c049 --- /dev/null +++ b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/Tables/RequestType.sql @@ -0,0 +1,9 @@ +CREATE TABLE [dbo].[RequestType]( + [Id] [int] NOT NULL, + [RequestType] [nvarchar](20) NOT NULL, + CONSTRAINT [PK_RequestType] PRIMARY KEY CLUSTERED +( + [Id] ASC +)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY] +) ON [PRIMARY] +GO diff --git a/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/User-Defined Table Types/QueueRequestTableType.sql b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/User-Defined Table Types/QueueRequestTableType.sql new file mode 100644 index 000000000..479676b6b --- /dev/null +++ b/MessageQueueing/LearningHub.Nhs.MessageQueueing.Database/User-Defined Table Types/QueueRequestTableType.sql @@ -0,0 +1,7 @@ +CREATE TYPE [dbo].[QueueRequestTableType] AS TABLE( + [Recipient] [nvarchar](255) NOT NULL, + [TemplateId] [nvarchar](50) NOT NULL, + [Personalisation] [nvarchar](max) NULL, + [DeliverAfter] [datetimeoffset](7) NULL +) +GO diff --git a/MessageQueueing/LearningHub.Nhs.MessageQueueing/EntityFramework/MessageQueueDbContext.cs b/MessageQueueing/LearningHub.Nhs.MessageQueueing/EntityFramework/MessageQueueDbContext.cs new file mode 100644 index 000000000..5b392c8d6 --- /dev/null +++ b/MessageQueueing/LearningHub.Nhs.MessageQueueing/EntityFramework/MessageQueueDbContext.cs @@ -0,0 +1,41 @@ +namespace LearningHub.Nhs.MessageQueueing.EntityFramework +{ + using LearningHub.Nhs.Models.GovNotifyMessaging; + using Microsoft.EntityFrameworkCore; + + /// + /// The Message Queue Db Context. + /// + public class MessageQueueDbContext : DbContext + { + /// + /// The options. + /// + private readonly MessageQueueDbContextOptions options; + + /// + /// Initializes a new instance of the class. + /// + /// The options. + public MessageQueueDbContext(MessageQueueDbContextOptions options) + : base(options.Options) + { + this.options = options; + } + + /// + /// Gets the Options. + /// + public MessageQueueDbContextOptions Options + { + get { return this.options; } + } + + ////public virtual DbSet QueueRequests { get; set; } + + /// + /// Gets or sets the PendingMessageRequests. + /// + public virtual DbSet PendingMessageRequests { get; set; } + } +} diff --git a/MessageQueueing/LearningHub.Nhs.MessageQueueing/EntityFramework/MessageQueueDbContextOptions.cs b/MessageQueueing/LearningHub.Nhs.MessageQueueing/EntityFramework/MessageQueueDbContextOptions.cs new file mode 100644 index 000000000..7a6f0d985 --- /dev/null +++ b/MessageQueueing/LearningHub.Nhs.MessageQueueing/EntityFramework/MessageQueueDbContextOptions.cs @@ -0,0 +1,24 @@ +namespace LearningHub.Nhs.MessageQueueing.EntityFramework +{ + using Microsoft.EntityFrameworkCore; + + /// + /// The MessageQueueDbContextOptions. + /// + public class MessageQueueDbContextOptions + { + /// + /// Initializes a new instance of the class. + /// + /// The options. + public MessageQueueDbContextOptions(DbContextOptions options) + { + this.Options = options; + } + + /// + /// Gets the options. + /// + public DbContextOptions Options { get; } + } +} diff --git a/MessageQueueing/LearningHub.Nhs.MessageQueueing/Helpers/DataTableBuilder.cs b/MessageQueueing/LearningHub.Nhs.MessageQueueing/Helpers/DataTableBuilder.cs new file mode 100644 index 000000000..f4e278b94 --- /dev/null +++ b/MessageQueueing/LearningHub.Nhs.MessageQueueing/Helpers/DataTableBuilder.cs @@ -0,0 +1,34 @@ +namespace LearningHub.Nhs.MessageQueueing.Helpers +{ + using System; + using System.Collections.Generic; + using System.Data; + using LearningHub.Nhs.Models.Entities.GovNotifyMessaging; + + /// + /// DataTable Builder. + /// + public static class DataTableBuilder + { + /// + /// ToQueueRequestDataTable. + /// + /// The requests list. + /// The table. + public static DataTable ToQueueRequestDataTable(IEnumerable requests) + { + var table = new DataTable(); + table.Columns.Add("Recipient", typeof(string)); + table.Columns.Add("TemplateId", typeof(string)); + table.Columns.Add("Personalisation", typeof(string)); + table.Columns.Add("DeliverAfter", typeof(DateTimeOffset)); + + foreach (var req in requests) + { + table.Rows.Add(req.Recipient, req.TemplateId, req.Personalisation, req.DeliverAfter); + } + + return table; + } + } +} diff --git a/MessageQueueing/LearningHub.Nhs.MessageQueueing/LearningHub.Nhs.MessageQueueing.csproj b/MessageQueueing/LearningHub.Nhs.MessageQueueing/LearningHub.Nhs.MessageQueueing.csproj new file mode 100644 index 000000000..1d7e25a32 --- /dev/null +++ b/MessageQueueing/LearningHub.Nhs.MessageQueueing/LearningHub.Nhs.MessageQueueing.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + enable + true + + + + + + + + + diff --git a/MessageQueueing/LearningHub.Nhs.MessageQueueing/Repositories/IMessageQueueRepository.cs b/MessageQueueing/LearningHub.Nhs.MessageQueueing/Repositories/IMessageQueueRepository.cs new file mode 100644 index 000000000..110343012 --- /dev/null +++ b/MessageQueueing/LearningHub.Nhs.MessageQueueing/Repositories/IMessageQueueRepository.cs @@ -0,0 +1,47 @@ +namespace LearningHub.Nhs.MessageQueueing.Repositories +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using LearningHub.Nhs.Models.Entities.GovNotifyMessaging; + using LearningHub.Nhs.Models.GovNotifyMessaging; + + /// + /// The IEmailQueueRepository class. + /// + public interface IMessageQueueRepository + { + /// + /// The QueueMessagesAsync. + /// + /// The emails list. + /// The . + Task QueueMessagesAsync(IEnumerable emails); + + /// + /// The GetPendingEmailsAsync. + /// + /// The . + Task> GetPendingEmailsAsync(); + + /// + /// Marks a message as failed, or queues it for a retry. + /// + /// The response. + /// The . + Task MessageDeliveryFailed(GovNotifyResponse response); + + /// + /// Marks a message as send. + /// + /// The response. + /// The . + Task MessageDeliverySuccess(GovNotifyResponse response); + + /// + /// Save failed one-off email. + /// + /// The email request. + /// The . + Task SaveFailedSingleEmail(SingleEmailFailedRequest request); + } +} diff --git a/MessageQueueing/LearningHub.Nhs.MessageQueueing/Repositories/MessageQueueRepository.cs b/MessageQueueing/LearningHub.Nhs.MessageQueueing/Repositories/MessageQueueRepository.cs new file mode 100644 index 000000000..8e5059bd6 --- /dev/null +++ b/MessageQueueing/LearningHub.Nhs.MessageQueueing/Repositories/MessageQueueRepository.cs @@ -0,0 +1,90 @@ +namespace LearningHub.Nhs.MessageQueueing.Repositories +{ + using System.Collections.Generic; + using System.Data; + using System.Threading.Tasks; + using LearningHub.Nhs.MessageQueueing.EntityFramework; + using LearningHub.Nhs.MessageQueueing.Helpers; + using LearningHub.Nhs.Models.Entities.GovNotifyMessaging; + using LearningHub.Nhs.Models.GovNotifyMessaging; + using Microsoft.Data.SqlClient; + using Microsoft.EntityFrameworkCore; + + /// + /// The MessageQueueRepository. + /// + public class MessageQueueRepository : IMessageQueueRepository + { + private readonly MessageQueueDbContext dbContext; + + /// + /// Initializes a new instance of the class. + /// + /// The context. + public MessageQueueRepository(MessageQueueDbContext dbContext) + { + this.dbContext = dbContext; + } + + /// + /// The QueueMessagesAsync. + /// + /// The queue requests. + /// The . + public async Task QueueMessagesAsync(IEnumerable requests) + { + var dataTable = DataTableBuilder.ToQueueRequestDataTable(requests); + var param0 = new SqlParameter("@p0", SqlDbType.Structured) { Value = dataTable, TypeName = "dbo.QueueRequestTableType" }; + await this.dbContext.Database.ExecuteSqlRawAsync("dbo.CreateQueueRequests @p0", param0); + } + + /// + /// The GetPendingEmailsAsync. + /// + /// The . + public async Task> GetPendingEmailsAsync() + { + var result = await this.dbContext.PendingMessageRequests.FromSqlRaw("[dbo].[GetQueueRequests]") + .AsNoTracking().ToListAsync(); + return result; + } + + /// + /// The Update Email request as success. + /// + /// Th response. + /// The . + public async Task MessageDeliverySuccess(GovNotifyResponse response) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = response.Id }; + var param1 = new SqlParameter("@p1", SqlDbType.NVarChar) { Value = response.NotificationId }; + await this.dbContext.Database.ExecuteSqlRawAsync("dbo.MessageDeliverySuccess @p0, @p1", param0, param1); + } + + /// + /// The Update Email request as failed. + /// + /// Th response. + /// The . + public async Task MessageDeliveryFailed(GovNotifyResponse response) + { + var param0 = new SqlParameter("@p0", SqlDbType.Int) { Value = response.Id }; + var param1 = new SqlParameter("@p1", SqlDbType.NVarChar) { Value = response.ErrorMessage }; + await this.dbContext.Database.ExecuteSqlRawAsync("dbo.MessageDeliveryFailed @p0, @p1", param0, param1); + } + + /// + /// The Save failed Single Emails. + /// + /// The request. + /// The . + public async Task SaveFailedSingleEmail(SingleEmailFailedRequest request) + { + var param0 = new SqlParameter("@p0", SqlDbType.NVarChar) { Value = request.Recipient }; + var param1 = new SqlParameter("@p1", SqlDbType.NVarChar) { Value = request.TemplateId }; + var param2 = new SqlParameter("@p2", SqlDbType.NVarChar) { Value = request.Personalisation }; + var param3 = new SqlParameter("@p3", SqlDbType.NVarChar) { Value = request.ErrorMessage }; + await this.dbContext.Database.ExecuteSqlRawAsync("dbo.SaveFailedSingleEmail @p0, @p1, @p2, @p3", param0, param1, param2, param3); + } + } +} diff --git a/MessageQueueing/LearningHub.Nhs.MessageQueueing/Startup.cs b/MessageQueueing/LearningHub.Nhs.MessageQueueing/Startup.cs new file mode 100644 index 000000000..e3cfa65d1 --- /dev/null +++ b/MessageQueueing/LearningHub.Nhs.MessageQueueing/Startup.cs @@ -0,0 +1,32 @@ +namespace LearningHub.Nhs.MessageQueueing +{ + using LearningHub.Nhs.MessageQueueing.EntityFramework; + using LearningHub.Nhs.MessageQueueing.Repositories; + using Microsoft.EntityFrameworkCore; + 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 AddQueueingRepositories(this IServiceCollection services, IConfiguration configuration) + { + var dbContextOptions = new DbContextOptionsBuilder() + .UseSqlServer(configuration.GetConnectionString("GovNotifyMessageDbConnection"), providerOptions => { providerOptions.EnableRetryOnFailure(3); }) + .Options; + + services.AddSingleton(dbContextOptions); + services.AddSingleton(); + + services.AddDbContext(); + services.AddScoped(); + } + } +} diff --git a/LearningHub.Nhs.MessagingService/Interfaces/IGovNotifyService.cs b/MessagingService/LearningHub.Nhs.MessagingService/Interfaces/IGovNotifyService.cs similarity index 73% rename from LearningHub.Nhs.MessagingService/Interfaces/IGovNotifyService.cs rename to MessagingService/LearningHub.Nhs.MessagingService/Interfaces/IGovNotifyService.cs index 23d0d3738..588d766e7 100644 --- a/LearningHub.Nhs.MessagingService/Interfaces/IGovNotifyService.cs +++ b/MessagingService/LearningHub.Nhs.MessagingService/Interfaces/IGovNotifyService.cs @@ -2,6 +2,7 @@ { using System.Collections.Generic; using System.Threading.Tasks; + using LearningHub.Nhs.Models.GovNotifyMessaging; /// /// IMessageServices. @@ -15,7 +16,7 @@ public interface IGovNotifyService /// templateId. /// personalisation. /// The . - Task SendEmailAsync(string email, string templateId, Dictionary personalisation); + Task SendEmailAsync(string email, string templateId, Dictionary personalisation); /// /// Send SmsAsync. @@ -24,6 +25,6 @@ public interface IGovNotifyService /// templateId. /// personalisation. /// The . - Task SendSmsAsync(string phoneNumber, string templateId, Dictionary personalisation); + Task SendSmsAsync(string phoneNumber, string templateId, Dictionary personalisation); } } diff --git a/LearningHub.Nhs.MessagingService/LearningHub.Nhs.MessagingService.csproj b/MessagingService/LearningHub.Nhs.MessagingService/LearningHub.Nhs.MessagingService.csproj similarity index 90% rename from LearningHub.Nhs.MessagingService/LearningHub.Nhs.MessagingService.csproj rename to MessagingService/LearningHub.Nhs.MessagingService/LearningHub.Nhs.MessagingService.csproj index d21593e42..bd12f3577 100644 --- a/LearningHub.Nhs.MessagingService/LearningHub.Nhs.MessagingService.csproj +++ b/MessagingService/LearningHub.Nhs.MessagingService/LearningHub.Nhs.MessagingService.csproj @@ -8,6 +8,7 @@ + diff --git a/LearningHub.Nhs.MessagingService/Model/MessagingServiceModel.cs b/MessagingService/LearningHub.Nhs.MessagingService/MessagingOptions/MessagingServiceOptions.cs similarity index 80% rename from LearningHub.Nhs.MessagingService/Model/MessagingServiceModel.cs rename to MessagingService/LearningHub.Nhs.MessagingService/MessagingOptions/MessagingServiceOptions.cs index 66a3f210e..b4b395f31 100644 --- a/LearningHub.Nhs.MessagingService/Model/MessagingServiceModel.cs +++ b/MessagingService/LearningHub.Nhs.MessagingService/MessagingOptions/MessagingServiceOptions.cs @@ -3,12 +3,12 @@ /// /// MessagingServiceModel. /// - public class MessagingServiceModel + public class MessagingServiceOptions { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public MessagingServiceModel() + public MessagingServiceOptions() { // Current = this; } diff --git a/MessagingService/LearningHub.Nhs.MessagingService/Services/GovNotifyService.cs b/MessagingService/LearningHub.Nhs.MessagingService/Services/GovNotifyService.cs new file mode 100644 index 000000000..9d06a21e6 --- /dev/null +++ b/MessagingService/LearningHub.Nhs.MessagingService/Services/GovNotifyService.cs @@ -0,0 +1,141 @@ +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 LearningHub.Nhs.Models.GovNotifyMessaging; + using Microsoft.Extensions.Options; + using Notify.Client; + using Notify.Exceptions; + + /// + /// GovNotify Service class. + /// + public class GovNotifyService : IGovNotifyService + { + private readonly NotificationClient client; + + /// + /// Initializes a new instance of the class. + /// + /// The Messaging Service Options. + 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. + /// + /// The . + public async Task SendEmailAsync(string email, string templateId, Dictionary personalisation) + { + try + { + var normalisedPersonlisation = new Dictionary(); + if (personalisation != null) + { + 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 new GovNotifyResponse + { + IsSuccess = true, + NotificationId = response.id, + }; + } + catch (NotifyClientException ex) + { + return new GovNotifyResponse + { + IsSuccess = false, + ErrorMessage = ex.Message, + ////Retry = true, + }; + } + catch (Exception ex) + { + return new GovNotifyResponse + { + IsSuccess = false, + ErrorMessage = ex.Message, + ////Retry = true, + }; + } + } + + /// + /// 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. + /// + /// The . + public async Task SendSmsAsync(string phoneNumber, string templateId, Dictionary personalisation) + { + try + { + 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 new GovNotifyResponse + { + IsSuccess = true, + NotificationId = response.id, + }; + } + catch (NotifyClientException ex) + { + return new GovNotifyResponse + { + IsSuccess = false, + ErrorMessage = ex.Message, + ////Retry = true, + }; + } + catch (Exception ex) + { + return new GovNotifyResponse + { + IsSuccess = false, + ErrorMessage = ex.Message, + ////Retry = true, + }; + } + } + } +} diff --git a/LearningHub.Nhs.MessagingService/Startup.cs b/MessagingService/LearningHub.Nhs.MessagingService/Startup.cs similarity index 90% rename from LearningHub.Nhs.MessagingService/Startup.cs rename to MessagingService/LearningHub.Nhs.MessagingService/Startup.cs index 522b98bda..30359eb08 100644 --- a/LearningHub.Nhs.MessagingService/Startup.cs +++ b/MessagingService/LearningHub.Nhs.MessagingService/Startup.cs @@ -18,7 +18,7 @@ public static class Startup /// The IConfiguration. public static void AddMessagingServices(this IServiceCollection services, IConfiguration configuration) { - services.Configure(configuration.GetSection("GovNotify")); + services.Configure(configuration.GetSection("GovNotify")); services.AddScoped(); } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj index f34790d51..a4341e802 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Models/LearningHub.Nhs.OpenApi.Models.csproj @@ -16,7 +16,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj index 20b3a1c06..d3907645f 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services.Interface/LearningHub.Nhs.OpenApi.Services.Interface.csproj @@ -17,7 +17,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj index f84d5121c..b7850fdea 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi.Services/LearningHub.Nhs.OpenApi.Services.csproj @@ -30,7 +30,7 @@ - + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/GovNotifyMessagingController.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/GovNotifyMessagingController.cs index 05066a292..574b9dced 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/GovNotifyMessagingController.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Controllers/GovNotifyMessagingController.cs @@ -1,11 +1,16 @@ namespace LearningHub.NHS.OpenAPI.Controllers { using System; + using System.Collections.Generic; + using System.Linq; using System.Threading.Tasks; + using LearningHub.Nhs.MessageQueueing.Repositories; using LearningHub.Nhs.MessagingService.Interfaces; - using LearningHub.Nhs.MessagingService.Model; + using LearningHub.Nhs.Models.Entities.GovNotifyMessaging; + using LearningHub.Nhs.Models.GovNotifyMessaging; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; + using Newtonsoft.Json; /// /// GovNotify Messaging Controller. @@ -15,14 +20,17 @@ public class GovNotifyMessagingController : OpenApiControllerBase { private readonly IGovNotifyService messageService; + private readonly IMessageQueueRepository messageQueueRepository; /// /// Initializes a new instance of the class. /// - /// The catalogue service. - public GovNotifyMessagingController(IGovNotifyService messageService) + /// The message service. + /// The email Queue Repository. + public GovNotifyMessagingController(IGovNotifyService messageService, IMessageQueueRepository messageQueueRepository) { this.messageService = messageService; + this.messageQueueRepository = messageQueueRepository; } /// @@ -30,18 +38,35 @@ public GovNotifyMessagingController(IGovNotifyService messageService) /// /// personalisation. /// The . - [Route("sendemail")] + [Route("SendEmail")] [HttpPost] - public async Task SendEmailAsync([FromBody] SendEmailRequest request) + public async Task SendEmailAsync([FromBody] EmailRequest request) { try { - if (string.IsNullOrWhiteSpace(request.Email) || string.IsNullOrWhiteSpace(request.TemplateId)) + if (string.IsNullOrWhiteSpace(request.Recipient) || 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); + var response = await this.messageService.SendEmailAsync(request.Recipient, request.TemplateId, request.Personalisation); + + Dictionary test = new Dictionary(); + if (response != null) + { + if (!response.IsSuccess && (request.Id == null || request.Id <= 0)) + { + var failedRequest = new SingleEmailFailedRequest + { + Recipient = request.Recipient, + TemplateId = request.TemplateId, + Personalisation = request.Personalisation != null ? JsonConvert.SerializeObject(request.Personalisation.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToString())) : null, + ErrorMessage = response.ErrorMessage, + }; + await this.messageQueueRepository.SaveFailedSingleEmail(failedRequest); + } + } + return this.Ok(response); } catch (Exception ex) @@ -55,9 +80,9 @@ public async Task SendEmailAsync([FromBody] SendEmailRequest requ /// /// SendSmsRequest. /// The . - [Route("sendsms")] + [Route("SendSms")] [HttpPost] - public async Task SendSmsAsync([FromBody] SendSmsRequest request) + public async Task SendSmsAsync([FromBody] SmsRequest request) { try { @@ -74,5 +99,69 @@ public async Task SendSmsAsync([FromBody] SendSmsRequest request) return this.Ok(ex.Message); } } + + /// + /// To queue the MessageRequests. + /// + /// The QueueRequestList. + /// The . + [Route("QueueRequests")] + [HttpPost] + public async Task QueueRequests([FromBody] QueueMessageList request) + { + if (request?.Messages == null || !request.Messages.Any()) + { + return this.BadRequest("At least one email must be provided in the request."); + } + + var requests = request.Messages.Select(q => new QueueRequests + { + Recipient = q.Recipient, + TemplateId = q.TemplateId, + Personalisation = q.Personalisation != null ? JsonConvert.SerializeObject(q.Personalisation) : null, + DeliverAfter = q.DeliverAfter ?? null, + }); + + await this.messageQueueRepository.QueueMessagesAsync(requests); + + return this.Ok(new { Message = $"{requests.Count()} message requests queued successfully." }); + } + + /// + /// To fetch the Pending or failed Message Requests. + /// + /// The . + [Route("PendingMessageRequests")] + [HttpGet] + public async Task> PendingMessageRequests() + { + return await this.messageQueueRepository.GetPendingEmailsAsync(); + } + + /// + /// Update message request as Success. + /// + /// The response. + /// The . + [Route("MessageSuccessUpdate")] + [HttpPost] + public async Task MessageSuccessUpdate([FromBody] GovNotifyResponse response) + { + await this.messageQueueRepository.MessageDeliverySuccess(response); + return this.Ok(); + } + + /// + /// Update message request as Failed. + /// + /// The response. + /// The . + [Route("MessageFailedUpdate")] + [HttpPost] + public async Task MessageFailedUpdate([FromBody] GovNotifyResponse response) + { + await this.messageQueueRepository.MessageDeliveryFailed(response); + return this.Ok(); + } } } diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj index 6ed354944..5969f63c9 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj +++ b/OpenAPI/LearningHub.Nhs.OpenApi/LearningHub.NHS.OpenAPI.csproj @@ -1,4 +1,4 @@ - + enable @@ -31,7 +31,8 @@ - + + diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs b/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs index f3dc3f51b..ea03ad70f 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs +++ b/OpenAPI/LearningHub.Nhs.OpenApi/Startup.cs @@ -31,6 +31,8 @@ namespace LearningHub.NHS.OpenAPI using System; using LearningHub.Nhs.Models.Extensions; using LearningHub.Nhs.MessagingService; + using LearningHub.Nhs.MessageQueueing; + using Microsoft.AspNetCore.Authentication.Cookies; /// /// The Startup class. @@ -60,6 +62,7 @@ public void ConfigureServices(IServiceCollection services) services.AddConfig(this.Configuration); services.AddApiKeyAuth(); + services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(); services.AddAuthentication() .AddJwtBearer(options => @@ -86,6 +89,7 @@ public void ConfigureServices(IServiceCollection services) services.AddControllers(options => options.Filters.Add(new HttpResponseExceptionFilter())); services.AddControllers(opt => { opt.Filters.Add(new AuthorizeFilter()); }); services.AddMessagingServices(this.Configuration); + services.AddQueueingRepositories(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 9633b6117..00bb8cf37 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json +++ b/OpenAPI/LearningHub.Nhs.OpenApi/SwaggerDefinitions/v1.3.0.json @@ -6034,29 +6034,28 @@ } } }, - "/GovNotifyMessage/sendsms": { + "/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" + "$ref": "#/components/schemas/LearningHub.Nhs.Models.GovNotifyMessaging.SmsRequest" } }, "text/json": { "schema": { - "$ref": "#/components/schemas/SendSmsRequest" + "$ref": "#/components/schemas/LearningHub.Nhs.Models.GovNotifyMessaging.SmsRequest" } }, "application/*+json": { "schema": { - "$ref": "#/components/schemas/SendSmsRequest" + "$ref": "#/components/schemas/LearningHub.Nhs.Models.GovNotifyMessaging.SmsRequest" } } } @@ -6074,7 +6073,7 @@ } } }, - "/GovNotifyMessage/sendemail": { + "/GovNotifyMessage/SendEmail": { "post": { "tags": [ "SendEmail" @@ -6085,17 +6084,17 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SendEmailRequest" + "$ref": "#/components/schemas/LearningHub.Nhs.Models.GovNotifyMessaging.EmailRequest" } }, "text/json": { "schema": { - "$ref": "#/components/schemas/SendEmailRequest" + "$ref": "#/components/schemas/LearningHub.Nhs.Models.GovNotifyMessaging.EmailRequest" } }, "application/*+json": { "schema": { - "$ref": "#/components/schemas/SendEmailRequest" + "$ref": "#/components/schemas/LearningHub.Nhs.Models.GovNotifyMessaging.EmailRequest" } } } @@ -6112,6 +6111,45 @@ } } } + }, + "/GovNotifyMessage/QueueRequests": { + "post": { + "tags": [ + "QueueRequests" + ], + "summary": "Queue one or more email/sms request for later processing.", + "requestBody": { + "description": "A list of sms/email requests to be queued for sending.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LearningHub.Nhs.Models.GovNotifyMessaging.QueueRequestsList" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/LearningHub.Nhs.Models.GovNotifyMessaging.QueueRequestsList" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/LearningHub.Nhs.Models.GovNotifyMessaging.QueueRequestsList" + } + } + } + }, + "responses": { + "200": { + "description": "Requests are queued successfully." + }, + "400": { + "description": "Bad request, invalid input." + }, + "500": { + "description": "Internal server error." + } + } + } } }, "components": { @@ -14218,50 +14256,85 @@ }, "additionalProperties": false }, - "SendSmsRequest": { + "LearningHub.Nhs.Models.GovNotifyMessaging.SmsRequest": { "type": "object", - "required": [ "mobileNumber", "templateId" ], + "required": [ "PhoneNumber", "TemplateId" ], "properties": { - "mobileNumber": { + "PhoneNumber": { "type": "string", "nullable": false, "description": "The recipient's mobile number." }, - "templateId": { + "TemplateId": { "type": "string", "nullable": false, "description": "The ID of the template to be used for the email." }, - "personalisation": { + "Personalisation": { "type": "object", - "additionalProperties:": { + "additionalProperties": { "type": "string" }, "description": "Key-value pairs for personalising the sms template." } } }, - "SendEmailRequest": { + "LearningHub.Nhs.Models.GovNotifyMessaging.EmailRequest": { "type": "object", "properties": { - "emailAddress": { + "Recipient": { "type": "string", "nullable": false, "description": "The recipient's email address." }, - "templateId": { + "TemplateId": { "type": "string", "nullable": false, "description": "The ID of the template to be used for the email." }, - "personalisation": { + "Personalisation": { "type": "object", - "additionalProperties:": { + "additionalProperties": { "type": "string" }, "description": "Key-value pairs for personalising the email template." } } + }, + "LearningHub.Nhs.Models.GovNotifyMessaging.QueueRequestsList": { + "type": "object", + "properties": { + "Messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LearningHub.Nhs.Models.GovNotifyMessaging.QueueRequests" + } + } + } + }, + "LearningHub.Nhs.Models.GovNotifyMessaging.QueueRequests": { + "type": "object", + "properties": { + "Recipient": { + "type": "string", + "nullable": false + }, + "TemplateId": { + "type": "string", + "nullable": false + }, + "DeliverAfter": { + "type": "string", + "format": "date-time" + }, + "Personalisation": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Key-value pairs for personalising the template." + } + } } }, "securitySchemes": { diff --git a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json index 56c507cff..f9c27f84e 100644 --- a/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json +++ b/OpenAPI/LearningHub.Nhs.OpenApi/appsettings.json @@ -11,6 +11,7 @@ "ElfhHubDbConnection": "", "LearningHubDbConnection": "", "LearningHubRedis": "", + "GovNotifyMessageDbConnection": "", "NLogDb": "" }, "Auth": { @@ -38,6 +39,12 @@ "Keys": [ "" ] + }, + { + "Name": "MessageQueueProcessor", + "Keys": [ + "" + ] } ] }, diff --git a/ReportAPI/LearningHub.Nhs.ReportApi.Services.Interface/LearningHub.Nhs.ReportApi.Services.Interface.csproj b/ReportAPI/LearningHub.Nhs.ReportApi.Services.Interface/LearningHub.Nhs.ReportApi.Services.Interface.csproj index 0a697a51f..655caa351 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Services.Interface/LearningHub.Nhs.ReportApi.Services.Interface.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Services.Interface/LearningHub.Nhs.ReportApi.Services.Interface.csproj @@ -16,7 +16,7 @@ - + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi.Services.UnitTests/LearningHub.Nhs.ReportApi.Services.UnitTests.csproj b/ReportAPI/LearningHub.Nhs.ReportApi.Services.UnitTests/LearningHub.Nhs.ReportApi.Services.UnitTests.csproj index 50e3d7536..537f585bd 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Services.UnitTests/LearningHub.Nhs.ReportApi.Services.UnitTests.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Services.UnitTests/LearningHub.Nhs.ReportApi.Services.UnitTests.csproj @@ -18,7 +18,7 @@ - + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj b/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj index 55c8a1be4..42d7766fc 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Services/LearningHub.Nhs.ReportApi.Services.csproj @@ -19,7 +19,7 @@ - + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj b/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj index 2f5518784..5b6a9d92e 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi.Shared/LearningHub.Nhs.ReportApi.Shared.csproj @@ -17,7 +17,7 @@ - + diff --git a/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj b/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj index 583caa9f2..b215db06c 100644 --- a/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj +++ b/ReportAPI/LearningHub.Nhs.ReportApi/LearningHub.Nhs.ReportApi.csproj @@ -20,7 +20,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj index bb0c6c6cc..37286151e 100644 --- a/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj +++ b/WebAPI/LearningHub.Nhs.API/LearningHub.Nhs.Api.csproj @@ -29,7 +29,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj index 74d2ccc77..8173a68b2 100644 --- a/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj +++ b/WebAPI/LearningHub.Nhs.Api.Shared/LearningHub.Nhs.Api.Shared.csproj @@ -9,7 +9,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj b/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj index 9663e4ee7..ca8916c4e 100644 --- a/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Api.UnitTests/LearningHub.Nhs.Api.UnitTests.csproj @@ -11,7 +11,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj b/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj index d41ba9e68..1c2dfe61b 100644 --- a/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Repository.Interface/LearningHub.Nhs.Repository.Interface.csproj @@ -10,7 +10,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj index 904bda4c8..a84412769 100644 --- a/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj +++ b/WebAPI/LearningHub.Nhs.Repository/LearningHub.Nhs.Repository.csproj @@ -9,7 +9,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj index b259b3a7e..c76a6ee5c 100644 --- a/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj +++ b/WebAPI/LearningHub.Nhs.Services.Interface/LearningHub.Nhs.Services.Interface.csproj @@ -16,7 +16,7 @@ - + all diff --git a/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj index f78df0084..06af56b2a 100644 --- a/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj +++ b/WebAPI/LearningHub.Nhs.Services.UnitTests/LearningHub.Nhs.Services.UnitTests.csproj @@ -13,7 +13,7 @@ - + diff --git a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj index e99e5300a..903884270 100644 --- a/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj +++ b/WebAPI/LearningHub.Nhs.Services/LearningHub.Nhs.Services.csproj @@ -13,7 +13,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj index 697d1db2f..3119248d5 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.ConsoleApp/LearningHub.Nhs.Migration.ConsoleApp.csproj @@ -25,7 +25,7 @@ - + all diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj index 89ca1344e..bc30ad48a 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Interface/LearningHub.Nhs.Migration.Interface.csproj @@ -9,7 +9,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj index 4ab608bac..5ab9d00b1 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Models/LearningHub.Nhs.Migration.Models.csproj @@ -10,7 +10,7 @@ - + all diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj index 7dd7febb7..539446335 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.Staging.Repository/LearningHub.Nhs.Migration.Staging.Repository.csproj @@ -9,7 +9,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj index 5b83ec893..693ef008d 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration.UnitTests/LearningHub.Nhs.Migration.UnitTests.csproj @@ -10,7 +10,7 @@ - + diff --git a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj index bf0235325..a184bb27f 100644 --- a/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj +++ b/WebAPI/MigrationTool/LearningHub.Nhs.Migration/LearningHub.Nhs.Migration.csproj @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive