From 032a19931dd82f79f3efd138f0a6b220dc9f8e9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ahmet=20Kaan=20G=C3=9CM=C3=9C=C5=9E?= <96421894+Tahinli@users.noreply.github.com> Date: Wed, 18 Dec 2024 04:53:14 +0300 Subject: [PATCH] feat: :sparkles: send_mail --- Cargo.toml | 7 ++-- configs/mail_config.toml | 8 +++++ src/lib.rs | 15 ++++---- src/mail.rs | 77 ++++++++++++++++++++++++++++++++++++++++ src/utils.rs | 8 ++--- 5 files changed, 101 insertions(+), 14 deletions(-) create mode 100644 configs/mail_config.toml create mode 100644 src/mail.rs diff --git a/Cargo.toml b/Cargo.toml index 79d74d3..9525b01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,9 +16,10 @@ strip = "symbols" [dependencies] axum = "0.7.9" -chrono = { version = "0.4.38", features = ["serde"] } -serde = { version = "1.0.215", features = ["derive"] } +chrono = { version = "0.4.39", features = ["serde"] } +lettre = { version = "0.11.11", default-features = false, features = ["builder", "smtp-transport", "tokio1-rustls-tls"] } +serde = { version = "1.0.216", features = ["derive"] } serde_json = "1.0.133" sqlx = { version = "0.8.2", features = ["chrono", "macros", "postgres", "runtime-tokio-rustls"] } -tokio = { version = "1.41.1", features = ["full"] } +tokio = { version = "1.42.0", features = ["rt-multi-thread"] } tower-http = { version = "0.6.2", features = ["cors"] } diff --git a/configs/mail_config.toml b/configs/mail_config.toml new file mode 100644 index 0000000..c97a3b6 --- /dev/null +++ b/configs/mail_config.toml @@ -0,0 +1,8 @@ +[mail_config] +name = "Ahmet Kaan Gümüş" +mail_address = "mail@mail.com" +username = "username" +password = "password" +relay_address = "localhost" +port = 587 +starttls = true \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 9c94eed..c1ec548 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ pub mod database; pub mod error; pub mod feature; +pub mod mail; pub mod routing; pub mod server; pub mod utils; @@ -26,12 +27,12 @@ impl Default for DatabaseConfig { if header == "[database_config]" { Self { - connection_pool_size: database_configs.pop().unwrap().parse().unwrap(), - backend: database_configs.pop().unwrap(), - database: database_configs.pop().unwrap(), - password: database_configs.pop().unwrap(), - username: database_configs.pop().unwrap(), - address: database_configs.pop().unwrap(), + address: database_configs.pop_front().unwrap().parse().unwrap(), + username: database_configs.pop_front().unwrap().parse().unwrap(), + password: database_configs.pop_front().unwrap().parse().unwrap(), + database: database_configs.pop_front().unwrap().parse().unwrap(), + backend: database_configs.pop_front().unwrap().parse().unwrap(), + connection_pool_size: database_configs.pop_front().unwrap().parse().unwrap(), } } else { panic!("Database Config File Must Include [database_config] at the First Line") @@ -50,7 +51,7 @@ impl Default for ServerConfig { if header == "[server_config]" { Self { - address: server_configs.pop().unwrap(), + address: server_configs.pop_front().unwrap().parse().unwrap(), } } else { panic!("Server Config File Must Include [server_config] at the First Line") diff --git a/src/mail.rs b/src/mail.rs new file mode 100644 index 0000000..167602c --- /dev/null +++ b/src/mail.rs @@ -0,0 +1,77 @@ +use lettre::{ + message::header::ContentType, + transport::smtp::{self, authentication::Credentials}, + AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor, +}; + +use crate::utils::naive_toml_parser; + +const MAIL_CONFIG_FILE_LOCATION: &str = "./configs/mail_config.toml"; + +pub struct MailConfig { + name: String, + mail_address: String, + username: String, + password: String, + relay_server: String, + port: u16, + starttls: bool, +} + +impl Default for MailConfig { + fn default() -> Self { + let (header, mut mail_configs) = naive_toml_parser(MAIL_CONFIG_FILE_LOCATION); + + if header == "[mail_config]" { + Self { + name: mail_configs.pop_front().unwrap().parse().unwrap(), + mail_address: mail_configs.pop_front().unwrap().parse().unwrap(), + username: mail_configs.pop_front().unwrap().parse().unwrap(), + password: mail_configs.pop_front().unwrap().parse().unwrap(), + relay_server: mail_configs.pop_front().unwrap().parse().unwrap(), + port: mail_configs.pop_front().unwrap().parse().unwrap(), + starttls: mail_configs.pop_front().unwrap().parse().unwrap(), + } + } else { + panic!("Mail Config File Must Include [mail_config] at the First Line") + } + } +} + +pub async fn send_mail( + receiver: &String, + subject: &String, + body: &String, +) -> Result { + let mail_config = MailConfig::default(); + + let message = Message::builder() + .from( + format!("{} <{}>", mail_config.name, mail_config.mail_address) + .parse() + .unwrap(), + ) + .to(format!("<{}>", receiver).parse().unwrap()) + .subject(subject) + .header(ContentType::TEXT_PLAIN) + .body(body.to_owned()) + .unwrap(); + + let credentials = Credentials::new( + mail_config.username.to_owned(), + mail_config.password.to_owned(), + ); + + let mailer = match mail_config.starttls { + true => AsyncSmtpTransport::::starttls_relay(&mail_config.relay_server), + false => AsyncSmtpTransport::::relay(&mail_config.relay_server), + }; + + let mailer = mailer + .unwrap() + .credentials(credentials) + .port(mail_config.port) + .build(); + + mailer.send(message).await +} diff --git a/src/utils.rs b/src/utils.rs index 565e99e..1e8d048 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,14 +1,14 @@ -use std::{fs::File, io::Read}; +use std::{collections::VecDeque, fs::File, io::Read}; use crate::error::ForumInputError; -pub fn naive_toml_parser(file_location: &str) -> (String, Vec) { +pub fn naive_toml_parser(file_location: &str) -> (String, VecDeque) { let mut toml_file = File::open(file_location).unwrap(); let mut toml_ingredients = String::default(); toml_file.read_to_string(&mut toml_ingredients).unwrap(); - let mut toml_ingredients = toml_ingredients.lines().collect::>(); + let mut toml_ingredients = toml_ingredients.lines().collect::>(); - let header = toml_ingredients.remove(0).trim_end().to_string(); + let header = toml_ingredients.pop_front().unwrap().trim_end().to_string(); let parsed = toml_ingredients .iter() .map(|ingredient| {