From 0e3b1b1b4ec9001802deaffb952bf661af7c6a99 Mon Sep 17 00:00:00 2001 From: oussi Date: Tue, 7 Apr 2026 11:30:11 +0200 Subject: [PATCH] feat: alerter module SMTP avec lettre --- src/alerter.rs | 124 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 1 deletion(-) diff --git a/src/alerter.rs b/src/alerter.rs index b4a5c24..29ef8f9 100644 --- a/src/alerter.rs +++ b/src/alerter.rs @@ -1 +1,123 @@ -// Alerter module — Task 3 +use crate::config::SmtpConfig; +use lettre::{ + message::header::ContentType, + transport::smtp::authentication::Credentials, + AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor, +}; + +pub fn is_smtp_configured(smtp: &SmtpConfig) -> bool { + !smtp.server.is_empty() && !smtp.from_email.is_empty() && !smtp.to_emails.is_empty() +} + +pub struct Alerter; + +impl Alerter { + pub async fn send(&self, smtp: &SmtpConfig, subject: &str, body: &str) -> (bool, String) { + if !is_smtp_configured(smtp) { + return (false, "SMTP non configure".into()); + } + match self.send_email(smtp, subject, body).await { + Ok(msg) => (true, msg), + Err(e) => (false, e), + } + } + + async fn send_email( + &self, + smtp: &SmtpConfig, + subject: &str, + body: &str, + ) -> Result { + let from = smtp + .from_email + .parse() + .map_err(|_| "Email expediteur invalide".to_string())?; + + let mut builder = Message::builder().from(from).subject(subject); + for recipient in &smtp.to_emails { + let mb = recipient + .parse() + .map_err(|_| format!("Destinataire invalide: {}", recipient))?; + builder = builder.to(mb); + } + let email = builder + .header(ContentType::TEXT_PLAIN) + .body(body.to_string()) + .map_err(|e| e.to_string())?; + + let transport = self.build_transport(smtp)?; + transport + .send(email) + .await + .map(|_| "Email envoye avec succes".to_string()) + .map_err(|e| format!("Erreur SMTP: {}", e)) + } + + fn build_transport( + &self, + smtp: &SmtpConfig, + ) -> Result, String> { + let mut builder = if smtp.use_tls { + AsyncSmtpTransport::::starttls_relay(&smtp.server) + .map_err(|e| e.to_string())? + } else { + AsyncSmtpTransport::::builder_dangerous(&smtp.server) + }; + + builder = builder.port(smtp.port); + + if !smtp.username.is_empty() { + builder = builder.credentials(Credentials::new( + smtp.username.clone(), + smtp.password.clone(), + )); + } + + Ok(builder.build()) + } + + pub async fn send_test(&self, smtp: &SmtpConfig) -> (bool, String) { + let subject = "[TEST] Supervision - Test de configuration email"; + let body = "Ceci est un email de test.\n\nSi vous recevez ce message, la configuration SMTP est correcte.\n\n-- Supervision"; + self.send(smtp, subject, body).await + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn not_configured_when_server_empty() { + let smtp = SmtpConfig::default(); + assert!(!is_smtp_configured(&smtp)); + } + + #[test] + fn configured_when_all_fields_present() { + let smtp = SmtpConfig { + server: "smtp.example.com".into(), + port: 587, + use_tls: true, + username: "user".into(), + password: "pass".into(), + from_email: "from@example.com".into(), + to_emails: vec!["to@example.com".into()], + }; + assert!(is_smtp_configured(&smtp)); + } + + #[test] + fn not_configured_when_no_recipients() { + let smtp = SmtpConfig { + server: "smtp.example.com".into(), + port: 587, + use_tls: true, + username: "user".into(), + password: "pass".into(), + from_email: "from@example.com".into(), + to_emails: vec![], + }; + assert!(!is_smtp_configured(&smtp)); + } +}