mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-15 18:14:44 +00:00
* Integrate Spectre.Console into seed application, use async data loading * Replace stored-procedure repositories with Dapper-based repositories Removes the USP_* stored procedures for Auth, Location, and Brewery data access in favour of services built with Dapper. Adds a new Features.Locations slice and optimistic concurrency support for the Brewery slice. * Add new documentation for Dapper migration/database schema * Update FluentAssertions to most recent open source ver * Update XML doc comment in backend projects * Modify brewery location dto to include denormalized data * - Updated column names from 'Timer' to 'RowVersion' in schema.sql for - Introduced new BeerPost entity and updated BreweryPost and BreweryPostLocation entities to use 'RowVersion' for concurrency. - Modified UserAccount, UserCredential, UserVerification, City, StateProvince, and Country entities to use 'RowVersion' instead of 'Timer'. - Updated tests and command handlers to reflect changes in concurrency token from 'Timer' to 'RowVersion'. - Adjusted BreweryDto and BreweryDtoMapper to include 'RowVersion' and updated mapping logic accordingly. - Ensured all repository methods and interfaces are aligned with the new concurrency handling approach. * updates * Add BeerStyle entity and related properties; refactor BreweryDto to remove BeerPosts * Fix Api.Specs test failures: stale Timer column refs and missing test.user fixture * Remove repository tests, usages of registration dto used in repo * archive old stored procs/functions * update stale docs * Add instructions for generating cryptographically secure base64 secrets on macOS/Linux and Windows
115 lines
4.1 KiB
C#
115 lines
4.1 KiB
C#
using MailKit.Net.Smtp;
|
|
using MailKit.Security;
|
|
using MimeKit;
|
|
|
|
namespace Infrastructure.Email;
|
|
|
|
/// <summary>
|
|
/// SMTP email service implementation using MailKit.
|
|
/// Configured via environment variables.
|
|
/// </summary>
|
|
public class SmtpEmailProvider : IEmailProvider
|
|
{
|
|
private readonly string _fromEmail;
|
|
private readonly string _fromName;
|
|
private readonly string _host;
|
|
private readonly string? _password;
|
|
private readonly int _port;
|
|
private readonly string? _username;
|
|
private readonly bool _useSsl;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="SmtpEmailProvider" /> class, reading SMTP configuration
|
|
/// from environment variables (<c>SMTP_HOST</c>, <c>SMTP_PORT</c>, <c>SMTP_USERNAME</c>,
|
|
/// <c>SMTP_PASSWORD</c>, <c>SMTP_USE_SSL</c>, <c>SMTP_FROM_EMAIL</c>, <c>SMTP_FROM_NAME</c>).
|
|
/// </summary>
|
|
/// <exception cref="InvalidOperationException">
|
|
/// Thrown when <c>SMTP_HOST</c> or <c>SMTP_FROM_EMAIL</c> is not set, or when <c>SMTP_PORT</c> is not a valid integer.
|
|
/// </exception>
|
|
public SmtpEmailProvider()
|
|
{
|
|
_host =
|
|
Environment.GetEnvironmentVariable("SMTP_HOST")
|
|
?? throw new InvalidOperationException("SMTP_HOST environment variable is not set");
|
|
|
|
string portString = Environment.GetEnvironmentVariable("SMTP_PORT") ?? "587";
|
|
if (!int.TryParse(portString, out _port))
|
|
throw new InvalidOperationException($"SMTP_PORT '{portString}' is not a valid integer");
|
|
|
|
_username = Environment.GetEnvironmentVariable("SMTP_USERNAME");
|
|
_password = Environment.GetEnvironmentVariable("SMTP_PASSWORD");
|
|
|
|
string useSslString = Environment.GetEnvironmentVariable("SMTP_USE_SSL") ?? "true";
|
|
_useSsl = bool.Parse(useSslString);
|
|
|
|
_fromEmail =
|
|
Environment.GetEnvironmentVariable("SMTP_FROM_EMAIL")
|
|
?? throw new InvalidOperationException(
|
|
"SMTP_FROM_EMAIL environment variable is not set"
|
|
);
|
|
|
|
_fromName = Environment.GetEnvironmentVariable("SMTP_FROM_NAME") ?? "The Biergarten";
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public async Task SendAsync(string to, string subject, string body, bool isHtml = true)
|
|
{
|
|
await SendAsync([to], subject, body, isHtml);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
/// <remarks>
|
|
/// Connects using StartTls when SSL is enabled (or no encryption otherwise), authenticates
|
|
/// if credentials were configured, sends the message, and disconnects.
|
|
/// </remarks>
|
|
/// <exception cref="InvalidOperationException">
|
|
/// Thrown when connecting, authenticating, or sending via SMTP fails, wrapping the original exception.
|
|
/// </exception>
|
|
public async Task SendAsync(
|
|
IEnumerable<string> to,
|
|
string subject,
|
|
string body,
|
|
bool isHtml = true
|
|
)
|
|
{
|
|
MimeMessage message = new();
|
|
message.From.Add(new MailboxAddress(_fromName, _fromEmail));
|
|
|
|
foreach (string recipient in to)
|
|
message.To.Add(MailboxAddress.Parse(recipient));
|
|
|
|
message.Subject = subject;
|
|
|
|
BodyBuilder bodyBuilder = new();
|
|
if (isHtml)
|
|
bodyBuilder.HtmlBody = body;
|
|
else
|
|
bodyBuilder.TextBody = body;
|
|
|
|
message.Body = bodyBuilder.ToMessageBody();
|
|
|
|
using SmtpClient client = new();
|
|
|
|
try
|
|
{
|
|
// Determine the SecureSocketOptions based on SSL setting
|
|
SecureSocketOptions secureSocketOptions = _useSsl
|
|
? SecureSocketOptions.StartTls
|
|
: SecureSocketOptions.None;
|
|
|
|
await client.ConnectAsync(_host, _port, secureSocketOptions);
|
|
|
|
// Authenticate if credentials are provided
|
|
if (!string.IsNullOrEmpty(_username) && !string.IsNullOrEmpty(_password))
|
|
await client.AuthenticateAsync(_username, _password);
|
|
|
|
await client.SendAsync(message);
|
|
await client.DisconnectAsync(true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
throw new InvalidOperationException($"Failed to send email: {ex.Message}", ex);
|
|
}
|
|
}
|
|
}
|