diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..3729ff0
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,25 @@
+**/.classpath
+**/.dockerignore
+**/.env
+**/.git
+**/.gitignore
+**/.project
+**/.settings
+**/.toolstarget
+**/.vs
+**/.vscode
+**/*.*proj.user
+**/*.dbmdl
+**/*.jfm
+**/azds.yaml
+**/bin
+**/charts
+**/docker-compose*
+**/Dockerfile*
+**/node_modules
+**/npm-debug.log
+**/obj
+**/secrets.dev.yaml
+**/values.dev.yaml
+LICENSE
+README.md
\ No newline at end of file
diff --git a/MessagingService.BusinessLogic/Common/ConfigurationReaderConnectionStringRepository.cs b/MessagingService.BusinessLogic/Common/ConfigurationReaderConnectionStringRepository.cs
new file mode 100644
index 0000000..f02dea5
--- /dev/null
+++ b/MessagingService.BusinessLogic/Common/ConfigurationReaderConnectionStringRepository.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MessagingService.BusinessLogic.Common
+{
+ using System.Data.Common;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Microsoft.Data.SqlClient;
+ using Shared.General;
+ using Shared.Repositories;
+
+ [ExcludeFromCodeCoverage]
+ public class ConfigurationReaderConnectionStringRepository : IConnectionStringConfigurationRepository
+ {
+ #region Methods
+
+ ///
+ /// Creates the connection string.
+ ///
+ /// The external identifier.
+ /// Type of the connection string.
+ /// The connection string.
+ /// The cancellation token.
+ public async Task CreateConnectionString(String externalIdentifier,
+ ConnectionStringType connectionStringType,
+ String connectionString,
+ CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException("This is only required to complete the interface");
+ }
+
+ ///
+ /// Deletes the connection string configuration.
+ ///
+ /// The external identifier.
+ /// Type of the connection string.
+ /// The cancellation token.
+ public async Task DeleteConnectionStringConfiguration(String externalIdentifier,
+ ConnectionStringType connectionStringType,
+ CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException("This is only required to complete the interface");
+ }
+
+ ///
+ /// Gets the connection string.
+ ///
+ /// The external identifier.
+ /// Type of the connection string.
+ /// The cancellation token.
+ ///
+ public async Task GetConnectionString(String externalIdentifier,
+ ConnectionStringType connectionStringType,
+ CancellationToken cancellationToken)
+ {
+ String connectionString = string.Empty;
+ String databaseName = string.Empty;
+ switch (connectionStringType)
+ {
+ case ConnectionStringType.ReadModel:
+ databaseName = "EstateReportingReadModel" + externalIdentifier;
+ connectionString = ConfigurationReader.GetConnectionString("EstateReportingReadModel");
+ break;
+ default:
+ throw new NotSupportedException($"Connection String type [{connectionStringType}] is not supported");
+ }
+
+ DbConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectionString)
+ {
+ InitialCatalog = databaseName
+ };
+
+ return builder.ToString();
+ }
+
+ #endregion
+ }
+}
diff --git a/MessagingService.BusinessLogic/MessagingService.BusinessLogic.csproj b/MessagingService.BusinessLogic/MessagingService.BusinessLogic.csproj
new file mode 100644
index 0000000..ba572f1
--- /dev/null
+++ b/MessagingService.BusinessLogic/MessagingService.BusinessLogic.csproj
@@ -0,0 +1,20 @@
+
+
+
+ netcoreapp3.1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MessagingService.BusinessLogic/RequestHandlers/EmailRequestHandler.cs b/MessagingService.BusinessLogic/RequestHandlers/EmailRequestHandler.cs
new file mode 100644
index 0000000..f5efb00
--- /dev/null
+++ b/MessagingService.BusinessLogic/RequestHandlers/EmailRequestHandler.cs
@@ -0,0 +1,65 @@
+namespace MessagingService.BusinessLogic.RequestHandlers
+{
+ using System;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using MediatR;
+ using Requests;
+ using Services;
+
+ ///
+ ///
+ ///
+ ///
+ public class EmailRequestHandler : IRequestHandler
+ {
+ #region Fields
+
+ ///
+ /// The email domain service
+ ///
+ private readonly IEmailDomainService EmailDomainService;
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The email domain service.
+ public EmailRequestHandler(IEmailDomainService emailDomainService)
+ {
+ this.EmailDomainService = emailDomainService;
+ }
+
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Handles a request
+ ///
+ /// The request
+ /// Cancellation token
+ ///
+ /// Response from the request
+ ///
+ public async Task Handle(SendEmailRequest request,
+ CancellationToken cancellationToken)
+ {
+ await this.EmailDomainService.SendEmailMessage(request.ConnectionIdentifier,
+ request.MessageId,
+ request.FromAddress,
+ request.ToAddresses,
+ request.Subject,
+ request.Body,
+ request.IsHtml,
+ cancellationToken);
+
+ return string.Empty;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/MessagingService.BusinessLogic/Requests/SendEmailRequest.cs b/MessagingService.BusinessLogic/Requests/SendEmailRequest.cs
new file mode 100644
index 0000000..95a0a00
--- /dev/null
+++ b/MessagingService.BusinessLogic/Requests/SendEmailRequest.cs
@@ -0,0 +1,130 @@
+namespace MessagingService.BusinessLogic.Requests
+{
+ using System;
+ using System.Collections.Generic;
+ using MediatR;
+
+ ///
+ ///
+ ///
+ ///
+ public class SendEmailRequest : IRequest
+ {
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The connection identifier.
+ /// The message identifier.
+ /// From address.
+ /// To addresses.
+ /// The subject.
+ /// The body.
+ /// if set to true [is HTML].
+ private SendEmailRequest(Guid connectionIdentifier,
+ Guid messageId,
+ String fromAddress,
+ List toAddresses,
+ String subject,
+ String body,
+ Boolean isHtml)
+ {
+ this.ConnectionIdentifier = connectionIdentifier;
+ this.MessageId = messageId;
+ this.FromAddress = fromAddress;
+ this.ToAddresses = toAddresses;
+ this.Subject = subject;
+ this.Body = body;
+ this.IsHtml = isHtml;
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets the body.
+ ///
+ ///
+ /// The body.
+ ///
+ public String Body { get; }
+
+ ///
+ /// Gets the connection identifier.
+ ///
+ ///
+ /// The connection identifier.
+ ///
+ public Guid ConnectionIdentifier { get; }
+
+ ///
+ /// Gets from address.
+ ///
+ ///
+ /// From address.
+ ///
+ public String FromAddress { get; }
+
+ ///
+ /// Gets a value indicating whether this instance is HTML.
+ ///
+ ///
+ /// true if this instance is HTML; otherwise, false.
+ ///
+ public Boolean IsHtml { get; }
+
+ ///
+ /// Gets the message identifier.
+ ///
+ ///
+ /// The message identifier.
+ ///
+ public Guid MessageId { get; }
+
+ ///
+ /// Gets the subject.
+ ///
+ ///
+ /// The subject.
+ ///
+ public String Subject { get; }
+
+ ///
+ /// Converts to address.
+ ///
+ ///
+ /// To address.
+ ///
+ public List ToAddresses { get; }
+
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Creates the specified from address.
+ ///
+ /// The connection identifier.
+ /// The message identifier.
+ /// From address.
+ /// To addresses.
+ /// The subject.
+ /// The body.
+ /// if set to true [is HTML].
+ ///
+ public static SendEmailRequest Create(Guid connectionIdentifier,
+ Guid messageId,
+ String fromAddress,
+ List toAddresses,
+ String subject,
+ String body,
+ Boolean isHtml)
+ {
+ return new SendEmailRequest(connectionIdentifier, messageId, fromAddress, toAddresses, subject, body, isHtml);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/MessagingService.BusinessLogic/Services/EmailDomainService.cs b/MessagingService.BusinessLogic/Services/EmailDomainService.cs
new file mode 100644
index 0000000..41eb16c
--- /dev/null
+++ b/MessagingService.BusinessLogic/Services/EmailDomainService.cs
@@ -0,0 +1,91 @@
+namespace MessagingService.BusinessLogic.Services
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using EmailMessageAggregate;
+ using EmailServices;
+ using Shared.DomainDrivenDesign.EventStore;
+ using Shared.EventStore.EventStore;
+
+ ///
+ ///
+ ///
+ ///
+ public class EmailDomainService : IEmailDomainService
+ {
+ #region Fields
+
+ ///
+ /// The aggregate repository manager
+ ///
+ private readonly IAggregateRepositoryManager AggregateRepositoryManager;
+
+ ///
+ /// The email service proxy
+ ///
+ private readonly IEmailServiceProxy EmailServiceProxy;
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The aggregate repository manager.
+ /// The email service proxy.
+ public EmailDomainService(IAggregateRepositoryManager aggregateRepositoryManager,
+ IEmailServiceProxy emailServiceProxy)
+ {
+ this.AggregateRepositoryManager = aggregateRepositoryManager;
+ this.EmailServiceProxy = emailServiceProxy;
+ }
+
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Sends the email message.
+ ///
+ /// The connection identifier.
+ /// The message identifier.
+ /// From address.
+ /// To addresses.
+ /// The subject.
+ /// The body.
+ /// if set to true [is HTML].
+ /// The cancellation token.
+ public async Task SendEmailMessage(Guid connectionIdentifier,
+ Guid messageId,
+ String fromAddress,
+ List toAddresses,
+ String subject,
+ String body,
+ Boolean isHtml,
+ CancellationToken cancellationToken)
+ {
+ IAggregateRepository emailAggregateRepository = this.AggregateRepositoryManager.GetAggregateRepository(connectionIdentifier);
+
+ // Rehydrate Email Message aggregate
+ EmailAggregate emailAggregate = await emailAggregateRepository.GetLatestVersion(messageId, cancellationToken);
+
+ // send message to provider (record event)
+ emailAggregate.SendRequestToProvider(fromAddress, toAddresses, subject, body, isHtml);
+
+ // Make call to Email provider here
+ EmailServiceProxyResponse emailResponse =
+ await this.EmailServiceProxy.SendEmail(messageId, fromAddress, toAddresses, subject, body, isHtml, cancellationToken);
+
+ // response message from provider (record event)
+ emailAggregate.ReceiveResponseFromProvider(emailResponse.RequestIdentifier, emailResponse.EmailIdentifier);
+
+ // Save Changes to persistance
+ await emailAggregateRepository.SaveChanges(emailAggregate, cancellationToken);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/MessagingService.BusinessLogic/Services/EmailServices/EmailServiceProxyResponse.cs b/MessagingService.BusinessLogic/Services/EmailServices/EmailServiceProxyResponse.cs
new file mode 100644
index 0000000..516d308
--- /dev/null
+++ b/MessagingService.BusinessLogic/Services/EmailServices/EmailServiceProxyResponse.cs
@@ -0,0 +1,51 @@
+namespace MessagingService.BusinessLogic.Services.EmailServices
+{
+ using System;
+ using System.Net;
+
+ ///
+ ///
+ ///
+ public class EmailServiceProxyResponse
+ {
+ ///
+ /// Gets or sets the API status code.
+ ///
+ ///
+ /// The API status code.
+ ///
+ public HttpStatusCode ApiStatusCode { get; set; }
+
+ ///
+ /// Gets or sets the request identifier.
+ ///
+ ///
+ /// The request identifier.
+ ///
+ public String RequestIdentifier { get; set; }
+
+ ///
+ /// Gets or sets the email identifier.
+ ///
+ ///
+ /// The email identifier.
+ ///
+ public String EmailIdentifier { get; set; }
+
+ ///
+ /// Gets or sets the error code.
+ ///
+ ///
+ /// The error code.
+ ///
+ public String ErrorCode { get; set; }
+
+ ///
+ /// Gets or sets the error.
+ ///
+ ///
+ /// The error.
+ ///
+ public String Error { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/MessagingService.BusinessLogic/Services/EmailServices/IEmailServiceProxy.cs b/MessagingService.BusinessLogic/Services/EmailServices/IEmailServiceProxy.cs
new file mode 100644
index 0000000..3a8780e
--- /dev/null
+++ b/MessagingService.BusinessLogic/Services/EmailServices/IEmailServiceProxy.cs
@@ -0,0 +1,33 @@
+namespace MessagingService.BusinessLogic.Services.EmailServices
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Threading;
+ using System.Threading.Tasks;
+
+ public interface IEmailServiceProxy
+ {
+ #region Methods
+
+ ///
+ /// Sends the email.
+ ///
+ /// The message identifier.
+ /// From address.
+ /// To addresses.
+ /// The subject.
+ /// The body.
+ /// if set to true [is HTML].
+ /// The cancellation token.
+ ///
+ Task SendEmail(Guid messageId,
+ String fromAddress,
+ List toAddresses,
+ String subject,
+ String body,
+ Boolean isHtml,
+ CancellationToken cancellationToken);
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/MessagingService.BusinessLogic/Services/EmailServices/IntegrationTest/IntegrationTestEmailServiceProxy.cs b/MessagingService.BusinessLogic/Services/EmailServices/IntegrationTest/IntegrationTestEmailServiceProxy.cs
new file mode 100644
index 0000000..8036e5d
--- /dev/null
+++ b/MessagingService.BusinessLogic/Services/EmailServices/IntegrationTest/IntegrationTestEmailServiceProxy.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MessagingService.Service.Services.Email.IntegrationTest
+{
+ using BusinessLogic.Requests;
+ using BusinessLogic.Services.EmailServices;
+
+ public class IntegrationTestEmailServiceProxy : IEmailServiceProxy
+ {
+ ///
+ /// Sends the email.
+ ///
+ /// The request.
+ /// The cancellation token.
+ ///
+ public async Task SendEmail(Guid messageId,
+ String fromAddress,
+ List toAddresses,
+ String subject,
+ String body,
+ Boolean isHtml, CancellationToken cancellationToken)
+ {
+ return new EmailServiceProxyResponse
+ {
+ RequestIdentifier = "requestid",
+ EmailIdentifier = "emailid",
+ ApiStatusCode = HttpStatusCode.OK,
+ Error = String.Empty,
+ ErrorCode = String.Empty
+ };
+ }
+ }
+}
diff --git a/MessagingService.BusinessLogic/Services/EmailServices/Smtp2Go/Smtp2GoProxy.cs b/MessagingService.BusinessLogic/Services/EmailServices/Smtp2Go/Smtp2GoProxy.cs
new file mode 100644
index 0000000..9aa4432
--- /dev/null
+++ b/MessagingService.BusinessLogic/Services/EmailServices/Smtp2Go/Smtp2GoProxy.cs
@@ -0,0 +1,103 @@
+namespace MessagingService.BusinessLogic.Services.EmailServices.Smtp2Go
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Net.Http;
+ using System.Text;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Newtonsoft.Json;
+ using Service.Services.Email.Smtp2Go;
+ using Shared.General;
+
+ ///
+ ///
+ ///
+ ///
+ public class Smtp2GoProxy : IEmailServiceProxy
+ {
+ #region Fields
+
+ ///
+ /// The HTTP client
+ ///
+ private readonly HttpClient HttpClient;
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The HTTP client.
+ public Smtp2GoProxy(HttpClient httpClient)
+ {
+ this.HttpClient = httpClient;
+ }
+
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Sends the email.
+ ///
+ /// The message identifier.
+ /// From address.
+ /// To addresses.
+ /// The subject.
+ /// The body.
+ /// if set to true [is HTML].
+ /// The cancellation token.
+ ///
+ public async Task SendEmail(Guid messageId,
+ String fromAddress,
+ List toAddresses,
+ String subject,
+ String body,
+ Boolean isHtml,
+ CancellationToken cancellationToken)
+ {
+ EmailServiceProxyResponse response = null;
+
+ // Translate the request message
+ Smtp2GoSendEmailRequest apiRequest = new Smtp2GoSendEmailRequest
+ {
+ ApiKey = ConfigurationReader.GetValue("SMTP2GoAPIKey"),
+ HTMLBody = isHtml ? body : string.Empty,
+ TextBody = isHtml ? string.Empty : body,
+ Sender = fromAddress,
+ Subject = subject,
+ TestMode = false,
+ To = toAddresses.ToArray()
+ };
+
+ String requestSerialised = JsonConvert.SerializeObject(apiRequest);
+ StringContent content = new StringContent(requestSerialised, Encoding.UTF8, "application/json");
+
+ using(HttpClient client = new HttpClient())
+ {
+ client.BaseAddress = new Uri(ConfigurationReader.GetValue("SMTP2GoBaseAddress"));
+
+ HttpResponseMessage httpResponse = await client.PostAsync("email/send", content, cancellationToken);
+
+ Smtp2GoSendEmailResponse apiResponse = JsonConvert.DeserializeObject(await httpResponse.Content.ReadAsStringAsync());
+
+ // Translate the Response
+ response = new EmailServiceProxyResponse
+ {
+ ApiStatusCode = httpResponse.StatusCode,
+ EmailIdentifier = apiResponse.Data.EmailId,
+ Error = apiResponse.Data.Error,
+ ErrorCode = apiResponse.Data.ErrorCode,
+ RequestIdentifier = apiResponse.RequestId
+ };
+ }
+
+ return response;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/MessagingService.BusinessLogic/Services/EmailServices/Smtp2Go/Smtp2GoSendEmailRequest.cs b/MessagingService.BusinessLogic/Services/EmailServices/Smtp2Go/Smtp2GoSendEmailRequest.cs
new file mode 100644
index 0000000..4fbc2c5
--- /dev/null
+++ b/MessagingService.BusinessLogic/Services/EmailServices/Smtp2Go/Smtp2GoSendEmailRequest.cs
@@ -0,0 +1,89 @@
+using System;
+using Newtonsoft.Json;
+
+namespace MessagingService.Service.Services.Email.Smtp2Go
+{
+ public class Smtp2GoSendEmailRequest
+ {
+ ///
+ /// Gets or sets the API key.
+ ///
+ ///
+ /// The API key.
+ ///
+ [JsonProperty("api_key")]
+ public String ApiKey { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether [test mode].
+ ///
+ ///
+ /// true if [test mode]; otherwise, false.
+ ///
+ [JsonProperty("test")]
+ public Boolean TestMode { get; set; }
+
+ ///
+ /// Gets or sets the sender.
+ ///
+ ///
+ /// The sender.
+ ///
+ [JsonProperty("sender")]
+ public String Sender { get; set; }
+
+ ///
+ /// Gets or sets to.
+ ///
+ ///
+ /// To.
+ ///
+ [JsonProperty("to")]
+ public String[] To { get; set; }
+
+ ///
+ /// Gets or sets the cc.
+ ///
+ ///
+ /// The cc.
+ ///
+ [JsonProperty("cc")]
+ public String[] CC { get; set; }
+
+ ///
+ /// Gets or sets the BCC.
+ ///
+ ///
+ /// The BCC.
+ ///
+ [JsonProperty("bcc")]
+ public String[] BCC { get; set; }
+
+ ///
+ /// Gets or sets the subject.
+ ///
+ ///
+ /// The subject.
+ ///
+ [JsonProperty("subject")]
+ public String Subject { get; set; }
+
+ ///
+ /// Gets or sets the HTML body.
+ ///
+ ///
+ /// The HTML body.
+ ///
+ [JsonProperty("html_body")]
+ public String HTMLBody { get; set; }
+
+ ///
+ /// Gets or sets the text body.
+ ///
+ ///
+ /// The text body.
+ ///
+ [JsonProperty("text_body")]
+ public String TextBody { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/MessagingService.BusinessLogic/Services/EmailServices/Smtp2Go/Smtp2GoSendEmailResponse.cs b/MessagingService.BusinessLogic/Services/EmailServices/Smtp2Go/Smtp2GoSendEmailResponse.cs
new file mode 100644
index 0000000..a7af089
--- /dev/null
+++ b/MessagingService.BusinessLogic/Services/EmailServices/Smtp2Go/Smtp2GoSendEmailResponse.cs
@@ -0,0 +1,26 @@
+using System;
+using Newtonsoft.Json;
+
+namespace MessagingService.Service.Services.Email.Smtp2Go
+{
+ public class Smtp2GoSendEmailResponse
+ {
+ ///
+ /// Gets or sets the request identifier.
+ ///
+ ///
+ /// The request identifier.
+ ///
+ [JsonProperty("request_id")]
+ public String RequestId { get; set; }
+
+ ///
+ /// Gets or sets the data.
+ ///
+ ///
+ /// The data.
+ ///
+ [JsonProperty("data")]
+ public Smtp2GoSendEmailResponseData Data { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/MessagingService.BusinessLogic/Services/EmailServices/Smtp2Go/Smtp2GoSendEmailResponseData.cs b/MessagingService.BusinessLogic/Services/EmailServices/Smtp2Go/Smtp2GoSendEmailResponseData.cs
new file mode 100644
index 0000000..988ef28
--- /dev/null
+++ b/MessagingService.BusinessLogic/Services/EmailServices/Smtp2Go/Smtp2GoSendEmailResponseData.cs
@@ -0,0 +1,62 @@
+using System;
+using Newtonsoft.Json;
+
+namespace MessagingService.Service.Services.Email.Smtp2Go
+{
+ public class Smtp2GoSendEmailResponseData
+ {
+ ///
+ /// Gets or sets the failed.
+ ///
+ ///
+ /// The failed.
+ ///
+ [JsonProperty("failed")]
+ public Int32 Failed { get; set; }
+
+ ///
+ /// Gets or sets the failures.
+ ///
+ ///
+ /// The failures.
+ ///
+ [JsonProperty("failures")]
+ public String[] Failures { get; set; }
+
+ ///
+ /// Gets or sets the email identifier.
+ ///
+ ///
+ /// The email identifier.
+ ///
+ [JsonProperty("email_id")]
+ public String EmailId { get; set; }
+
+ ///
+ /// Gets or sets the succeesful.
+ ///
+ ///
+ /// The succeesful.
+ ///
+ [JsonProperty("succeeded")]
+ public Int32 Succeesful { get; set; }
+
+ ///
+ /// Gets or sets the error.
+ ///
+ ///
+ /// The error.
+ ///
+ [JsonProperty("error")]
+ public String Error { get; set; }
+
+ ///
+ /// Gets or sets the error code.
+ ///
+ ///
+ /// The error code.
+ ///
+ [JsonProperty("error_code")]
+ public String ErrorCode { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/MessagingService.BusinessLogic/Services/IEmailDomainService.cs b/MessagingService.BusinessLogic/Services/IEmailDomainService.cs
new file mode 100644
index 0000000..d16838b
--- /dev/null
+++ b/MessagingService.BusinessLogic/Services/IEmailDomainService.cs
@@ -0,0 +1,38 @@
+namespace MessagingService.BusinessLogic.Services
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Threading;
+ using System.Threading.Tasks;
+
+ ///
+ ///
+ ///
+ public interface IEmailDomainService
+ {
+ #region Methods
+
+ ///
+ /// Sends the email message.
+ ///
+ /// The connection identifier.
+ /// The message identifier.
+ /// From address.
+ /// To addresses.
+ /// The subject.
+ /// The body.
+ /// if set to true [is HTML].
+ /// The cancellation token.
+ ///
+ Task SendEmailMessage(Guid connectionIdentifier,
+ Guid messageId,
+ String fromAddress,
+ List toAddresses,
+ String subject,
+ String body,
+ Boolean isHtml,
+ CancellationToken cancellationToken);
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/MessagingService.DataTransferObjects/MessagingService.DataTransferObjects.csproj b/MessagingService.DataTransferObjects/MessagingService.DataTransferObjects.csproj
new file mode 100644
index 0000000..9f5c4f4
--- /dev/null
+++ b/MessagingService.DataTransferObjects/MessagingService.DataTransferObjects.csproj
@@ -0,0 +1,7 @@
+
+
+
+ netstandard2.0
+
+
+
diff --git a/MessagingService.DataTransferObjects/SendEmailRequest.cs b/MessagingService.DataTransferObjects/SendEmailRequest.cs
new file mode 100644
index 0000000..cec5f51
--- /dev/null
+++ b/MessagingService.DataTransferObjects/SendEmailRequest.cs
@@ -0,0 +1,60 @@
+namespace MessagingService.DataTransferObjects
+{
+ using System;
+ using System.Collections.Generic;
+
+ public class SendEmailRequest
+ {
+ #region Properties
+
+ ///
+ /// Gets or sets the body.
+ ///
+ ///
+ /// The body.
+ ///
+ public String Body { get; set; }
+
+ ///
+ /// Gets or sets the connection identifier.
+ ///
+ ///
+ /// The connection identifier.
+ ///
+ public Guid ConnectionIdentifier { get; set; }
+
+ ///
+ /// Gets or sets from address.
+ ///
+ ///
+ /// From address.
+ ///
+ public String FromAddress { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether this instance is HTML.
+ ///
+ ///
+ /// true if this instance is HTML; otherwise, false.
+ ///
+ public Boolean IsHtml { get; set; }
+
+ ///
+ /// Gets or sets the subject.
+ ///
+ ///
+ /// The subject.
+ ///
+ public String Subject { get; set; }
+
+ ///
+ /// Gets or sets to addresses.
+ ///
+ ///
+ /// To addresses.
+ ///
+ public List ToAddresses { get; set; }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/MessagingService.DataTransferObjects/SendEmailResponse.cs b/MessagingService.DataTransferObjects/SendEmailResponse.cs
new file mode 100644
index 0000000..6815675
--- /dev/null
+++ b/MessagingService.DataTransferObjects/SendEmailResponse.cs
@@ -0,0 +1,22 @@
+namespace MessagingService.DataTransferObjects
+{
+ using System;
+
+ ///
+ ///
+ ///
+ public class SendEmailResponse
+ {
+ #region Properties
+
+ ///
+ /// Gets or sets the message identifier.
+ ///
+ ///
+ /// The message identifier.
+ ///
+ public Guid MessageId { get; set; }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/MessagingService.EmailMessageAggregate/EmailAggregate.cs b/MessagingService.EmailMessageAggregate/EmailAggregate.cs
new file mode 100644
index 0000000..0a77798
--- /dev/null
+++ b/MessagingService.EmailMessageAggregate/EmailAggregate.cs
@@ -0,0 +1,158 @@
+namespace MessagingService.EmailMessageAggregate
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using EmailMessage.DomainEvents;
+ using Microsoft.EntityFrameworkCore.Migrations.Operations;
+ using Shared.DomainDrivenDesign.EventSourcing;
+ using Shared.DomainDrivenDesign.EventStore;
+ using Shared.General;
+
+ ///
+ ///
+ ///
+ ///
+ public class EmailAggregate : Aggregate
+ {
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ [ExcludeFromCodeCoverage]
+ public EmailAggregate()
+ {
+ this.Recipients = new List();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The aggregate identifier.
+ private EmailAggregate(Guid aggregateId)
+ {
+ Guard.ThrowIfInvalidGuid(aggregateId, "Aggregate Id cannot be an Empty Guid");
+
+ this.AggregateId = aggregateId;
+ this.Recipients = new List();
+ }
+
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Messages the send to recipient failure.
+ ///
+ public void MessageSendToRecipientFailure()
+ {
+ }
+
+ ///
+ /// Messages the send to recipient successful.
+ ///
+ public void MessageSendToRecipientSuccessful()
+ {
+ }
+
+ ///
+ /// Receives the response from provider.
+ ///
+ /// The provider request reference.
+ /// The provider email reference.
+ public void ReceiveResponseFromProvider(String providerRequestReference,
+ String providerEmailReference)
+ {
+ ResponseReceivedFromProviderEvent responseReceivedFromProviderEvent = ResponseReceivedFromProviderEvent.Create(this.AggregateId, providerRequestReference, providerEmailReference);
+
+ this.ApplyAndPend(responseReceivedFromProviderEvent);
+ }
+
+ ///
+ /// Sends the request to provider.
+ ///
+ /// From address.
+ /// To addresses.
+ /// The subject.
+ /// The body.
+ /// if set to true [is HTML].
+ public void SendRequestToProvider(String fromAddress,
+ List toAddresses,
+ String subject,
+ String body,
+ Boolean isHtml)
+ {
+ RequestSentToProviderEvent requestSentToProviderEvent = RequestSentToProviderEvent.Create(this.AggregateId, fromAddress, toAddresses, subject, body, isHtml);
+
+ this.ApplyAndPend(requestSentToProviderEvent);
+ }
+
+ public String ProviderRequestReference { get; private set; }
+ public String ProviderEmailReference { get; private set; }
+ public String FromAddress { get; private set; }
+ public String Subject { get; private set; }
+ public String Body { get; private set; }
+ public Boolean IsHtml { get; private set; }
+
+ private List Recipients;
+
+
+ ///
+ /// Gets the metadata.
+ ///
+ ///
+ [ExcludeFromCodeCoverage]
+ protected override Object GetMetadata()
+ {
+ return null;
+ }
+
+ ///
+ /// Plays the event.
+ ///
+ /// The domain event.
+ protected override void PlayEvent(DomainEvent domainEvent)
+ {
+ this.PlayEvent((dynamic)domainEvent);
+ }
+
+ private void PlayEvent(RequestSentToProviderEvent domainEvent)
+ {
+ this.Body = domainEvent.Body;
+ this.Subject = domainEvent.Subject;
+ this.IsHtml = domainEvent.IsHtml;
+ this.FromAddress = domainEvent.FromAddress;
+
+ foreach (String domainEventToAddress in domainEvent.ToAddresses)
+ {
+ MessageRecipient messageRecipient = new MessageRecipient();
+ messageRecipient.Create(domainEventToAddress);
+ this.Recipients.Add(messageRecipient);
+ }
+ }
+
+ private void PlayEvent(ResponseReceivedFromProviderEvent domainEvent)
+ {
+ this.ProviderEmailReference = domainEvent.ProviderEmailReference;
+ this.ProviderRequestReference = domainEvent.ProviderRequestReference;
+ }
+
+ #endregion
+ }
+
+ internal class MessageRecipient
+ {
+ internal String ToAddress { get; private set; }
+
+ internal MessageRecipient()
+ {
+
+ }
+
+ internal void Create(String toAddress)
+ {
+ this.ToAddress = toAddress;
+ }
+ }
+}
\ No newline at end of file
diff --git a/MessagingService.EmailMessageAggregate/MessagingService.EmailMessageAggregate.csproj b/MessagingService.EmailMessageAggregate/MessagingService.EmailMessageAggregate.csproj
new file mode 100644
index 0000000..318d813
--- /dev/null
+++ b/MessagingService.EmailMessageAggregate/MessagingService.EmailMessageAggregate.csproj
@@ -0,0 +1,15 @@
+
+
+
+ netcoreapp3.1
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MessagingService.sln b/MessagingService.sln
new file mode 100644
index 0000000..0d0e167
--- /dev/null
+++ b/MessagingService.sln
@@ -0,0 +1,60 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.28803.156
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{BF2482A1-13C0-4305-B732-AB62EBD9429B}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{9AEE6ADE-DD45-4605-A933-E06CF0BA4203}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagingService", "MessagingService\MessagingService.csproj", "{3A9325F7-4A47-4B4E-8A99-58AEBD33FF3F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagingService.DataTransferObjects", "MessagingService.DataTransferObjects\MessagingService.DataTransferObjects.csproj", "{2E8C8118-8456-4CBD-BD68-578B838200CC}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MessagingService.BusinessLogic", "MessagingService.BusinessLogic\MessagingService.BusinessLogic.csproj", "{A372C9E9-C412-4A36-961F-A358FF8F700E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessagingService.EmailMessageAggregate", "MessagingService.EmailMessageAggregate\MessagingService.EmailMessageAggregate.csproj", "{0BD162FA-64F1-44D5-9C06-749400F84FA5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MessagingService.EmailMessage.DomainEvents", "NessagingService.EmailMessage.DomainEvents\MessagingService.EmailMessage.DomainEvents.csproj", "{689A531D-86EB-4656-81B3-4C6E569A7E4B}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {3A9325F7-4A47-4B4E-8A99-58AEBD33FF3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3A9325F7-4A47-4B4E-8A99-58AEBD33FF3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3A9325F7-4A47-4B4E-8A99-58AEBD33FF3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3A9325F7-4A47-4B4E-8A99-58AEBD33FF3F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2E8C8118-8456-4CBD-BD68-578B838200CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2E8C8118-8456-4CBD-BD68-578B838200CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2E8C8118-8456-4CBD-BD68-578B838200CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2E8C8118-8456-4CBD-BD68-578B838200CC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A372C9E9-C412-4A36-961F-A358FF8F700E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A372C9E9-C412-4A36-961F-A358FF8F700E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A372C9E9-C412-4A36-961F-A358FF8F700E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A372C9E9-C412-4A36-961F-A358FF8F700E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0BD162FA-64F1-44D5-9C06-749400F84FA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0BD162FA-64F1-44D5-9C06-749400F84FA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0BD162FA-64F1-44D5-9C06-749400F84FA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0BD162FA-64F1-44D5-9C06-749400F84FA5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {689A531D-86EB-4656-81B3-4C6E569A7E4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {689A531D-86EB-4656-81B3-4C6E569A7E4B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {689A531D-86EB-4656-81B3-4C6E569A7E4B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {689A531D-86EB-4656-81B3-4C6E569A7E4B}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {3A9325F7-4A47-4B4E-8A99-58AEBD33FF3F} = {BF2482A1-13C0-4305-B732-AB62EBD9429B}
+ {2E8C8118-8456-4CBD-BD68-578B838200CC} = {BF2482A1-13C0-4305-B732-AB62EBD9429B}
+ {A372C9E9-C412-4A36-961F-A358FF8F700E} = {BF2482A1-13C0-4305-B732-AB62EBD9429B}
+ {0BD162FA-64F1-44D5-9C06-749400F84FA5} = {BF2482A1-13C0-4305-B732-AB62EBD9429B}
+ {689A531D-86EB-4656-81B3-4C6E569A7E4B} = {BF2482A1-13C0-4305-B732-AB62EBD9429B}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {1929C0FE-8CEB-4D0E-BD22-9E5E16E2B49F}
+ EndGlobalSection
+EndGlobal
diff --git a/MessagingService/Common/ClaimsHelper.cs b/MessagingService/Common/ClaimsHelper.cs
new file mode 100644
index 0000000..a6e13e6
--- /dev/null
+++ b/MessagingService/Common/ClaimsHelper.cs
@@ -0,0 +1,114 @@
+using Shared.Exceptions;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Security.Claims;
+using System.Threading.Tasks;
+
+namespace MessagingService.Common
+{
+ [ExcludeFromCodeCoverage]
+ public class ClaimsHelper
+ {
+ #region Methods
+
+ ///
+ /// Gets the user claims.
+ ///
+ /// The user.
+ /// Type of the custom claim.
+ /// The default value.
+ ///
+ /// No claim [{customClaimType}] found for user id [{userIdClaim.Value}
+ public static Claim GetUserClaim(ClaimsPrincipal user,
+ String customClaimType,
+ String defaultValue = "")
+ {
+ Claim userClaim = null;
+
+ if (ClaimsHelper.IsPasswordToken(user))
+ {
+ // Get the claim from the token
+ userClaim = user.Claims.SingleOrDefault(c => c.Type == customClaimType);
+
+ if (userClaim == null)
+ {
+ throw new NotFoundException($"Claim type [{customClaimType}] not found");
+ }
+ }
+ else
+ {
+ userClaim = new Claim(customClaimType, defaultValue);
+ }
+
+ return userClaim;
+ }
+
+ ///
+ /// Determines whether [is client token] [the specified user].
+ ///
+ /// The user.
+ ///
+ /// true if [is client token] [the specified user]; otherwise, false.
+ ///
+ public static Boolean IsPasswordToken(ClaimsPrincipal user)
+ {
+ Boolean result = false;
+
+ Claim userIdClaim = user.Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
+
+ if (userIdClaim != null)
+ {
+ result = true;
+ }
+
+ return result;
+ }
+
+ ///
+ /// Validates the route parameter.
+ ///
+ ///
+ /// The route parameter.
+ /// The user claim.
+ public static Boolean ValidateRouteParameter(T routeParameter,
+ Claim userClaim)
+ {
+ if (routeParameter.ToString() != userClaim.Value)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Determines whether [is user roles valid] [the specified user].
+ ///
+ /// The user.
+ /// The allowed roles.
+ ///
+ /// true if [is user roles valid] [the specified user]; otherwise, false.
+ ///
+ public static Boolean IsUserRolesValid(ClaimsPrincipal user, String[] allowedRoles)
+ {
+ if (IsPasswordToken(user) == false)
+ {
+ return true;
+ }
+
+ foreach (String allowedRole in allowedRoles)
+ {
+ if (user.IsInRole(allowedRole) == false)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ #endregion
+ }
+}
diff --git a/MessagingService/Common/ConfigureSwaggerOptions.cs b/MessagingService/Common/ConfigureSwaggerOptions.cs
new file mode 100644
index 0000000..a4c4479
--- /dev/null
+++ b/MessagingService/Common/ConfigureSwaggerOptions.cs
@@ -0,0 +1,131 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace MessagingService.Common
+{
+ using System.Diagnostics.CodeAnalysis;
+ using Microsoft.AspNetCore.Mvc;
+ using Microsoft.AspNetCore.Mvc.Abstractions;
+ using Microsoft.AspNetCore.Mvc.ApiExplorer;
+ using Microsoft.AspNetCore.Mvc.Versioning;
+ using Microsoft.Extensions.DependencyInjection;
+ using Microsoft.Extensions.Options;
+ using Microsoft.OpenApi.Models;
+ using Swashbuckle.AspNetCore.SwaggerGen;
+
+ ///
+ /// Configures the Swagger generation options.
+ ///
+ /// This allows API versioning to define a Swagger document per API version after the
+ /// service has been resolved from the service container.
+ [ExcludeFromCodeCoverage]
+ public class ConfigureSwaggerOptions : IConfigureOptions
+ {
+ #region Fields
+
+ ///
+ /// The provider
+ ///
+ private readonly IApiVersionDescriptionProvider provider;
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The provider used to generate Swagger documents.
+ public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) => this.provider = provider;
+
+ #endregion
+
+ #region Methods
+
+ ///
+ public void Configure(SwaggerGenOptions options)
+ {
+ // add a swagger document for each discovered API version
+ // note: you might choose to skip or document deprecated API versions differently
+ foreach (ApiVersionDescription description in this.provider.ApiVersionDescriptions)
+ {
+ options.SwaggerDoc(description.GroupName, ConfigureSwaggerOptions.CreateInfoForApiVersion(description));
+ }
+ }
+
+ ///
+ /// Creates the information for API version.
+ ///
+ /// The description.
+ ///
+ private static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
+ {
+ OpenApiInfo info = new OpenApiInfo
+ {
+ Title = "Golf Handicapping API",
+ Version = description.ApiVersion.ToString(),
+ Description = "A REST Api to manage the golf club handicapping system.",
+ Contact = new OpenApiContact
+ {
+ Name = "Stuart Ferguson",
+ Email = "golfhandicapping@btinternet.com"
+ },
+ License = new OpenApiLicense
+ {
+ Name = "TODO"
+ }
+ };
+
+ if (description.IsDeprecated)
+ {
+ info.Description += " This API version has been deprecated.";
+ }
+
+ return info;
+ }
+
+ #endregion
+ }
+
+ ///
+ /// Represents the Swagger/Swashbuckle operation filter used to document the implicit API version parameter.
+ ///
+ /// This is only required due to bugs in the .
+ /// Once they are fixed and published, this class can be removed.
+ [ExcludeFromCodeCoverage]
+ public class SwaggerDefaultValues : IOperationFilter
+ {
+ ///
+ /// Applies the filter to the specified operation using the given context.
+ ///
+ /// The operation to apply the filter to.
+ /// The current operation filter context.
+ public void Apply(OpenApiOperation operation,
+ OperationFilterContext context)
+ {
+ ApiDescription apiDescription = context.ApiDescription;
+ ApiVersion apiVersion = apiDescription.GetApiVersion();
+ ApiVersionModel model = apiDescription.ActionDescriptor.GetApiVersionModel(ApiVersionMapping.Explicit | ApiVersionMapping.Implicit);
+
+ operation.Deprecated = model.DeprecatedApiVersions.Contains(apiVersion);
+
+ if (operation.Parameters == null)
+ {
+ return;
+ }
+
+ foreach (OpenApiParameter parameter in operation.Parameters)
+ {
+ ApiParameterDescription description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name);
+
+ if (parameter.Description == null)
+ {
+ parameter.Description = description.ModelMetadata?.Description;
+ }
+
+ parameter.Required |= description.IsRequired;
+ }
+ }
+ }
+}
diff --git a/MessagingService/Common/SwaggerJsonConverter.cs b/MessagingService/Common/SwaggerJsonConverter.cs
new file mode 100644
index 0000000..03c4fd8
--- /dev/null
+++ b/MessagingService/Common/SwaggerJsonConverter.cs
@@ -0,0 +1,80 @@
+namespace MessagingService.Common
+{
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+ using Newtonsoft.Json;
+ using Newtonsoft.Json.Linq;
+
+ ///
+ ///
+ ///
+ ///
+ [ExcludeFromCodeCoverage]
+ public class SwaggerJsonConverter : JsonConverter
+ {
+ #region Properties
+
+ ///
+ /// Gets a value indicating whether this can read JSON.
+ ///
+ ///
+ /// true if this can read JSON; otherwise, false.
+ ///
+ public override Boolean CanRead => false;
+
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Determines whether this instance can convert the specified object type.
+ ///
+ /// Type of the object.
+ ///
+ /// true if this instance can convert the specified object type; otherwise, false.
+ ///
+ public override Boolean CanConvert(Type objectType)
+ {
+ return true;
+ }
+
+ ///
+ /// Reads the JSON representation of the object.
+ ///
+ /// The to read from.
+ /// Type of the object.
+ /// The existing value of object being read.
+ /// The calling serializer.
+ ///
+ /// The object value.
+ ///
+ ///
+ ///
+ public override Object ReadJson(JsonReader reader,
+ Type objectType,
+ Object existingValue,
+ JsonSerializer serializer)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ /// Writes the JSON representation of the object.
+ ///
+ /// The to write to.
+ /// The value.
+ /// The calling serializer.
+ public override void WriteJson(JsonWriter writer,
+ Object value,
+ JsonSerializer serializer)
+ {
+ // Disable sending the $type in the serialized json
+ serializer.TypeNameHandling = TypeNameHandling.None;
+
+ JToken t = JToken.FromObject(value);
+ t.WriteTo(writer);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/MessagingService/Controllers/EmailController.cs b/MessagingService/Controllers/EmailController.cs
new file mode 100644
index 0000000..c5fc1d5
--- /dev/null
+++ b/MessagingService/Controllers/EmailController.cs
@@ -0,0 +1,106 @@
+using SendEmailRequestDTO = MessagingService.DataTransferObjects.SendEmailRequest;
+using SendEmailResponseDTO = MessagingService.DataTransferObjects.SendEmailResponse;
+
+namespace MessagingService.Controllers
+{
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using BusinessLogic.Requests;
+ using Common;
+ using MediatR;
+ using Microsoft.AspNetCore.Authorization;
+ using Microsoft.AspNetCore.Mvc;
+
+ ///
+ ///
+ ///
+ ///
+ [ExcludeFromCodeCoverage]
+ [Route(EmailController.ControllerRoute)]
+ [ApiController]
+ [ApiVersion("1.0")]
+ [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
+
+ ///
+ /// Posts the email.
+ ///
+ /// The send email request.
+ /// The cancellation token.
+ ///
+ [HttpPost]
+ [Route("")]
+ public async Task PostEmail([FromBody] SendEmailRequestDTO sendEmailRequest,
+ CancellationToken cancellationToken)
+ {
+ // Reject password tokens
+ if (ClaimsHelper.IsPasswordToken(this.User))
+ {
+ return this.Forbid();
+ }
+
+ Guid messageId = Guid.NewGuid();
+
+ // Create the command
+ SendEmailRequest request = SendEmailRequest.Create(sendEmailRequest.ConnectionIdentifier,
+ messageId,
+ sendEmailRequest.FromAddress,
+ sendEmailRequest.ToAddresses,
+ sendEmailRequest.Subject,
+ sendEmailRequest.Body,
+ sendEmailRequest.IsHtml);
+
+ // Route the command
+ await this.Mediator.Send(request, cancellationToken);
+
+ // return the result
+ return this.Created($"{EmailController.ControllerRoute}/{messageId}",
+ new SendEmailResponseDTO
+ {
+ MessageId = messageId
+ });
+ }
+
+ #endregion
+
+ #region Others
+
+ ///
+ /// The controller name
+ ///
+ public 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/Dockerfile b/MessagingService/Dockerfile
new file mode 100644
index 0000000..fe4b032
--- /dev/null
+++ b/MessagingService/Dockerfile
@@ -0,0 +1,21 @@
+#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
+
+FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
+WORKDIR /app
+EXPOSE 80
+
+FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
+WORKDIR /src
+COPY ["MessagingService/MessagingService.csproj", "MessagingService/"]
+RUN dotnet restore "MessagingService/MessagingService.csproj"
+COPY . .
+WORKDIR "/src/MessagingService"
+RUN dotnet build "MessagingService.csproj" -c Release -o /app/build
+
+FROM build AS publish
+RUN dotnet publish "MessagingService.csproj" -c Release -o /app/publish
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "MessagingService.dll"]
\ No newline at end of file
diff --git a/MessagingService/MessagingService.csproj b/MessagingService/MessagingService.csproj
new file mode 100644
index 0000000..9fb5660
--- /dev/null
+++ b/MessagingService/MessagingService.csproj
@@ -0,0 +1,31 @@
+
+
+
+ netcoreapp3.1
+ Linux
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MessagingService/Program.cs b/MessagingService/Program.cs
new file mode 100644
index 0000000..8b9c011
--- /dev/null
+++ b/MessagingService/Program.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace MessagingService
+{
+ using System.Diagnostics.CodeAnalysis;
+
+ [ExcludeFromCodeCoverage]
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ Program.CreateHostBuilder(args).Build().Run();
+ }
+
+ public static IHostBuilder CreateHostBuilder(string[] args)
+ {
+ Console.Title = "Messaging Service";
+
+ //At this stage, we only need our hosting file for ip and ports
+ IConfigurationRoot config = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile("hosting.json", optional: true)
+ .AddJsonFile("hosting.development.json", optional: true)
+ .AddEnvironmentVariables().Build();
+
+ IHostBuilder hostBuilder = Host.CreateDefaultBuilder(args);
+ hostBuilder.ConfigureLogging(logging =>
+ {
+ logging.AddConsole();
+
+ });
+ hostBuilder.ConfigureWebHostDefaults(webBuilder =>
+ {
+ webBuilder.UseStartup();
+ webBuilder.UseConfiguration(config);
+ webBuilder.UseKestrel();
+ });
+ return hostBuilder;
+ }
+
+ }
+}
diff --git a/MessagingService/Properties/launchSettings.json b/MessagingService/Properties/launchSettings.json
new file mode 100644
index 0000000..4eedb6b
--- /dev/null
+++ b/MessagingService/Properties/launchSettings.json
@@ -0,0 +1,36 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:50775",
+ "sslPort": 0
+ }
+ },
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "weatherforecast",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "MessagingService": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "weatherforecast",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "http://localhost:5000"
+ },
+ "Docker": {
+ "commandName": "Docker",
+ "launchBrowser": true,
+ "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/weatherforecast",
+ "publishAllPorts": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/MessagingService/Startup.cs b/MessagingService/Startup.cs
new file mode 100644
index 0000000..013af96
--- /dev/null
+++ b/MessagingService/Startup.cs
@@ -0,0 +1,267 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace MessagingService
+{
+ using System.IO;
+ using System.Net.Http;
+ using System.Reflection;
+ using BusinessLogic.Common;
+ using BusinessLogic.RequestHandlers;
+ using BusinessLogic.Requests;
+ using BusinessLogic.Services;
+ using BusinessLogic.Services.EmailServices;
+ using BusinessLogic.Services.EmailServices.Smtp2Go;
+ using Common;
+ using EmailMessageAggregate;
+ using EventStore.ClientAPI;
+ using MediatR;
+ using Microsoft.AspNetCore.Authentication.JwtBearer;
+ using Microsoft.AspNetCore.Mvc.ApiExplorer;
+ using Microsoft.AspNetCore.Mvc.Versioning;
+ using Microsoft.Extensions.Options;
+ using Newtonsoft.Json;
+ using Newtonsoft.Json.Serialization;
+ using NLog.Extensions.Logging;
+ using Shared.DomainDrivenDesign.EventStore;
+ using Shared.EntityFramework.ConnectionStringConfiguration;
+ using Shared.EventStore.EventStore;
+ using Shared.Extensions;
+ using Shared.General;
+ using Shared.Logger;
+ using Shared.Repositories;
+ using Swashbuckle.AspNetCore.Filters;
+ using Swashbuckle.AspNetCore.SwaggerGen;
+ using ILogger = EventStore.ClientAPI.ILogger;
+
+ public class Startup
+ {
+ public Startup(IWebHostEnvironment webHostEnvironment)
+ {
+ IConfigurationBuilder builder = new ConfigurationBuilder().SetBasePath(webHostEnvironment.ContentRootPath)
+ .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
+ .AddJsonFile($"appsettings.{webHostEnvironment.EnvironmentName}.json", optional: true).AddEnvironmentVariables();
+
+ Startup.Configuration = builder.Build();
+ Startup.WebHostEnvironment = webHostEnvironment;
+ }
+
+ public static IConfigurationRoot Configuration { get; set; }
+
+ public static IWebHostEnvironment WebHostEnvironment { get; set; }
+
+ // This method gets called by the runtime. Use this method to add services to the container.
+ public void ConfigureServices(IServiceCollection services)
+ {
+ this.ConfigureMiddlewareServices(services);
+
+ services.AddTransient();
+
+ ConfigurationReader.Initialise(Startup.Configuration);
+ String connString = Startup.Configuration.GetValue("EventStoreSettings:ConnectionString");
+ String connectionName = Startup.Configuration.GetValue("EventStoreSettings:ConnectionName");
+ Int32 httpPort = Startup.Configuration.GetValue("EventStoreSettings:HttpPort");
+
+ Boolean useConnectionStringConfig = Boolean.Parse(ConfigurationReader.GetValue("AppSettings", "UseConnectionStringConfig"));
+ EventStoreConnectionSettings settings = EventStoreConnectionSettings.Create(connString, connectionName, httpPort);
+ services.AddSingleton(settings);
+
+ services.AddSingleton>(cont => (connectionSettings) =>
+ {
+ return EventStoreConnection.Create(connectionSettings
+ .ConnectionString);
+ });
+
+ services.AddSingleton>(cont => (connectionString) =>
+ {
+ EventStoreConnectionSettings connectionSettings =
+ EventStoreConnectionSettings.Create(connectionString, connectionName, httpPort);
+
+ Func eventStoreConnectionFunc = cont.GetService>();
+
+ IEventStoreContext context =
+ new EventStoreContext(connectionSettings, eventStoreConnectionFunc);
+
+ return context;
+ });
+
+
+ if (useConnectionStringConfig)
+ {
+ String connectionStringConfigurationConnString = ConfigurationReader.GetConnectionString("ConnectionStringConfiguration");
+ services.AddSingleton();
+ services.AddTransient(c =>
+ {
+ return new ConnectionStringConfigurationContext(connectionStringConfigurationConnString);
+ });
+
+ services.AddSingleton(c =>
+ {
+ Func contextFunc = c.GetService>();
+ IConnectionStringConfigurationRepository connectionStringConfigurationRepository =
+ c.GetService();
+ return new EventStoreContextManager(contextFunc,
+ connectionStringConfigurationRepository);
+ });
+ }
+ else
+ {
+ services.AddSingleton(c =>
+ {
+ IEventStoreContext context = c.GetService();
+ return new EventStoreContextManager(context);
+ });
+ services.AddSingleton();
+ }
+
+ services.AddTransient();
+ services.AddSingleton();
+ services.AddSingleton, AggregateRepository>();
+ services.AddSingleton();
+ services.AddSingleton();
+ //services.AddSingleton();
+ //services.AddSingleton();
+ //services.AddSingleton();
+
+ //// request & notification handlers
+ services.AddTransient(context =>
+ {
+ return t => context.GetService(t);
+ });
+
+ services.AddSingleton, EmailRequestHandler>();
+
+ services.AddSingleton>(container => (serviceName) =>
+ {
+ return ConfigurationReader.GetBaseServerUri(serviceName).OriginalString;
+ });
+ services.AddSingleton();
+ }
+
+ private void ConfigureMiddlewareServices(IServiceCollection services)
+ {
+ services.AddApiVersioning(
+ options =>
+ {
+ // reporting api versions will return the headers "api-supported-versions" and "api-deprecated-versions"
+ options.ReportApiVersions = true;
+ options.DefaultApiVersion = new ApiVersion(1, 0);
+ options.AssumeDefaultVersionWhenUnspecified = true;
+ options.ApiVersionReader = new HeaderApiVersionReader("api-version");
+ });
+
+ services.AddVersionedApiExplorer(
+ options =>
+ {
+ // add the versioned api explorer, which also adds IApiVersionDescriptionProvider service
+ // note: the specified format code will format the version as "'v'major[.minor][-status]"
+ options.GroupNameFormat = "'v'VVV";
+
+ // note: this option is only necessary when versioning by url segment. the SubstitutionFormat
+ // can also be used to control the format of the API version in route templates
+ options.SubstituteApiVersionInUrl = true;
+ });
+
+ services.AddTransient, ConfigureSwaggerOptions>();
+
+ services.AddSwaggerGen(c =>
+ {
+ // add a custom operation filter which sets default values
+ c.OperationFilter();
+ c.ExampleFilters();
+ });
+
+ services.AddSwaggerExamplesFromAssemblyOf();
+
+ services.AddAuthentication(options =>
+ {
+ options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+ options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
+ options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
+ })
+ .AddJwtBearer(options =>
+ {
+ //options.SaveToken = true;
+ options.Authority = ConfigurationReader.GetValue("SecurityConfiguration", "Authority");
+ options.Audience = ConfigurationReader.GetValue("SecurityConfiguration", "ApiName");
+ options.RequireHttpsMetadata = false;
+ options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
+ {
+ ValidateIssuer = true,
+ ValidateAudience = true,
+ ValidAudience = ConfigurationReader.GetValue("SecurityConfiguration", "ApiName"),
+ ValidIssuer = ConfigurationReader.GetValue("SecurityConfiguration", "Authority"),
+ };
+ options.IncludeErrorDetails = true;
+ });
+
+ services.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;
+ services.AddMvcCore().AddApplicationPart(assembly).AddControllersAsServices();
+ }
+
+ // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory,
+ IApiVersionDescriptionProvider provider)
+ {
+ String nlogConfigFilename = "nlog.config";
+
+ if (env.IsDevelopment())
+ {
+ nlogConfigFilename = $"nlog.{env.EnvironmentName}.config";
+ app.UseDeveloperExceptionPage();
+ }
+
+ loggerFactory.ConfigureNLog(Path.Combine(env.ContentRootPath, nlogConfigFilename));
+ loggerFactory.AddNLog();
+
+ Microsoft.Extensions.Logging.ILogger logger = loggerFactory.CreateLogger("EstateManagement");
+
+ Logger.Initialise(logger);
+
+ ConfigurationReader.Initialise(Startup.Configuration);
+
+ app.AddRequestLogging();
+ app.AddResponseLogging();
+ app.AddExceptionHandler();
+
+ app.UseRouting();
+
+ app.UseAuthentication();
+ app.UseAuthorization();
+
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapControllers();
+ });
+ app.UseSwagger();
+
+ app.UseSwaggerUI(
+ options =>
+ {
+ // build a swagger endpoint for each discovered API version
+ foreach (ApiVersionDescription description in provider.ApiVersionDescriptions)
+ {
+ options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
+ }
+ });
+ }
+ }
+}
diff --git a/MessagingService/appsettings.Development.json b/MessagingService/appsettings.Development.json
new file mode 100644
index 0000000..7ea7725
--- /dev/null
+++ b/MessagingService/appsettings.Development.json
@@ -0,0 +1,19 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "EventStoreSettings": {
+ "ConnectionString": "ConnectTo=tcp://admin:changeit@127.0.0.1:1113;VerboseLogging=true;",
+ "ConnectionName": "Messaging Service",
+ "HttpPort": 2113,
+ "START_PROJECTIONS": false,
+ "ContinuousProjectionsFolder": ""
+ },
+ "ConnectionStrings": {
+ "ConnectionStringConfiguration": "server=127.0.0.1;database=ConnectionStringConfiguration;user id=sa;password=Sc0tland",
+ }
+}
diff --git a/MessagingService/appsettings.json b/MessagingService/appsettings.json
new file mode 100644
index 0000000..0935e8a
--- /dev/null
+++ b/MessagingService/appsettings.json
@@ -0,0 +1,31 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "EventStoreSettings": {
+ "ConnectionString": "ConnectTo=tcp://admin:changeit@192.168.1.133:1113;VerboseLogging=true;",
+ "ConnectionName": "Estate Management",
+ "HttpPort": 2113,
+ "START_PROJECTIONS": false,
+ "ContinuousProjectionsFolder": ""
+ },
+ "ConnectionStrings": {
+ "ConnectionStringConfiguration": "server=192.168.1.133;database=ConnectionStringConfiguration;user id=sa;password=Sc0tland",
+ "EstateReportingReadModel": "server=localhost;user id=sa;password=sp1ttal;database=EstateReportingReadModel"
+ },
+ "AppSettings": {
+ "UseConnectionStringConfig": false,
+ "SecurityService": "http://192.168.1.133:5001",
+ "SMTP2GoBaseAddress": "https://api.smtp2go.com/v3/",
+ "SMTP2GoAPIKey": "api-4CE2C6BC80D111EAB45BF23C91C88F4E"
+ },
+ "SecurityConfiguration": {
+ "ApiName": "messagingService",
+ "Authority": "http://192.168.1.133:5001"
+ },
+ "AllowedHosts": "*"
+}
diff --git a/MessagingService/hosting.json b/MessagingService/hosting.json
new file mode 100644
index 0000000..759a312
--- /dev/null
+++ b/MessagingService/hosting.json
@@ -0,0 +1,3 @@
+{
+ "urls": "http://*:5006"
+}
\ No newline at end of file
diff --git a/MessagingService/nlog.config b/MessagingService/nlog.config
new file mode 100644
index 0000000..da18452
--- /dev/null
+++ b/MessagingService/nlog.config
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/MessagingService/nlog.development.config b/MessagingService/nlog.development.config
new file mode 100644
index 0000000..99744c8
--- /dev/null
+++ b/MessagingService/nlog.development.config
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/NessagingService.EmailMessage.DomainEvents/MessagingService.EmailMessage.DomainEvents.csproj b/NessagingService.EmailMessage.DomainEvents/MessagingService.EmailMessage.DomainEvents.csproj
new file mode 100644
index 0000000..44c1c40
--- /dev/null
+++ b/NessagingService.EmailMessage.DomainEvents/MessagingService.EmailMessage.DomainEvents.csproj
@@ -0,0 +1,11 @@
+
+
+
+ netstandard2.1
+
+
+
+
+
+
+
diff --git a/NessagingService.EmailMessage.DomainEvents/RequestSentToProviderEvent.cs b/NessagingService.EmailMessage.DomainEvents/RequestSentToProviderEvent.cs
new file mode 100644
index 0000000..8b40d5d
--- /dev/null
+++ b/NessagingService.EmailMessage.DomainEvents/RequestSentToProviderEvent.cs
@@ -0,0 +1,126 @@
+namespace MessagingService.EmailMessage.DomainEvents
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using Newtonsoft.Json;
+ using Shared.DomainDrivenDesign.EventSourcing;
+
+ ///
+ ///
+ ///
+ ///
+ public class RequestSentToProviderEvent : DomainEvent
+ {
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ [ExcludeFromCodeCoverage]
+ public RequestSentToProviderEvent()
+ {
+ //We need this for serialisation, so just embrace the DDD crime
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The aggregate identifier.
+ /// The event identifier.
+ /// From address.
+ /// To addresses.
+ /// The subject.
+ /// The body.
+ /// if set to true [is HTML].
+ private RequestSentToProviderEvent(Guid aggregateId,
+ Guid eventId,
+ String fromAddress,
+ List toAddresses,
+ String subject,
+ String body,
+ Boolean isHtml) : base(aggregateId, eventId)
+ {
+ this.FromAddress = fromAddress;
+ this.ToAddresses = toAddresses;
+ this.Subject = subject;
+ this.Body = body;
+ this.IsHtml = isHtml;
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets the body.
+ ///
+ ///
+ /// The body.
+ ///
+ [JsonProperty]
+ public String Body { get; private set; }
+
+ ///
+ /// Gets from address.
+ ///
+ ///
+ /// From address.
+ ///
+ [JsonProperty]
+ public String FromAddress { get; private set; }
+
+ ///
+ /// Gets a value indicating whether this instance is HTML.
+ ///
+ ///
+ /// true if this instance is HTML; otherwise, false.
+ ///
+ [JsonProperty]
+ public Boolean IsHtml { get; private set; }
+
+ ///
+ /// Gets the subject.
+ ///
+ ///
+ /// The subject.
+ ///
+ [JsonProperty]
+ public String Subject { get; private set; }
+
+ ///
+ /// Converts to addresses.
+ ///
+ ///
+ /// To addresses.
+ ///
+ [JsonProperty]
+ public List ToAddresses { get; private set; }
+
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Creates the specified aggregate identifier.
+ ///
+ /// The aggregate identifier.
+ /// From address.
+ /// To addresses.
+ /// The subject.
+ /// The body.
+ /// if set to true [is HTML].
+ ///
+ public static RequestSentToProviderEvent Create(Guid aggregateId,
+ String fromAddress,
+ List toAddresses,
+ String subject,
+ String body,
+ Boolean isHtml)
+ {
+ return new RequestSentToProviderEvent(aggregateId, Guid.NewGuid(), fromAddress, toAddresses, subject, body, isHtml);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/NessagingService.EmailMessage.DomainEvents/ResponseReceivedFromProvider.cs b/NessagingService.EmailMessage.DomainEvents/ResponseReceivedFromProvider.cs
new file mode 100644
index 0000000..1e96555
--- /dev/null
+++ b/NessagingService.EmailMessage.DomainEvents/ResponseReceivedFromProvider.cs
@@ -0,0 +1,83 @@
+namespace MessagingService.EmailMessage.DomainEvents
+{
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+ using Newtonsoft.Json;
+ using Shared.DomainDrivenDesign.EventSourcing;
+
+ ///
+ ///
+ ///
+ ///
+ public class ResponseReceivedFromProviderEvent : DomainEvent
+ {
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ [ExcludeFromCodeCoverage]
+ public ResponseReceivedFromProviderEvent()
+ {
+ //We need this for serialisation, so just embrace the DDD crime
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The aggregate identifier.
+ /// The event identifier.
+ /// The provider request reference.
+ /// The provider email reference.
+ private ResponseReceivedFromProviderEvent(Guid aggregateId,
+ Guid eventId,
+ String providerRequestReference,
+ String providerEmailReference) : base(aggregateId, eventId)
+ {
+ this.ProviderRequestReference = providerRequestReference;
+ this.ProviderEmailReference = providerEmailReference;
+ }
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets the provider email reference.
+ ///
+ ///
+ /// The provider email reference.
+ ///
+ [JsonProperty]
+ public String ProviderEmailReference { get; private set; }
+
+ ///
+ /// Gets the provider request reference.
+ ///
+ ///
+ /// The provider request reference.
+ ///
+ [JsonProperty]
+ public String ProviderRequestReference { get; private set; }
+
+ #endregion
+
+ #region Methods
+
+ ///
+ /// Creates the specified aggregate identifier.
+ ///
+ /// The aggregate identifier.
+ /// The provider request reference.
+ /// The provider email reference.
+ ///
+ public static ResponseReceivedFromProviderEvent Create(Guid aggregateId,
+ String providerRequestReference,
+ String providerEmailReference)
+ {
+ return new ResponseReceivedFromProviderEvent(aggregateId, Guid.NewGuid(), providerRequestReference, providerEmailReference);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file