feat: alerter module SMTP avec lettre

This commit is contained in:
oussi
2026-04-07 11:30:11 +02:00
parent 655f9e567a
commit 0e3b1b1b4e

View File

@@ -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<String, String> {
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<AsyncSmtpTransport<Tokio1Executor>, String> {
let mut builder = if smtp.use_tls {
AsyncSmtpTransport::<Tokio1Executor>::starttls_relay(&smtp.server)
.map_err(|e| e.to_string())?
} else {
AsyncSmtpTransport::<Tokio1Executor>::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));
}
}