using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using RabbitMQ.Client; using RabbitMQ.Client.Events; using System.Net; using System.Net.Mail; using System.Text; using System.Text.Json; using FrameworkBLL.ActionProcessor; namespace FrameworkSL.Controllers.ActionProcessor { /// /// Simple dedicated worker: listens on "email-direct" queue → sends email via SMTP. /// No DB, no tenant, no idempotency — pure RabbitMQ → SMTP pipeline. /// public sealed class SimpleEmailWorker : BackgroundService { public const string QueueName = "email-direct"; private readonly IConfiguration _config; private readonly SmtpSettings _smtp; private readonly ILogger _logger; public SimpleEmailWorker( IConfiguration config, IOptions smtp, ILogger logger) { _config = config; _smtp = smtp.Value; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken ct) { _logger.LogInformation("SimpleEmailWorker: starting"); while (!ct.IsCancellationRequested) { try { await RunAsync(ct).ConfigureAwait(false); } catch (OperationCanceledException) when (ct.IsCancellationRequested) { break; } catch (Exception ex) { _logger.LogError(ex, "SimpleEmailWorker: connection lost — retrying in 30s"); await Task.Delay(TimeSpan.FromSeconds(30), ct).ConfigureAwait(false); } } _logger.LogInformation("SimpleEmailWorker: stopped"); } private async Task RunAsync(CancellationToken ct) { var factory = new ConnectionFactory { HostName = _config["RabbitMQ:Host"] ?? "localhost", UserName = _config["RabbitMQ:UserName"] ?? "guest", Password = _config["RabbitMQ:Password"] ?? "guest", VirtualHost = _config["RabbitMQ:VirtualHost"] ?? "/", AutomaticRecoveryEnabled = true }; _logger.LogInformation("SimpleEmailWorker: connecting to RabbitMQ at {Host}", factory.HostName); var connection = await factory.CreateConnectionAsync(ct).ConfigureAwait(false); _logger.LogInformation("SimpleEmailWorker: ✅ RabbitMQ connected"); var channel = await connection.CreateChannelAsync(cancellationToken: ct); await channel.QueueDeclareAsync( queue: QueueName, durable: true, exclusive: false, autoDelete: false, arguments: null, cancellationToken: ct); await channel.BasicQosAsync(0, 1, false, ct); _logger.LogInformation("SimpleEmailWorker: ✅ Listening on queue '{Q}'", QueueName); var consumer = new AsyncEventingBasicConsumer(channel); consumer.ReceivedAsync += async (_, ea) => { var raw = Encoding.UTF8.GetString(ea.Body.Span); _logger.LogInformation("SimpleEmailWorker: 📨 Message received | Body={Body}", raw); SimpleEmailMessage? msg = null; try { msg = JsonSerializer.Deserialize(ea.Body.Span); } catch (Exception ex) { _logger.LogError(ex, "SimpleEmailWorker: ❌ Deserialize failed — ACKing bad message"); await channel.BasicAckAsync(ea.DeliveryTag, false); return; } if (msg is null || string.IsNullOrWhiteSpace(msg.To)) { _logger.LogError("SimpleEmailWorker: ❌ 'To' is empty — ACKing bad message"); await channel.BasicAckAsync(ea.DeliveryTag, false); return; } _logger.LogInformation( "SimpleEmailWorker: 📧 Sending email | To={To} Subject={Subject} SmtpHost={Host}:{Port}", msg.To, msg.Subject, _smtp.Host, _smtp.Port); try { #pragma warning disable SYSLIB0006 using var client = new SmtpClient(_smtp.Host, _smtp.Port) { Credentials = new NetworkCredential(_smtp.User, _smtp.Password), EnableSsl = _smtp.EnableSsl }; #pragma warning restore SYSLIB0006 using var mail = new MailMessage { From = new MailAddress(_smtp.User, "GoodBooks ERP"), Subject = msg.Subject ?? "GoodBooks Notification", Body = msg.Body ?? string.Empty, IsBodyHtml = msg.IsHtml }; mail.To.Add(msg.To); await client.SendMailAsync(mail, ct); _logger.LogInformation("SimpleEmailWorker: ✅ EMAIL SENT | To={To}", msg.To); await channel.BasicAckAsync(ea.DeliveryTag, false); } catch (Exception ex) { _logger.LogError(ex, "SimpleEmailWorker: ❌ SMTP FAILED | To={To} Host={Host}:{Port}", msg.To, _smtp.Host, _smtp.Port); await channel.BasicNackAsync(ea.DeliveryTag, false, requeue: false); } }; await channel.BasicConsumeAsync(QueueName, autoAck: false, consumer, ct); await Task.Delay(Timeout.Infinite, ct).ConfigureAwait(false); } } public class SimpleEmailMessage { public string To { get; set; } = string.Empty; public string? Subject { get; set; } public string? Body { get; set; } public bool IsHtml { get; set; } = false; } }