| | | 1 | | using System.Diagnostics.CodeAnalysis; |
| | | 2 | | using System.Net; |
| | | 3 | | using System.Net.Mail; |
| | | 4 | | using Microsoft.Extensions.Configuration; |
| | | 5 | | using MechanicsSoftware.Application.Abstractions; |
| | | 6 | | using MechanicsSoftware.Domain.ValueObjects; |
| | | 7 | | |
| | | 8 | | namespace MechanicsSoftware.Infrastructure.Notifications; |
| | | 9 | | |
| | | 10 | | public sealed class SmtpEmailNotifier : IEmailNotifier |
| | | 11 | | { |
| | | 12 | | private readonly string _host; |
| | | 13 | | private readonly int _port; |
| | | 14 | | private readonly string _user; |
| | | 15 | | private readonly string _pass; |
| | | 16 | | private readonly string _from; |
| | | 17 | | |
| | 22 | 18 | | public SmtpEmailNotifier(IConfiguration configuration) |
| | 22 | 19 | | { |
| | 22 | 20 | | _host = configuration["SMTP_HOST"] |
| | 22 | 21 | | ?? throw new InvalidOperationException( |
| | 22 | 22 | | "SMTP host not configured. Set the 'SMTP_HOST' environment variable."); |
| | | 23 | | |
| | 20 | 24 | | var portRaw = configuration["SMTP_PORT"] |
| | 20 | 25 | | ?? throw new InvalidOperationException( |
| | 20 | 26 | | "SMTP port not configured. Set the 'SMTP_PORT' environment variable."); |
| | | 27 | | |
| | 18 | 28 | | if (!int.TryParse(portRaw, out _port)) |
| | 10 | 29 | | throw new InvalidOperationException( |
| | 10 | 30 | | $"Invalid value for 'SMTP_PORT': '{portRaw}'. Expected a numeric port (e.g. 587)."); |
| | | 31 | | |
| | 8 | 32 | | _user = configuration["SMTP_USER"] |
| | 8 | 33 | | ?? throw new InvalidOperationException( |
| | 8 | 34 | | "SMTP user not configured. Set the 'SMTP_USER' environment variable."); |
| | | 35 | | |
| | 6 | 36 | | _pass = configuration["SMTP_PASS"] |
| | 6 | 37 | | ?? throw new InvalidOperationException( |
| | 6 | 38 | | "SMTP password not configured. Set the 'SMTP_PASS' environment variable."); |
| | | 39 | | |
| | 4 | 40 | | _from = configuration["SMTP_FROM"] |
| | 4 | 41 | | ?? throw new InvalidOperationException( |
| | 4 | 42 | | "SMTP sender address not configured. Set the 'SMTP_FROM' environment variable."); |
| | 2 | 43 | | } |
| | | 44 | | |
| | | 45 | | [ExcludeFromCodeCoverage] |
| | | 46 | | public async Task SendStatusChangedAsync( |
| | | 47 | | string toEmail, |
| | | 48 | | string customerName, |
| | | 49 | | Guid serviceOrderId, |
| | | 50 | | ServiceOrderStatus.Status newStatus, |
| | | 51 | | CancellationToken cancellationToken = default) |
| | | 52 | | { |
| | | 53 | | var subject = $"Atualização da sua Ordem de Serviço #{serviceOrderId}"; |
| | | 54 | | var body = BuildBody(customerName, serviceOrderId, newStatus); |
| | | 55 | | |
| | | 56 | | using var client = new SmtpClient(_host, _port) |
| | | 57 | | { |
| | | 58 | | Credentials = new NetworkCredential(_user, _pass), |
| | | 59 | | EnableSsl = true |
| | | 60 | | }; |
| | | 61 | | |
| | | 62 | | using var message = new MailMessage( |
| | | 63 | | from: new MailAddress(_from), |
| | | 64 | | to: new MailAddress(toEmail)) |
| | | 65 | | { |
| | | 66 | | Subject = subject, |
| | | 67 | | Body = body, |
| | | 68 | | IsBodyHtml = true |
| | | 69 | | }; |
| | | 70 | | |
| | | 71 | | await client.SendMailAsync(message, cancellationToken); |
| | | 72 | | } |
| | | 73 | | |
| | | 74 | | [ExcludeFromCodeCoverage] |
| | | 75 | | private static string BuildBody( |
| | | 76 | | string customerName, |
| | | 77 | | Guid serviceOrderId, |
| | | 78 | | ServiceOrderStatus.Status newStatus) |
| | | 79 | | { |
| | | 80 | | var statusLabel = new ServiceOrderStatus(newStatus).ToString(); |
| | | 81 | | |
| | | 82 | | return $""" |
| | | 83 | | <!DOCTYPE html> |
| | | 84 | | <html lang="pt-BR"> |
| | | 85 | | <head><meta charset="UTF-8" /></head> |
| | | 86 | | <body style="font-family:Arial,sans-serif;background:#f4f4f4;padding:32px;"> |
| | | 87 | | <table width="600" cellpadding="0" cellspacing="0" |
| | | 88 | | style="background:#ffffff;border-radius:8px;padding:32px;margin:auto;"> |
| | | 89 | | <tr> |
| | | 90 | | <td> |
| | | 91 | | <h2 style="color:#1a1a1a;margin-top:0;"> |
| | | 92 | | Atualização da sua Ordem de Serviço |
| | | 93 | | </h2> |
| | | 94 | | <p style="color:#333;">Olá, <strong>{customerName}</strong>!</p> |
| | | 95 | | <p style="color:#333;"> |
| | | 96 | | Sua Ordem de Serviço <strong>#{serviceOrderId}</strong> |
| | | 97 | | teve o status atualizado. |
| | | 98 | | </p> |
| | | 99 | | <p style="background:#f0f4ff;border-left:4px solid #4f6ef7; |
| | | 100 | | padding:12px 16px;border-radius:4px;color:#1a1a1a;"> |
| | | 101 | | Novo status: <strong>{statusLabel}</strong> |
| | | 102 | | </p> |
| | | 103 | | <p style="color:#555;font-size:14px;"> |
| | | 104 | | Caso tenha dúvidas, entre em contato conosco. |
| | | 105 | | </p> |
| | | 106 | | <hr style="border:none;border-top:1px solid #eee;margin:24px 0;" /> |
| | | 107 | | <p style="color:#999;font-size:12px;margin:0;"> |
| | | 108 | | Equipe de Atendimento |
| | | 109 | | </p> |
| | | 110 | | </td> |
| | | 111 | | </tr> |
| | | 112 | | </table> |
| | | 113 | | </body> |
| | | 114 | | </html> |
| | | 115 | | """; |
| | | 116 | | } |
| | | 117 | | } |