use std::{path::PathBuf, str::FromStr}; use email_address::EmailAddress; use time::OffsetDateTime; use super::{ EmailMessenger, EmailMessengerError, EmailType, EMAIL_TEMPLATE_DEFAULT_LOGIN, EMAIL_TEMPLATE_DEFAULT_SIGNUP, }; pub(crate) struct LocalEmailStubber { pub(crate) email_template_login: Option, pub(crate) email_template_signup: Option, } #[async_trait::async_trait] impl EmailMessenger for LocalEmailStubber { // TODO: this module really shouldn't be called client, it should be called services... the client is sqlx/mailgun/sns wrapper or whatever... async fn send_email( &self, email_address: &str, validation_id: &str, secret_code: &str, t: EmailType, ) -> Result<(), EmailMessengerError> { let login_template = self .email_template_login .clone() .unwrap_or(EMAIL_TEMPLATE_DEFAULT_LOGIN.to_string()); let signup_template = self .email_template_signup .clone() .unwrap_or(EMAIL_TEMPLATE_DEFAULT_SIGNUP.to_string()); let replace_template = |s: &str| { s.replace( "%secd_link%", &format!("{}?code={}", validation_id, secret_code), ) .replace("%secd_email_address%", email_address) .replace("%secd_code%", secret_code) }; if !EmailAddress::is_valid(email_address) { return Err(EmailMessengerError::InvalidEmailAddress); } let body = match t { EmailType::Login => replace_template(&login_template), EmailType::Signup => replace_template(&signup_template), }; // TODO: write to the system mailbox instead? std::fs::write( PathBuf::from_str(&format!( "/tmp/{}_{}.localmail", OffsetDateTime::now_utc(), validation_id )) .map_err(|_| EmailMessengerError::Unknown)?, body, ) .map_err(|_| EmailMessengerError::FailedToSendEmail)?; Ok(()) } }