diff --git a/src/Application/Common/Services/EmailSenderService.cs b/src/Application/Common/Services/EmailSenderService.cs
new file mode 100644
index 0000000..706d96d
--- /dev/null
+++ b/src/Application/Common/Services/EmailSenderService.cs
@@ -0,0 +1,7 @@
+namespace cuqmbr.TravelGuide.Application.Common.Services;
+
+public interface EmailSenderService
+{
+ Task SendAsync(string[] addresses, string subject, string body,
+ CancellationToken cancellationToken);
+}
diff --git a/src/Configuration/packages.lock.json b/src/Configuration/packages.lock.json
index bba215c..aed0299 100644
--- a/src/Configuration/packages.lock.json
+++ b/src/Configuration/packages.lock.json
@@ -164,11 +164,25 @@
"Microsoft.Extensions.Options": "8.0.0"
}
},
+ "BouncyCastle.Cryptography": {
+ "type": "Transitive",
+ "resolved": "2.5.1",
+ "contentHash": "zy8TMeTP+1FH2NrLaNZtdRbBdq7u5MI+NFZQOBSM69u5RFkciinwzV2eveY6Kjf5MzgsYvvl6kTStsj3JrXqkg=="
+ },
"FluentValidation": {
"type": "Transitive",
"resolved": "11.11.0",
"contentHash": "cyIVdQBwSipxWG8MA3Rqox7iNbUNUTK5bfJi9tIdm4CAfH71Oo5ABLP4/QyrUwuakqpUEPGtE43BDddvEehuYw=="
},
+ "MailKit": {
+ "type": "Transitive",
+ "resolved": "4.12.1",
+ "contentHash": "rIqJm92qtHvk1hDchsJ95Hy7n46A7imE24ol++ikXBsjf3Bi1qDBu4H91FfY6LrYXJaxRlc2gIIpC8AOJrCbqg==",
+ "dependencies": {
+ "MimeKit": "4.12.0",
+ "System.Formats.Asn1": "8.0.1"
+ }
+ },
"MediatR": {
"type": "Transitive",
"resolved": "12.4.1",
@@ -701,6 +715,15 @@
"System.Security.Principal.Windows": "4.5.0"
}
},
+ "MimeKit": {
+ "type": "Transitive",
+ "resolved": "4.12.0",
+ "contentHash": "PFUHfs6BZxKYM/QPJksAwXphbJf0SEfdSfsoQ6p6yvFRaJPofFJMBiotWhFRrdSUzfp6C6K49EjBIqIwZ2TJqA==",
+ "dependencies": {
+ "BouncyCastle.Cryptography": "2.5.1",
+ "System.Security.Cryptography.Pkcs": "8.0.1"
+ }
+ },
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.3",
@@ -769,6 +792,11 @@
"resolved": "5.0.0",
"contentHash": "dMkqfy2el8A8/I76n2Hi1oBFEbG1SfxD2l5nhwXV3XjlnOmwxJlQbYpJH4W51odnU9sARCSAgv7S3CyAFMkpYg=="
},
+ "System.Formats.Asn1": {
+ "type": "Transitive",
+ "resolved": "8.0.1",
+ "contentHash": "XqKba7Mm/koKSjKMfW82olQdmfbI5yqeoLV/tidRp7fbh5rmHAQ5raDI/7SU0swTzv+jgqtUGkzmFxuUg0it1A=="
+ },
"System.IdentityModel.Tokens.Jwt": {
"type": "Transitive",
"resolved": "8.0.1",
@@ -845,6 +873,7 @@
"type": "Project",
"dependencies": {
"Application": "[1.0.0, )",
+ "MailKit": "[4.12.1, )",
"Microsoft.Extensions.Http": "[9.0.4, )",
"Newtonsoft.Json": "[13.0.3, )"
}
diff --git a/src/HttpApi/Controllers/TestsController.cs b/src/HttpApi/Controllers/TestsController.cs
index 3e2abf0..73b7134 100644
--- a/src/HttpApi/Controllers/TestsController.cs
+++ b/src/HttpApi/Controllers/TestsController.cs
@@ -1,7 +1,6 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using cuqmbr.TravelGuide.Application.Common.Services;
-using cuqmbr.TravelGuide.Application.Common.Persistence;
namespace cuqmbr.TravelGuide.HttpApi.Controllers;
@@ -9,15 +8,13 @@ namespace cuqmbr.TravelGuide.HttpApi.Controllers;
public class TestsController : ControllerBase
{
private readonly IStringLocalizer _localizer;
- private readonly UnitOfWork _unitOfWork;
+ private readonly EmailSenderService _emailSender;
- public TestsController(
- SessionCultureService cultureService,
- IStringLocalizer localizer,
- UnitOfWork unitOfWork)
+ public TestsController(SessionCultureService cultureService,
+ IStringLocalizer localizer, EmailSenderService emailSender)
{
_localizer = localizer;
- _unitOfWork = unitOfWork;
+ _emailSender = emailSender;
}
[HttpGet("getLocalizedString/{inputString}")]
@@ -31,19 +28,15 @@ public class TestsController : ControllerBase
[HttpGet("trigger")]
public async Task Trigger(CancellationToken cancellationToken)
{
- // await _unitOfWork.BusRepository.AddOneAsync(
- // new Domain.Entities.Bus()
- // {
- // Number = "AB1234MK",
- // Model = "This is a fancy bus model",
- // Capacity = 40
- // },
- // cancellationToken);
- //
- // await _unitOfWork.SaveAsync(cancellationToken);
- // _unitOfWork.Dispose();
+ var body =
+@"Hello, friend!
- var vehicles = await _unitOfWork.VehicleRepository
- .GetPageAsync(1, 10, cancellationToken);
+This is my email message for you.
+
+--
+Travel Guide Service
+";
+
+ await _emailSender.SendAsync(new string[] { "cuqmbr@ya.ru" }, "Test subject", body, cancellationToken);
}
}
diff --git a/src/HttpApi/appsettings.Development.json b/src/HttpApi/appsettings.Development.json
index 05be9dc..1fd9334 100644
--- a/src/HttpApi/appsettings.Development.json
+++ b/src/HttpApi/appsettings.Development.json
@@ -27,5 +27,16 @@
"PublicKey": "sandbox_xxxxxxxxxxxx",
"PrivateKey": "sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
+ },
+ "Email": {
+ "Smtp": {
+ "Host": "mail.travel-guide.cuqmbr.xyz",
+ "Port": "465",
+ "UseTls": true,
+ "Username": "no-reply",
+ "Password": "super-secret-password",
+ "SenderAddress": "no-reply@travel-guide.cuqmbr.xyz",
+ "SenderName": "Travel Guide"
+ }
}
}
diff --git a/src/HttpApi/appsettings.json b/src/HttpApi/appsettings.json
index 05be9dc..1fd9334 100644
--- a/src/HttpApi/appsettings.json
+++ b/src/HttpApi/appsettings.json
@@ -27,5 +27,16 @@
"PublicKey": "sandbox_xxxxxxxxxxxx",
"PrivateKey": "sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
+ },
+ "Email": {
+ "Smtp": {
+ "Host": "mail.travel-guide.cuqmbr.xyz",
+ "Port": "465",
+ "UseTls": true,
+ "Username": "no-reply",
+ "Password": "super-secret-password",
+ "SenderAddress": "no-reply@travel-guide.cuqmbr.xyz",
+ "SenderName": "Travel Guide"
+ }
}
}
diff --git a/src/HttpApi/packages.lock.json b/src/HttpApi/packages.lock.json
index 02503f2..59d1bfe 100644
--- a/src/HttpApi/packages.lock.json
+++ b/src/HttpApi/packages.lock.json
@@ -106,6 +106,11 @@
"Microsoft.Extensions.Options": "8.0.0"
}
},
+ "BouncyCastle.Cryptography": {
+ "type": "Transitive",
+ "resolved": "2.5.1",
+ "contentHash": "zy8TMeTP+1FH2NrLaNZtdRbBdq7u5MI+NFZQOBSM69u5RFkciinwzV2eveY6Kjf5MzgsYvvl6kTStsj3JrXqkg=="
+ },
"FluentValidation": {
"type": "Transitive",
"resolved": "11.11.0",
@@ -125,6 +130,15 @@
"resolved": "2.14.1",
"contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw=="
},
+ "MailKit": {
+ "type": "Transitive",
+ "resolved": "4.12.1",
+ "contentHash": "rIqJm92qtHvk1hDchsJ95Hy7n46A7imE24ol++ikXBsjf3Bi1qDBu4H91FfY6LrYXJaxRlc2gIIpC8AOJrCbqg==",
+ "dependencies": {
+ "MimeKit": "4.12.0",
+ "System.Formats.Asn1": "8.0.1"
+ }
+ },
"MediatR": {
"type": "Transitive",
"resolved": "12.4.1",
@@ -848,6 +862,15 @@
"System.Security.Principal.Windows": "4.5.0"
}
},
+ "MimeKit": {
+ "type": "Transitive",
+ "resolved": "4.12.0",
+ "contentHash": "PFUHfs6BZxKYM/QPJksAwXphbJf0SEfdSfsoQ6p6yvFRaJPofFJMBiotWhFRrdSUzfp6C6K49EjBIqIwZ2TJqA==",
+ "dependencies": {
+ "BouncyCastle.Cryptography": "2.5.1",
+ "System.Security.Cryptography.Pkcs": "8.0.1"
+ }
+ },
"Mono.TextTemplating": {
"type": "Transitive",
"resolved": "3.0.0",
@@ -982,6 +1005,11 @@
"System.Composition.Runtime": "7.0.0"
}
},
+ "System.Formats.Asn1": {
+ "type": "Transitive",
+ "resolved": "8.0.1",
+ "contentHash": "XqKba7Mm/koKSjKMfW82olQdmfbI5yqeoLV/tidRp7fbh5rmHAQ5raDI/7SU0swTzv+jgqtUGkzmFxuUg0it1A=="
+ },
"System.IdentityModel.Tokens.Jwt": {
"type": "Transitive",
"resolved": "8.0.1",
@@ -1109,6 +1137,7 @@
"type": "Project",
"dependencies": {
"Application": "[1.0.0, )",
+ "MailKit": "[4.12.1, )",
"Microsoft.Extensions.Http": "[9.0.4, )",
"Newtonsoft.Json": "[13.0.3, )"
}
diff --git a/src/Infrastructure/ConfigurationOptions.cs b/src/Infrastructure/ConfigurationOptions.cs
index b0642ee..87ac871 100644
--- a/src/Infrastructure/ConfigurationOptions.cs
+++ b/src/Infrastructure/ConfigurationOptions.cs
@@ -5,6 +5,8 @@ public sealed class ConfigurationOptions
public static string SectionName { get; } = "";
public PaymentProcessingConfigurationOptions PaymentProcessing { get; set; } = new();
+
+ public EmailConfigurationOptions Email { get; set; } = new();
}
public sealed class PaymentProcessingConfigurationOptions
@@ -22,3 +24,25 @@ public sealed class LiqPayConfigurationOptions
public string PrivateKey { get; set; }
}
+
+public sealed class EmailConfigurationOptions
+{
+ public SmtpConfigurationOptions Smtp { get; set; } = new();
+}
+
+public sealed class SmtpConfigurationOptions
+{
+ public string Host { get; set; }
+
+ public ushort Port { get; set; }
+
+ public bool UseTls { get; set; }
+
+ public string Username { get; set; }
+
+ public string Password { get; set; }
+
+ public string SenderAddress { get; set; }
+
+ public string SenderName { get; set; }
+}
diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj
index 2ae6a29..cf7f00a 100644
--- a/src/Infrastructure/Infrastructure.csproj
+++ b/src/Infrastructure/Infrastructure.csproj
@@ -11,6 +11,7 @@
+
diff --git a/src/Infrastructure/Services/MailKitEmailSenderService.cs b/src/Infrastructure/Services/MailKitEmailSenderService.cs
new file mode 100644
index 0000000..36eb50d
--- /dev/null
+++ b/src/Infrastructure/Services/MailKitEmailSenderService.cs
@@ -0,0 +1,50 @@
+using cuqmbr.TravelGuide.Application.Common.Services;
+using MailKit.Net.Smtp;
+using Microsoft.Extensions.Options;
+using MimeKit;
+
+namespace cuqmbr.TravelGuide.Infrastructure.Services;
+
+public sealed class MailKitEmailSenderService : EmailSenderService
+{
+
+ private readonly SmtpConfigurationOptions _configuration;
+
+ public MailKitEmailSenderService(
+ IOptions configuration)
+ {
+ _configuration = configuration.Value.Email.Smtp;
+ }
+
+ public async Task SendAsync(string[] addresses, string subject,
+ string body, CancellationToken cancellationToken)
+ {
+ var message = new MimeMessage();
+
+ message.From.Add(new MailboxAddress(
+ _configuration.SenderName, _configuration.SenderAddress));
+ foreach (var address in addresses)
+ {
+ message.To.Add(new MailboxAddress("", address));
+ }
+ message.Subject = subject;
+
+ message.Body = new TextPart("plain")
+ {
+ Text = body
+ };
+
+
+ using var client = new SmtpClient();
+
+ await client.ConnectAsync(_configuration.Host,
+ _configuration.Port, _configuration.UseTls,
+ cancellationToken);
+
+ await client.AuthenticateAsync(_configuration.Username,
+ _configuration.Password, cancellationToken);
+
+ await client.SendAsync(message, cancellationToken);
+ await client.DisconnectAsync(true, cancellationToken);
+ }
+}
diff --git a/src/Infrastructure/packages.lock.json b/src/Infrastructure/packages.lock.json
index 47ecef3..ccfa74f 100644
--- a/src/Infrastructure/packages.lock.json
+++ b/src/Infrastructure/packages.lock.json
@@ -2,6 +2,16 @@
"version": 1,
"dependencies": {
"net9.0": {
+ "MailKit": {
+ "type": "Direct",
+ "requested": "[4.12.1, )",
+ "resolved": "4.12.1",
+ "contentHash": "rIqJm92qtHvk1hDchsJ95Hy7n46A7imE24ol++ikXBsjf3Bi1qDBu4H91FfY6LrYXJaxRlc2gIIpC8AOJrCbqg==",
+ "dependencies": {
+ "MimeKit": "4.12.0",
+ "System.Formats.Asn1": "8.0.1"
+ }
+ },
"Microsoft.Extensions.Http": {
"type": "Direct",
"requested": "[9.0.4, )",
@@ -40,6 +50,11 @@
"Microsoft.Extensions.Options": "8.0.0"
}
},
+ "BouncyCastle.Cryptography": {
+ "type": "Transitive",
+ "resolved": "2.5.1",
+ "contentHash": "zy8TMeTP+1FH2NrLaNZtdRbBdq7u5MI+NFZQOBSM69u5RFkciinwzV2eveY6Kjf5MzgsYvvl6kTStsj3JrXqkg=="
+ },
"FluentValidation": {
"type": "Transitive",
"resolved": "11.11.0",
@@ -289,11 +304,25 @@
"Microsoft.IdentityModel.Logging": "8.11.0"
}
},
+ "MimeKit": {
+ "type": "Transitive",
+ "resolved": "4.12.0",
+ "contentHash": "PFUHfs6BZxKYM/QPJksAwXphbJf0SEfdSfsoQ6p6yvFRaJPofFJMBiotWhFRrdSUzfp6C6K49EjBIqIwZ2TJqA==",
+ "dependencies": {
+ "BouncyCastle.Cryptography": "2.5.1",
+ "System.Security.Cryptography.Pkcs": "8.0.1"
+ }
+ },
"QuikGraph": {
"type": "Transitive",
"resolved": "2.5.0",
"contentHash": "sG+mrPpXwxlXknRK5VqWUGiOmDACa9X+3ftlkQIMgOZUqxVOQSe0+HIU9PTjwqazy0pqSf8MPDXYFGl0GYWcKw=="
},
+ "System.Formats.Asn1": {
+ "type": "Transitive",
+ "resolved": "8.0.1",
+ "contentHash": "XqKba7Mm/koKSjKMfW82olQdmfbI5yqeoLV/tidRp7fbh5rmHAQ5raDI/7SU0swTzv+jgqtUGkzmFxuUg0it1A=="
+ },
"System.IdentityModel.Tokens.Jwt": {
"type": "Transitive",
"resolved": "8.0.1",
@@ -308,6 +337,11 @@
"resolved": "1.6.2",
"contentHash": "piIcdelf4dGotuIjFlyu7JLIZkYTmYM0ZTLGpCcxs9iCJFflhJht0nchkIV+GS5wfA3OtC3QNjIcUqyOdBdOsA=="
},
+ "System.Security.Cryptography.Pkcs": {
+ "type": "Transitive",
+ "resolved": "8.0.1",
+ "contentHash": "CoCRHFym33aUSf/NtWSVSZa99dkd0Hm7OCZUxORBjRB16LNhIEOf8THPqzIYlvKM0nNDAPTRBa1FxEECrgaxxA=="
+ },
"application": {
"type": "Project",
"dependencies": {
diff --git a/tst/Application.IntegrationTests/packages.lock.json b/tst/Application.IntegrationTests/packages.lock.json
index 1a02d20..34e1b41 100644
--- a/tst/Application.IntegrationTests/packages.lock.json
+++ b/tst/Application.IntegrationTests/packages.lock.json
@@ -65,6 +65,11 @@
"Microsoft.Extensions.Options": "8.0.0"
}
},
+ "BouncyCastle.Cryptography": {
+ "type": "Transitive",
+ "resolved": "2.5.1",
+ "contentHash": "zy8TMeTP+1FH2NrLaNZtdRbBdq7u5MI+NFZQOBSM69u5RFkciinwzV2eveY6Kjf5MzgsYvvl6kTStsj3JrXqkg=="
+ },
"Castle.Core": {
"type": "Transitive",
"resolved": "5.1.1",
@@ -87,6 +92,15 @@
"Microsoft.Extensions.Dependencyinjection.Abstractions": "2.1.0"
}
},
+ "MailKit": {
+ "type": "Transitive",
+ "resolved": "4.12.1",
+ "contentHash": "rIqJm92qtHvk1hDchsJ95Hy7n46A7imE24ol++ikXBsjf3Bi1qDBu4H91FfY6LrYXJaxRlc2gIIpC8AOJrCbqg==",
+ "dependencies": {
+ "MimeKit": "4.12.0",
+ "System.Formats.Asn1": "8.0.1"
+ }
+ },
"MediatR": {
"type": "Transitive",
"resolved": "12.4.1",
@@ -775,6 +789,15 @@
"System.Security.Principal.Windows": "4.5.0"
}
},
+ "MimeKit": {
+ "type": "Transitive",
+ "resolved": "4.12.0",
+ "contentHash": "PFUHfs6BZxKYM/QPJksAwXphbJf0SEfdSfsoQ6p6yvFRaJPofFJMBiotWhFRrdSUzfp6C6K49EjBIqIwZ2TJqA==",
+ "dependencies": {
+ "BouncyCastle.Cryptography": "2.5.1",
+ "System.Security.Cryptography.Pkcs": "8.0.1"
+ }
+ },
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.3",
@@ -848,6 +871,11 @@
"resolved": "6.0.0",
"contentHash": "lcyUiXTsETK2ALsZrX+nWuHSIQeazhqPphLfaRxzdGaG93+0kELqpgEHtwWOlQe7+jSFnKwaCAgL4kjeZCQJnw=="
},
+ "System.Formats.Asn1": {
+ "type": "Transitive",
+ "resolved": "8.0.1",
+ "contentHash": "XqKba7Mm/koKSjKMfW82olQdmfbI5yqeoLV/tidRp7fbh5rmHAQ5raDI/7SU0swTzv+jgqtUGkzmFxuUg0it1A=="
+ },
"System.IdentityModel.Tokens.Jwt": {
"type": "Transitive",
"resolved": "8.0.1",
@@ -1012,6 +1040,7 @@
"type": "Project",
"dependencies": {
"Application": "[1.0.0, )",
+ "MailKit": "[4.12.1, )",
"Microsoft.Extensions.Http": "[9.0.4, )",
"Newtonsoft.Json": "[13.0.3, )"
}