First Commit

This commit is contained in:
Jorge Luis caudillo esparza
2026-05-09 16:36:57 -07:00
parent 96ad4c0d3e
commit d0c284d9c0
131 changed files with 4009 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using ADS.Helpers.NotifyCenter.Abstractions;
namespace ADS.Helpers.NotifyCenter.Providers;
// Simple in-memory settings store. Later you replace this with a tenant-aware provider (DB/SQLite/etc.)
public sealed class InMemoryNotificationSettingsProvider : INotificationSettingsProvider
{
private readonly ConcurrentDictionary<(Type type, string? tenantId), object> _store = new();
public Task<TSettings> GetAsync<TSettings>(string? tenantId, CancellationToken ct = default)
where TSettings : class, new()
{
var key = (typeof(TSettings), tenantId);
if (_store.TryGetValue(key, out var obj) && obj is TSettings typed)
return Task.FromResult(typed);
// fallback to default (tenantId = null)
var keyDefault = (typeof(TSettings), (string?)null);
if (_store.TryGetValue(keyDefault, out var objDefault) && objDefault is TSettings typedDefault)
return Task.FromResult(typedDefault);
return Task.FromResult(new TSettings());
}
public void Set<TSettings>(TSettings settings, string? tenantId = null) where TSettings : class
{
_store[(typeof(TSettings), tenantId)] = settings;
}
}

View File

@@ -0,0 +1,139 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ADS.Helpers.Base.Abstractions;
using ADS.Helpers.MultiTenant.Context;
using ADS.Helpers.NotifyCenter.Abstractions;
using ADS.Helpers.NotifyCenter.Models;
using MailKit.Net.Smtp;
using MailKit.Security;
namespace ADS.Helpers.NotifyCenter.Providers.Smtp;
public sealed class SmtpNotificationProvider : INotificationProvider
{
private readonly INotificationSettingsProvider _settingsProvider;
private readonly ITenantContextAccessor _tenantAccessor;
private readonly IAppLogger _log;
public NotificationChannel Channel => NotificationChannel.SmtpEmail;
public string Name => "SMTP";
public SmtpNotificationProvider(
INotificationSettingsProvider settingsProvider,
ITenantContextAccessor tenantAccessor,
IAppLogger log)
{
_settingsProvider = settingsProvider;
_tenantAccessor = tenantAccessor;
_log = log;
}
public async Task<NotificationResult> SendAsync(NotificationMessage message, CancellationToken ct = default)
{
var tenant = _tenantAccessor.Current;
if (tenant is null || string.IsNullOrWhiteSpace(tenant.TenantId))
{
var dbg = "TenantContext is not available for SMTP provider.";
_log.Error(
source: MessageSources.SmtpProvider,
code: NotifyCodes.TenantContextMissing,
message: dbg,
ex: null,
data: new { message.TraceId });
return NotificationResult.Fail(
providerName: Name,
source: MessageSources.SmtpProvider,
code: NotifyCodes.TenantContextMissing,
debugMessage: dbg);
}
var opts = await _settingsProvider.GetAsync<SmtpSettings>(tenant.TenantId, ct);
if (string.IsNullOrWhiteSpace(opts.Host) || opts.Port <= 0 || string.IsNullOrWhiteSpace(opts.FromEmail))
{
_log.Warn(
source: MessageSources.SmtpProvider,
code: NotifyCodes.SmtpNotConfigured,
message: "SMTP settings are missing or invalid.",
data: new { message.TraceId, tenant.TenantId });
return NotificationResult.Fail(
providerName: Name,
source: MessageSources.SmtpProvider,
code: NotifyCodes.SmtpNotConfigured,
debugMessage: "Host/Port/FromEmail missing or invalid.");
}
var mime = new MimeKit.MimeMessage();
mime.From.Add(new MimeKit.MailboxAddress(opts.FromName ?? "", opts.FromEmail));
foreach (var to in message.To.Distinct(StringComparer.OrdinalIgnoreCase))
mime.To.Add(MimeKit.MailboxAddress.Parse(to));
foreach (var cc in message.Cc.Distinct(StringComparer.OrdinalIgnoreCase))
mime.Cc.Add(MimeKit.MailboxAddress.Parse(cc));
foreach (var bcc in message.Bcc.Distinct(StringComparer.OrdinalIgnoreCase))
mime.Bcc.Add(MimeKit.MailboxAddress.Parse(bcc));
mime.Subject = message.Subject ?? "";
var bodyBuilder = new MimeKit.BodyBuilder
{
TextBody = message.BodyText,
HtmlBody = message.BodyHtml
};
mime.Body = bodyBuilder.ToMessageBody();
try
{
using var client = new SmtpClient { Timeout = opts.TimeoutMs };
SecureSocketOptions sec;
if (opts.UseSsl) sec = SecureSocketOptions.SslOnConnect;
else if (opts.UseStartTls) sec = SecureSocketOptions.StartTls;
else sec = SecureSocketOptions.None;
await client.ConnectAsync(opts.Host, opts.Port, sec, ct);
if (!string.IsNullOrWhiteSpace(opts.Username))
await client.AuthenticateAsync(opts.Username, opts.Password, ct);
await client.SendAsync(mime, ct);
await client.DisconnectAsync(true, ct);
_log.Info(
source: MessageSources.SmtpProvider,
code: NotifyCodes.Ok,
message: "SMTP message sent.",
data: new { message.TraceId, tenant.TenantId });
return NotificationResult.Ok(
providerName: Name,
source: MessageSources.SmtpProvider,
code: NotifyCodes.Ok,
providerMessageId: null,
debugMessage: "SMTP message sent.");
}
catch (Exception ex)
{
_log.Error(
source: MessageSources.SmtpProvider,
code: NotifyCodes.SmtpSendFailed,
message: "SMTP send failed.",
ex: ex,
data: new { message.TraceId, tenant.TenantId });
return NotificationResult.Fail(
providerName: Name,
source: MessageSources.SmtpProvider,
code: NotifyCodes.SmtpSendFailed,
debugMessage: ex.Message);
}
}
}

View File

@@ -0,0 +1,19 @@
namespace ADS.Helpers.NotifyCenter.Providers.Smtp;
public sealed class SmtpOptions
{
public string Host { get; set; } = "";
public int Port { get; set; } = 587;
public bool UseSsl { get; set; } = false; // implicit SSL (SMTPS)
public bool UseStartTls { get; set; } = true; // STARTTLS
public string Username { get; set; } = "";
public string Password { get; set; } = "";
public string FromEmail { get; set; } = "";
public string FromName { get; set; } = "";
// Optional timeout
public int TimeoutMs { get; set; } = 15000;
}

View File

@@ -0,0 +1,18 @@
namespace ADS.Helpers.NotifyCenter.Providers.Smtp;
public sealed class SmtpSettings
{
public string Host { get; set; } = "";
public int Port { get; set; } = 587;
public bool UseSsl { get; set; } = false;
public bool UseStartTls { get; set; } = true;
public string Username { get; set; } = "";
public string Password { get; set; } = "";
public string FromEmail { get; set; } = "";
public string FromName { get; set; } = "";
public int TimeoutMs { get; set; } = 15000;
}