package dev.lions.services; import dev.lions.models.EmailMessage; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.mail.Message; import jakarta.mail.MessagingException; import jakarta.mail.Session; import jakarta.mail.Transport; import jakarta.mail.internet.InternetAddress; import jakarta.mail.internet.MimeMessage; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import lombok.extern.slf4j.Slf4j; import dev.lions.config.ApplicationConfig; import dev.lions.models.EmailTemplate; import dev.lions.models.Notification; import dev.lions.exceptions.EmailException; import dev.lions.utils.TemplateProcessor; import java.util.Map; import java.util.Properties; /** * Service gérant l'envoi des emails dans l'application. * Cette classe assure la configuration SMTP, le traitement des modèles * et l'envoi sécurisé des emails. */ @Slf4j @ApplicationScoped public class EmailService { @Inject ApplicationConfig config; @Inject TemplateProcessor templateProcessor; private static final int MAX_RETRY_ATTEMPTS = 3; private static final long RETRY_DELAY_MS = 1000; /** * Envoie un email basé sur un modèle. * * @param template Modèle d'email à utiliser */ public void sendTemplatedEmail(@Valid @NotNull EmailTemplate template) { log.info("Préparation de l'envoi d'email avec le modèle : {}", template.getTemplateName()); try { String htmlContent = processTemplate(template); EmailMessage message = EmailMessage.builder() .from(config.getSystemEmailAddress()) .to(template.getRecipient()) .subject(template.getSubject()) .htmlContent(htmlContent) .build(); sendEmailWithRetry(message); } catch (Exception e) { log.error("Erreur lors de l'envoi de l'email", e); throw new EmailException("Impossible d'envoyer l'email"); } } /** * Traite le contenu du modèle avec les paramètres fournis. */ private String processTemplate(EmailTemplate template) { log.debug("Traitement du modèle d'email : {}", template.getTemplateName()); return templateProcessor.process( template.getContent(), template.getParameters() ); } /** * Envoie un email avec mécanisme de reprise en cas d'échec. */ private void sendEmailWithRetry(EmailMessage message) { Exception lastException = null; for (int attempt = 1; attempt <= MAX_RETRY_ATTEMPTS; attempt++) { try { sendEmail(message); log.info("Email envoyé avec succès à : {}", message.getTo()); return; } catch (Exception e) { lastException = e; log.warn("Échec de l'envoi (tentative {}/{})", attempt, MAX_RETRY_ATTEMPTS); if (attempt < MAX_RETRY_ATTEMPTS) { sleep(RETRY_DELAY_MS * attempt); } } } throw new EmailException( "Échec de l'envoi après " + MAX_RETRY_ATTEMPTS + " tentatives"); } /** * Envoie effectif de l'email via SMTP. */ private void sendEmail(EmailMessage message) throws MessagingException { Properties props = configureSmtpProperties(); Session session = createSmtpSession(props); MimeMessage mimeMessage = new MimeMessage(session); configureMimeMessage(mimeMessage, message); Transport.send(mimeMessage); } /** * Configure les propriétés SMTP. */ private Properties configureSmtpProperties() { Properties props = new Properties(); props.put("mail.smtp.host", config.getSmtpHost()); props.put("mail.smtp.port", config.getSmtpPort()); props.put("mail.smtp.auth", "true"); props.put("mail.smtp.starttls.enable", "true"); props.put("mail.smtp.connectiontimeout", "5000"); props.put("mail.smtp.timeout", "5000"); if (config.isSmtpSslEnabled()) { props.put("mail.smtp.ssl.enable", "true"); props.put("mail.smtp.ssl.trust", config.getSmtpHost()); } return props; } /** * Crée une session SMTP authentifiée. */ private Session createSmtpSession(Properties props) { return Session.getInstance(props, new jakarta.mail.Authenticator() { @Override protected jakarta.mail.PasswordAuthentication getPasswordAuthentication() { return new jakarta.mail.PasswordAuthentication( config.getSmtpUsername().orElseThrow(() -> new EmailException("Nom d'utilisateur SMTP manquant")), config.getSmtpPassword().orElseThrow(() -> new EmailException("Mot de passe SMTP manquant")) ); } }); } /** * Configure le message MIME avec les paramètres fournis. */ private void configureMimeMessage(MimeMessage mimeMessage, EmailMessage message) throws MessagingException { mimeMessage.setFrom(new InternetAddress(message.getFrom())); mimeMessage.setRecipients( Message.RecipientType.TO, InternetAddress.parse(message.getTo()) ); mimeMessage.setSubject(message.getSubject()); mimeMessage.setContent(message.getHtmlContent(), "text/html; charset=utf-8"); } /** * Envoie une notification par email. */ public void sendNotificationEmail(@NotNull Notification notification) { EmailTemplate template = EmailTemplate.builder() .templateName("notification-email") .recipient(config.getAdminEmailAddress()) .subject("Notification système : " + notification.getTitle()) .parameters(Map.of( "title", notification.getTitle(), "message", notification.getMessage(), "type", notification.getType().toString(), "timestamp", notification.getTimestamp().toString(), "actionUrl", notification.getActionUrl() )) .build(); sendTemplatedEmail(template); } private void sleep(long milliseconds) { try { Thread.sleep(milliseconds); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new EmailException("Interruption pendant la reprise"); } } }