diff --git a/MessagingService.Client/MessagingService.Client.csproj b/MessagingService.Client/MessagingService.Client.csproj
index 2649e26..38943a0 100644
--- a/MessagingService.Client/MessagingService.Client.csproj
+++ b/MessagingService.Client/MessagingService.Client.csproj
@@ -6,9 +6,9 @@
-
+
-
+
diff --git a/MessagingService.EmailAggregate.Tests/MessagingService.EmailAggregate.Tests.csproj b/MessagingService.EmailAggregate.Tests/MessagingService.EmailAggregate.Tests.csproj
index 0085f15..e1d967a 100644
--- a/MessagingService.EmailAggregate.Tests/MessagingService.EmailAggregate.Tests.csproj
+++ b/MessagingService.EmailAggregate.Tests/MessagingService.EmailAggregate.Tests.csproj
@@ -8,7 +8,7 @@
-
+
diff --git a/MessagingService.EmailMessage.DomainEvents/MessagingService.EmailMessage.DomainEvents.csproj b/MessagingService.EmailMessage.DomainEvents/MessagingService.EmailMessage.DomainEvents.csproj
index 8aa9ea1..36ed7e0 100644
--- a/MessagingService.EmailMessage.DomainEvents/MessagingService.EmailMessage.DomainEvents.csproj
+++ b/MessagingService.EmailMessage.DomainEvents/MessagingService.EmailMessage.DomainEvents.csproj
@@ -6,8 +6,8 @@
-
-
+
+
diff --git a/MessagingService.EmailMessageAggregate/MessagingService.EmailMessageAggregate.csproj b/MessagingService.EmailMessageAggregate/MessagingService.EmailMessageAggregate.csproj
index 5a1c187..d93bdd6 100644
--- a/MessagingService.EmailMessageAggregate/MessagingService.EmailMessageAggregate.csproj
+++ b/MessagingService.EmailMessageAggregate/MessagingService.EmailMessageAggregate.csproj
@@ -6,8 +6,8 @@
-
-
+
+
diff --git a/MessagingService.IntegrationTesting.Helpers/MessagingService.IntegrationTesting.Helpers.csproj b/MessagingService.IntegrationTesting.Helpers/MessagingService.IntegrationTesting.Helpers.csproj
index 6c160a3..b3ac028 100644
--- a/MessagingService.IntegrationTesting.Helpers/MessagingService.IntegrationTesting.Helpers.csproj
+++ b/MessagingService.IntegrationTesting.Helpers/MessagingService.IntegrationTesting.Helpers.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/MessagingService.IntegrationTests/MessagingService.IntegrationTests.csproj b/MessagingService.IntegrationTests/MessagingService.IntegrationTests.csproj
index 7403b5c..14ba96f 100644
--- a/MessagingService.IntegrationTests/MessagingService.IntegrationTests.csproj
+++ b/MessagingService.IntegrationTests/MessagingService.IntegrationTests.csproj
@@ -6,7 +6,7 @@
-
+
@@ -16,7 +16,7 @@
-
+
all
diff --git a/MessagingService.SMSMessage.DomainEvents/MessagingService.SMSMessage.DomainEvents.csproj b/MessagingService.SMSMessage.DomainEvents/MessagingService.SMSMessage.DomainEvents.csproj
index efebbd1..5637812 100644
--- a/MessagingService.SMSMessage.DomainEvents/MessagingService.SMSMessage.DomainEvents.csproj
+++ b/MessagingService.SMSMessage.DomainEvents/MessagingService.SMSMessage.DomainEvents.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/MessagingService.SMSMessageAggregate/MessagingService.SMSMessageAggregate.csproj b/MessagingService.SMSMessageAggregate/MessagingService.SMSMessageAggregate.csproj
index 72a9731..13cc55c 100644
--- a/MessagingService.SMSMessageAggregate/MessagingService.SMSMessageAggregate.csproj
+++ b/MessagingService.SMSMessageAggregate/MessagingService.SMSMessageAggregate.csproj
@@ -6,7 +6,7 @@
-
+
diff --git a/MessagingService.Tests/General/BootstrapperTests.cs b/MessagingService.Tests/General/BootstrapperTests.cs
index 77ad2ca..03846a0 100644
--- a/MessagingService.Tests/General/BootstrapperTests.cs
+++ b/MessagingService.Tests/General/BootstrapperTests.cs
@@ -1,17 +1,24 @@
-using System;
+using Microsoft.AspNetCore.Mvc.Infrastructure;
+using Microsoft.AspNetCore.Routing;
+using System;
using System.Collections.Generic;
+using System.Reflection;
using System.Text;
+using Microsoft.AspNetCore.Routing.Patterns;
namespace MessagingService.Tests.General
{
- using System.Diagnostics;
- using System.Linq;
using Lamar;
using Microsoft.AspNetCore.Hosting;
+ using Microsoft.AspNetCore.Mvc.Abstractions;
+ using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
+ using Microsoft.Extensions.Options;
using Moq;
+ using System.Diagnostics;
+ using System.Linq;
using Testing;
using Xunit;
diff --git a/MessagingService.Tests/MessagingService.Tests.csproj b/MessagingService.Tests/MessagingService.Tests.csproj
index 8d1c9a0..8944057 100644
--- a/MessagingService.Tests/MessagingService.Tests.csproj
+++ b/MessagingService.Tests/MessagingService.Tests.csproj
@@ -7,6 +7,7 @@
+
diff --git a/MessagingService/Bootstrapper/MiddlewareRegistry.cs b/MessagingService/Bootstrapper/MiddlewareRegistry.cs
index f27927c..295e79b 100644
--- a/MessagingService/Bootstrapper/MiddlewareRegistry.cs
+++ b/MessagingService/Bootstrapper/MiddlewareRegistry.cs
@@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging;
+using Shared.Authorisation;
namespace MessagingService.Bootstrapper
{
@@ -43,20 +44,10 @@ public MiddlewareRegistry() {
failureStatus: HealthStatus.Unhealthy,
tags: new string[] { "db", "eventstore" });
+
+ this.AddControllers();
this.ConfigureAuthentication();
this.ConfigureSwagger();
-
- this.AddControllers().AddNewtonsoftJson(options =>
- {
- options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
- options.SerializerSettings.TypeNameHandling = TypeNameHandling.Auto;
- options.SerializerSettings.Formatting = Formatting.Indented;
- options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
- options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
- });
-
- Assembly assembly = this.GetType().GetTypeInfo().Assembly;
- this.AddMvcCore().AddApplicationPart(assembly).AddControllersAsServices();
bool logRequests = ConfigurationReader.GetValueOrDefault("MiddlewareLogging", "LogRequests", true);
bool logResponses = ConfigurationReader.GetValueOrDefault("MiddlewareLogging", "LogResponses", true);
@@ -92,8 +83,17 @@ private void ConfigureAuthentication() {
};
options.IncludeErrorDetails = true;
});
+ this.AddClientCredentialsOnlyPolicy();
+ this.AddClientCredentialsHandler();
+
+ this.ConfigureHttpJsonOptions(options =>
+ {
+ options.SerializerOptions.PropertyNamingPolicy = new SnakeCaseNamingPolicy();
+ options.SerializerOptions.PropertyNameCaseInsensitive = true; // optional, but safer
+ });
}
private void ConfigureSwagger() {
+
this.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
diff --git a/MessagingService/Controllers/DomainEventController.cs b/MessagingService/Controllers/DomainEventController.cs
deleted file mode 100644
index 95deb4d..0000000
--- a/MessagingService/Controllers/DomainEventController.cs
+++ /dev/null
@@ -1,162 +0,0 @@
-using Shared.Exceptions;
-
-namespace MessagingService.Controllers
-{
- using System;
- using System.Collections.Generic;
- using System.Diagnostics.CodeAnalysis;
- using System.Linq;
- using System.Threading;
- using System.Threading.Tasks;
- using Microsoft.AspNetCore.Mvc;
- using Newtonsoft.Json;
- using Newtonsoft.Json.Linq;
- using Shared.DomainDrivenDesign.EventSourcing;
- using Shared.EventStore.Aggregate;
- using Shared.EventStore.EventHandling;
- using Shared.General;
- using Shared.Logger;
- using Shared.Serialisation;
-
- ///
- ///
- ///
- ///
- [Route(DomainEventController.ControllerRoute)]
- [ApiController]
- [ExcludeFromCodeCoverage]
- [ApiExplorerSettings(IgnoreApi = true)]
- public class DomainEventController : ControllerBase
- {
- #region Fields
-
- ///
- /// The domain event handler resolver
- ///
- private readonly IDomainEventHandlerResolver DomainEventHandlerResolver;
-
- #endregion
-
- #region Constructors
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The domain event handler resolver.
- public DomainEventController(IDomainEventHandlerResolver domainEventHandlerResolver) {
- this.DomainEventHandlerResolver = domainEventHandlerResolver;
- }
-
- #endregion
-
- #region Methods
-
- ///
- /// Posts the event asynchronous.
- ///
- /// The domain event.
- /// The cancellation token.
- ///
- [HttpPost]
- public async Task PostEventAsync([FromBody] Object request,
- CancellationToken cancellationToken) {
- IDomainEvent domainEvent = await this.GetDomainEvent(request);
-
- cancellationToken.Register(() => this.Callback(cancellationToken, domainEvent.EventId));
-
- try {
- Logger.LogInformation($"Processing event - ID [{domainEvent.EventId}], Type[{domainEvent.GetType().Name}]");
-
- var eventHandlersResult = this.DomainEventHandlerResolver.GetDomainEventHandlers(domainEvent);
-
- if (eventHandlersResult.IsFailed) {
- // Log a warning out
- Logger.LogWarning($"No event handlers configured for Event Type [{domainEvent.GetType().Name}]");
- return this.Ok();
- }
-
- var eventHandlers = eventHandlersResult.Data;
-
- List tasks = new List();
- foreach (IDomainEventHandler domainEventHandler in eventHandlers) {
- tasks.Add(domainEventHandler.Handle(domainEvent, cancellationToken));
- }
-
- Task.WaitAll(tasks.ToArray());
-
- Logger.LogInformation($"Finished processing event - ID [{domainEvent.EventId}]");
-
- return this.Ok();
- }
- catch(Exception ex) {
- String domainEventData = JsonConvert.SerializeObject(domainEvent);
- Logger.LogError(new Exception($" Failed to Process Event, Event Data received [{domainEventData}]", ex));
-
- throw;
- }
- }
-
- private void Callback(CancellationToken cancellationToken,
- Guid eventId) {
- if (cancellationToken.IsCancellationRequested) //I think this would always be true anyway
- {
- Logger.LogInformation($"Cancel request for EventId {eventId}");
- cancellationToken.ThrowIfCancellationRequested();
- }
- }
-
- private async Task GetDomainEvent(Object domainEvent) {
- String eventType = this.Request.Headers["eventType"].ToString();
-
- Type type = TypeMap.GetType(eventType);
-
- if (type == null)
- throw new NotFoundException($"Failed to find a domain event with type {eventType}");
-
- JsonIgnoreAttributeIgnorerContractResolver jsonIgnoreAttributeIgnorerContractResolver = new();
- JsonSerializerSettings jsonSerialiserSettings = new() {
- ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
- TypeNameHandling = TypeNameHandling.All,
- Formatting = Formatting.Indented,
- DateTimeZoneHandling = DateTimeZoneHandling.Utc,
- ContractResolver = jsonIgnoreAttributeIgnorerContractResolver
- };
-
- if (type.IsSubclassOf(typeof(DomainEvent))) {
- String json = JsonConvert.SerializeObject(domainEvent, jsonSerialiserSettings);
-
- DomainEventFactory domainEventFactory = new();
- String validatedJson = this.ValidateEvent(json);
- return domainEventFactory.CreateDomainEvent(validatedJson, type);
- }
-
- return null;
- }
-
- private String ValidateEvent(String domainEventJson) {
- JObject domainEvent = JObject.Parse(domainEventJson);
-
- if (domainEvent.ContainsKey("eventId") == false || domainEvent["eventId"].ToObject() == Guid.Empty) {
- throw new ArgumentException("Domain Event must contain an Event Id");
- }
-
- return domainEventJson;
- }
-
- #endregion
-
- #region Others
-
- ///
- /// The controller name
- ///
- private const String ControllerName = "domainevents";
-
- ///
- /// The controller route
- ///
- private const String ControllerRoute = "api/" + DomainEventController.ControllerName;
-
- #endregion
- }
-}
\ No newline at end of file
diff --git a/MessagingService/Controllers/EmailController.cs b/MessagingService/Controllers/EmailController.cs
deleted file mode 100644
index 7ce902e..0000000
--- a/MessagingService/Controllers/EmailController.cs
+++ /dev/null
@@ -1,159 +0,0 @@
-using Shared.General;
-using Shared.Results;
-using Shared.Results.Web;
-using SimpleResults;
-using SendEmailRequestDTO = MessagingService.DataTransferObjects.SendEmailRequest;
-using SendEmailResponseDTO = MessagingService.DataTransferObjects.SendEmailResponse;
-using ResendEmailRequestDTO = MessagingService.DataTransferObjects.ResendEmailRequest;
-
-namespace MessagingService.Controllers
-{
- using System;
- using System.Collections.Generic;
- using System.Diagnostics.CodeAnalysis;
- using System.Threading;
- using System.Threading.Tasks;
- using BusinessLogic.Requests;
- using Common;
- using Common.RequestExamples;
- using MediatR;
- using Microsoft.AspNetCore.Authorization;
- using Microsoft.AspNetCore.Mvc;
- using Swashbuckle.AspNetCore.Annotations;
- using Swashbuckle.AspNetCore.Filters;
-
- ///
- ///
- ///
- ///
- [ExcludeFromCodeCoverage]
- [Route(EmailController.ControllerRoute)]
- [ApiController]
- [Authorize]
- public class EmailController : ControllerBase
- {
- #region Fields
-
- ///
- /// The mediator
- ///
- private readonly IMediator Mediator;
-
- #endregion
-
- #region Constructors
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The mediator.
- public EmailController(IMediator mediator)
- {
- this.Mediator = mediator;
- }
-
- #endregion
-
- #region Methods
-
- ///
- /// Sends the email.
- ///
- /// The send email request.
- /// The cancellation token.
- ///
- [HttpPost]
- [Route("")]
- [SwaggerResponse(201, "Created", typeof(SendEmailResponseDTO))]
- [SwaggerResponseExample(201, typeof(SendEmailResponseExample))]
- public async Task SendEmail([FromBody] SendEmailRequestDTO sendEmailRequest,
- CancellationToken cancellationToken)
- {
- // Reject password tokens
- if (ClaimsHelper.IsPasswordToken(this.User))
- {
- return this.Forbid();
- }
-
- Guid messageId = sendEmailRequest.MessageId.HasValue ? sendEmailRequest.MessageId.Value : Guid.NewGuid();
-
- List emailAttachments = new List();
- foreach (DataTransferObjects.EmailAttachment emailAttachment in sendEmailRequest.EmailAttachments)
- {
- emailAttachments.Add(new EmailAttachment
- {
- FileData = emailAttachment.FileData,
- FileType = this.ConvertFileType(emailAttachment.FileType),
- Filename = emailAttachment.Filename
- });
- }
-
- // Create the command
- EmailCommands.SendEmailCommand command= new(sendEmailRequest.ConnectionIdentifier,
- messageId,
- sendEmailRequest.FromAddress,
- sendEmailRequest.ToAddresses,
- sendEmailRequest.Subject,
- sendEmailRequest.Body,
- sendEmailRequest.IsHtml,
- emailAttachments);
-
- // Route the command
- Result result = await this.Mediator.Send(command, cancellationToken);
-
- // return the result
- return result.ToActionResultX();
- }
-
- [ValidateAntiForgeryToken]
- [HttpPost]
- [Route("resend")]
- [SwaggerResponse(202, "Accepted", typeof(SendEmailResponseDTO))]
- [SwaggerResponseExample(202, typeof(SendEmailResponseExample))]
- public async Task ResendEmail([FromBody] ResendEmailRequestDTO resendEmailRequest,
- CancellationToken cancellationToken)
- {
- // Reject password tokens
- if (ClaimsHelper.IsPasswordToken(this.User))
- {
- return this.Forbid();
- }
-
- // Create the command
- EmailCommands.ResendEmailCommand command = new(resendEmailRequest.ConnectionIdentifier, resendEmailRequest.MessageId);
-
- // Route the command
- Result result = await this.Mediator.Send(command, cancellationToken);
-
- // return the result
- return result.ToActionResultX();
- }
-
- private FileType ConvertFileType(DataTransferObjects.FileType emailAttachmentFileType)
- {
- switch(emailAttachmentFileType)
- {
- case DataTransferObjects.FileType.PDF:
- return MessagingService.BusinessLogic.Requests.FileType.PDF;
- default:
- return MessagingService.BusinessLogic.Requests.FileType.None;
- }
- }
-
- #endregion
-
- #region Others
-
- ///
- /// The controller name
- ///
- private const String ControllerName = "email";
-
- ///
- /// The controller route
- ///
- private const String ControllerRoute = "api/" + EmailController.ControllerName;
-
- #endregion
- }
-}
\ No newline at end of file
diff --git a/MessagingService/Controllers/SMSController.cs b/MessagingService/Controllers/SMSController.cs
deleted file mode 100644
index 5b89539..0000000
--- a/MessagingService/Controllers/SMSController.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-using MessagingService.BusinessLogic.Requests;
-using Shared.General;
-using Shared.Results;
-using Shared.Results.Web;
-using SimpleResults;
-
-namespace MessagingService.Controllers
-{
- using System;
- using System.Diagnostics.CodeAnalysis;
- using System.Threading;
- using System.Threading.Tasks;
- using Common;
- using Common.RequestExamples;
- using DataTransferObjects;
- using MediatR;
- using Microsoft.AspNetCore.Authorization;
- using Microsoft.AspNetCore.Mvc;
- using Swashbuckle.AspNetCore.Annotations;
- using Swashbuckle.AspNetCore.Filters;
-
- [ExcludeFromCodeCoverage]
- [Route(SMSController.ControllerRoute)]
- [ApiController]
- [Authorize]
- public class SMSController : ControllerBase
- {
- #region Fields
-
- ///
- /// The mediator
- ///
- private readonly IMediator Mediator;
-
- #endregion
-
- #region Constructors
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The mediator.
- public SMSController(IMediator mediator)
- {
- this.Mediator = mediator;
- }
-
- #endregion
-
- #region Methods
-
- ///
- /// Sends the SMS.
- ///
- /// The send SMS request.
- /// The cancellation token.
- ///
- [HttpPost]
- [Route("")]
- [SwaggerResponse(201, "Created", typeof(SendSMSResponse))]
- [SwaggerResponseExample(201, typeof(SendSMSResponseExample))]
- public async Task SendSMS([FromBody] SendSMSRequest sendSMSRequest,
- CancellationToken cancellationToken)
- {
- // Reject password tokens
- if (ClaimsHelper.IsPasswordToken(this.User)) {
- return Result.Forbidden().ToActionResultX();
- }
-
- Guid messageId = sendSMSRequest.MessageId.HasValue ? sendSMSRequest.MessageId.Value : Guid.NewGuid();
-
- // Create the command
- SMSCommands.SendSMSCommand command = new(sendSMSRequest.ConnectionIdentifier,
- messageId,
- sendSMSRequest.Sender,
- sendSMSRequest.Destination,
- sendSMSRequest.Message);
-
- // Route the command
- Result result = await this.Mediator.Send(command, cancellationToken);
-
- // return the result
- return result.ToActionResultX();
- }
-
- [ValidateAntiForgeryToken]
- [HttpPost]
- [Route("resend")]
- [SwaggerResponse(202, "Accepted", typeof(SendSMSResponse))]
- [SwaggerResponseExample(202, typeof(SendSMSResponseExample))]
- public async Task ResendSMS([FromBody] ResendSMSRequest resendSMSRequest,
- CancellationToken cancellationToken)
- {
- // Reject password tokens
- if (ClaimsHelper.IsPasswordToken(this.User))
- {
- return Result.Forbidden().ToActionResultX();
- }
-
- // Create the command
- SMSCommands.ResendSMSCommand command = new(resendSMSRequest.ConnectionIdentifier, resendSMSRequest.MessageId);
-
- // Route the command
- Result result = await this.Mediator.Send(command, cancellationToken);
-
- // return the result
- return result.ToActionResultX();
- }
-
- #endregion
-
- #region Others
-
- ///
- /// The controller name
- ///
- private const String ControllerName = "sms";
-
- ///
- /// The controller route
- ///
- private const String ControllerRoute = "api/" + SMSController.ControllerName;
-
- #endregion
- }
-}
\ No newline at end of file
diff --git a/MessagingService/Endpoints/DomainEventEndpoints.cs b/MessagingService/Endpoints/DomainEventEndpoints.cs
new file mode 100644
index 0000000..c6e2e16
--- /dev/null
+++ b/MessagingService/Endpoints/DomainEventEndpoints.cs
@@ -0,0 +1,18 @@
+using MessagingService.Handlers;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing;
+
+namespace MessagingService.Endpoints;
+
+public static class DomainEventEndpoints
+{
+ private const string Route = "api/domainevents";
+
+ public static void MapDomainEventEndpoint(this IEndpointRouteBuilder app)
+ {
+ app.MapPost(Route, DomainEventHandlers.HandleDomainEvent)
+ .ExcludeFromDescription() // hides from Swagger
+ .WithName("DomainEvent");
+ }
+}
\ No newline at end of file
diff --git a/MessagingService/Endpoints/EmailEndpoints.cs b/MessagingService/Endpoints/EmailEndpoints.cs
new file mode 100644
index 0000000..94271fa
--- /dev/null
+++ b/MessagingService/Endpoints/EmailEndpoints.cs
@@ -0,0 +1,35 @@
+using MessagingService.DataTransferObjects;
+using MessagingService.Handlers;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing;
+using Shared.Authorisation;
+using Shared.Extensions;
+using Shared.Middleware;
+
+namespace MessagingService.Endpoints
+{
+ public static class EmailEndpoints
+ {
+ private const string BaseRoute = "/api/email";
+
+ public static IEndpointRouteBuilder MapEmailEndpoints(this IEndpointRouteBuilder endpoints)
+ {
+ RouteGroupBuilder emailGroup = endpoints.MapGroup(BaseRoute).WithTags("Email");
+
+ emailGroup.MapPost("/", EmailHandlers.SendEmail)
+ .RequireAuthorization(AuthorizationExtensions.PolicyNames.ClientCredentialsOnlyPolicy)
+ .WithName("Send Email")
+ .WithSummary("Sends an email")
+ .WithStandardProduces(StatusCodes.Status201Created);
+
+ emailGroup.MapPost("/resend", EmailHandlers.ResendEmail)
+ .RequireAuthorization(AuthorizationExtensions.PolicyNames.ClientCredentialsOnlyPolicy)
+ .WithName("Resend Email")
+ .WithSummary("Resends an email by id")
+ .WithStandardProduces< ErrorResponse>(StatusCodes.Status202Accepted);
+
+ return endpoints;
+ }
+ }
+}
diff --git a/MessagingService/Endpoints/SMSEndpoints.cs b/MessagingService/Endpoints/SMSEndpoints.cs
new file mode 100644
index 0000000..8ee3527
--- /dev/null
+++ b/MessagingService/Endpoints/SMSEndpoints.cs
@@ -0,0 +1,33 @@
+using MessagingService.DataTransferObjects;
+using MessagingService.Handlers;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing;
+using Shared.Authorisation;
+using Shared.Extensions;
+using Shared.Middleware;
+
+namespace MessagingService.Endpoints;
+
+public static class SMSEndpoints
+{
+ private const string BaseRoute = "/api/sms";
+ public static IEndpointRouteBuilder MapSMSEndpoints(this IEndpointRouteBuilder endpoints)
+ {
+ RouteGroupBuilder smsGroup = endpoints.MapGroup(BaseRoute).WithTags("SMS");
+
+ smsGroup.MapPost("/", SMSHandlers.SendSMS)
+ .RequireAuthorization(AuthorizationExtensions.PolicyNames.ClientCredentialsOnlyPolicy)
+ .WithName("Send SMS")
+ .WithSummary("Sends an SMS message")
+ .WithStandardProduces(StatusCodes.Status201Created);
+
+ smsGroup.MapPost("/resend", SMSHandlers.ResendSMS)
+ .RequireAuthorization(AuthorizationExtensions.PolicyNames.ClientCredentialsOnlyPolicy)
+ .WithName("Resend SMS")
+ .WithSummary("Resends an SMS message by id")
+ .WithStandardProduces< ErrorResponse>(StatusCodes.Status202Accepted);
+
+ return endpoints;
+ }
+}
\ No newline at end of file
diff --git a/MessagingService/Handlers/DomainEventHandlers.cs b/MessagingService/Handlers/DomainEventHandlers.cs
new file mode 100644
index 0000000..5393ab0
--- /dev/null
+++ b/MessagingService/Handlers/DomainEventHandlers.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Shared.DomainDrivenDesign.EventSourcing;
+using Shared.EventStore.Aggregate;
+using Shared.EventStore.EventHandling;
+using Shared.Exceptions;
+using Shared.General;
+using Shared.Logger;
+using Shared.Serialisation;
+using SimpleResults;
+
+namespace MessagingService.Handlers;
+
+public static class DomainEventHandlers
+{
+ public static async Task HandleDomainEvent(HttpRequest request,
+ object body,
+ IDomainEventHandlerResolver resolver,
+ CancellationToken cancellationToken)
+ {
+ IDomainEvent domainEvent = await GetDomainEvent(request, body);
+
+ cancellationToken.Register(() => Callback(cancellationToken, domainEvent.EventId));
+
+ try
+ {
+ Logger.LogInformation($"Processing event - ID [{domainEvent.EventId}], Type[{domainEvent.GetType().Name}]");
+
+ Result> eventHandlersResult = resolver.GetDomainEventHandlers(domainEvent);
+
+ if (eventHandlersResult.IsFailed)
+ {
+ Logger.LogWarning($"No event handlers configured for Event Type [{domainEvent.GetType().Name}]");
+ return Results.Ok();
+ }
+
+ var eventHandlers = eventHandlersResult.Data;
+ var tasks = eventHandlers.Select(h => h.Handle(domainEvent, cancellationToken));
+ await Task.WhenAll(tasks);
+
+ Logger.LogInformation("Finished processing event - ID [{domainEvent.EventId}]");
+
+ return Results.Ok();
+ }
+ catch (Exception ex)
+ {
+ string domainEventData = JsonConvert.SerializeObject(domainEvent);
+ Logger.LogError($"Failed to process event. Data received [{domainEventData}]", ex);
+ throw;
+ }
+ }
+
+ private static void Callback(CancellationToken cancellationToken, Guid eventId)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ Logger.LogInformation($"Cancel request for EventId {eventId}");
+ cancellationToken.ThrowIfCancellationRequested();
+ }
+ }
+
+ private static async Task GetDomainEvent(HttpRequest request, object domainEvent)
+ {
+ string eventType = request.Headers["eventType"].ToString();
+
+ Type type = TypeMap.GetType(eventType);
+
+ if (type == null)
+ throw new NotFoundException($"Failed to find a domain event with type {eventType}");
+
+ var resolver = new JsonIgnoreAttributeIgnorerContractResolver();
+ var settings = new JsonSerializerSettings
+ {
+ ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
+ TypeNameHandling = TypeNameHandling.All,
+ Formatting = Formatting.Indented,
+ DateTimeZoneHandling = DateTimeZoneHandling.Utc,
+ ContractResolver = resolver
+ };
+
+ if (type.IsSubclassOf(typeof(DomainEvent)))
+ {
+ string json = JsonConvert.SerializeObject(domainEvent, settings);
+
+ var factory = new DomainEventFactory();
+ string validatedJson = ValidateEvent(json);
+ return factory.CreateDomainEvent(validatedJson, type);
+ }
+
+ return null;
+ }
+
+ private static string ValidateEvent(string domainEventJson)
+ {
+ var domainEvent = JObject.Parse(domainEventJson);
+
+ if (!domainEvent.ContainsKey("eventId") ||
+ domainEvent["eventId"]!.ToObject() == Guid.Empty)
+ {
+ throw new ArgumentException("Domain Event must contain an Event Id");
+ }
+
+ return domainEventJson;
+ }
+}
\ No newline at end of file
diff --git a/MessagingService/Handlers/EmailHandlers.cs b/MessagingService/Handlers/EmailHandlers.cs
new file mode 100644
index 0000000..8e37157
--- /dev/null
+++ b/MessagingService/Handlers/EmailHandlers.cs
@@ -0,0 +1,76 @@
+using MediatR;
+using MessagingService.BusinessLogic.Requests;
+using MessagingService.DataTransferObjects;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Shared.Results.Web;
+using SimpleResults;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using EmailAttachment = MessagingService.BusinessLogic.Requests.EmailAttachment;
+using FileType = MessagingService.BusinessLogic.Requests.FileType;
+using ILogger = Microsoft.Extensions.Logging.ILogger;
+
+namespace MessagingService.Handlers
+{
+ public static class EmailHandlers
+ {
+ public static async Task SendEmail(SendEmailRequest sendEmailRequest,
+ IMediator mediator,
+ CancellationToken cancellationToken) {
+ Guid messageId = sendEmailRequest.MessageId.HasValue ? sendEmailRequest.MessageId.Value : Guid.NewGuid();
+
+ List emailAttachments = new();
+ foreach (DataTransferObjects.EmailAttachment emailAttachment in sendEmailRequest.EmailAttachments)
+ {
+ emailAttachments.Add(new EmailAttachment
+ {
+ FileData = emailAttachment.FileData,
+ FileType = ConvertFileType(emailAttachment.FileType),
+ Filename = emailAttachment.Filename
+ });
+ }
+
+ // Create the command
+ EmailCommands.SendEmailCommand command = new(sendEmailRequest.ConnectionIdentifier,
+ messageId,
+ sendEmailRequest.FromAddress,
+ sendEmailRequest.ToAddresses,
+ sendEmailRequest.Subject,
+ sendEmailRequest.Body,
+ sendEmailRequest.IsHtml,
+ emailAttachments);
+
+ // Route the command
+ Result result = await mediator.Send(command, cancellationToken);
+
+ return ResponseFactory.FromResult(result, (r) => new SendEmailResponse
+ {
+ MessageId = r
+ });
+ }
+
+ public static async Task ResendEmail(ResendEmailRequest resendEmailRequest,
+ IMediator mediator,
+ CancellationToken cancellationToken) {
+ // Create the command
+ EmailCommands.ResendEmailCommand command = new(resendEmailRequest.ConnectionIdentifier,
+ resendEmailRequest.MessageId);
+ // Route the command
+ Result result = await mediator.Send(command, cancellationToken);
+
+ return ResponseFactory.FromResult(result);
+ }
+
+ internal static FileType ConvertFileType(DataTransferObjects.FileType emailAttachmentFileType) {
+ switch (emailAttachmentFileType) {
+ case DataTransferObjects.FileType.PDF:
+ return FileType.PDF;
+ default:
+ return FileType.None;
+ }
+ }
+ }
+}
diff --git a/MessagingService/Handlers/SMSHandlers.cs b/MessagingService/Handlers/SMSHandlers.cs
new file mode 100644
index 0000000..d32d82c
--- /dev/null
+++ b/MessagingService/Handlers/SMSHandlers.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using MediatR;
+using MessagingService.BusinessLogic.Requests;
+using MessagingService.DataTransferObjects;
+using Microsoft.AspNetCore.Http;
+using Shared.Results.Web;
+using SimpleResults;
+
+namespace MessagingService.Handlers;
+
+public static class SMSHandlers {
+ public static async Task SendSMS(SendSMSRequest sendSMSRequest,
+ IMediator mediator,
+ CancellationToken cancellationToken) {
+ Guid messageId = sendSMSRequest.MessageId.HasValue ? sendSMSRequest.MessageId.Value : Guid.NewGuid();
+ // Create the command
+ SMSCommands.SendSMSCommand command = new(sendSMSRequest.ConnectionIdentifier,
+ messageId,
+ sendSMSRequest.Sender,
+ sendSMSRequest.Destination,
+ sendSMSRequest.Message);
+ // Route the command
+ Result result = await mediator.Send(command, cancellationToken);
+ return ResponseFactory.FromResult(result, (r) => new SendSMSResponse
+ {
+ MessageId = r
+ });
+ }
+
+ public static async Task ResendSMS(ResendSMSRequest resendSMSRequest,
+ IMediator mediator,
+ CancellationToken cancellationToken) {
+ // Create the command
+ SMSCommands.ResendSMSCommand command = new(resendSMSRequest.ConnectionIdentifier,
+ resendSMSRequest.MessageId);
+ // Route the command
+ Result result = await mediator.Send(command, cancellationToken);
+ return ResponseFactory.FromResult(result);
+ }
+}
\ No newline at end of file
diff --git a/MessagingService/MessagingService.csproj b/MessagingService/MessagingService.csproj
index 63561f8..0fb2325 100644
--- a/MessagingService/MessagingService.csproj
+++ b/MessagingService/MessagingService.csproj
@@ -18,13 +18,13 @@
-
-
+
+
-
-
+
+
diff --git a/MessagingService/Startup.cs b/MessagingService/Startup.cs
index b97e1a1..a2249e6 100644
--- a/MessagingService/Startup.cs
+++ b/MessagingService/Startup.cs
@@ -6,6 +6,7 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
+using MessagingService.Endpoints;
namespace MessagingService
{
@@ -92,13 +93,15 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF
app.AddExceptionHandler();
app.UseRouting();
-
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
- endpoints.MapControllers();
+ endpoints.MapEmailEndpoints();
+ endpoints.MapSMSEndpoints();
+ endpoints.MapDomainEventEndpoint();
+
endpoints.MapHealthChecks("health", new HealthCheckOptions()
{
Predicate = _ => true,