From 3b75c4ebcd82c2d5f93ac8ea7d2acd87e297b37e Mon Sep 17 00:00:00 2001 From: Adeoye Adefemi Date: Fri, 8 Sep 2023 03:41:08 +0100 Subject: [PATCH] refactor --- .gitignore | 3 +- .vscode/settings.json | 7 +- Cargo.lock | 98 ++++++++++++++++++++ Cargo.toml | 4 +- src/assets/email.hbs | 20 ----- src/assets/email.mjml | 19 ++-- src/commands/email.rs | 131 +++++++++++++++++++-------- src/commands/gitignore.rs | 4 +- src/config.rs | 29 ++++++ src/main.rs | 8 +- src/parser.rs | 4 +- templates/email.hbs | 184 ++++++++++++++++++++++++++++++++++++++ 12 files changed, 434 insertions(+), 77 deletions(-) delete mode 100644 src/assets/email.hbs create mode 100644 src/config.rs create mode 100644 templates/email.hbs diff --git a/.gitignore b/.gitignore index 140d27f..569db4d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ /shell.js /utils.db utils.db-shm -utils.db-wal \ No newline at end of file +utils.db-wal +.env \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index fc97c31..f3d2c21 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,5 @@ { "cSpell.words": [ "sqlx" - ], - "workbench.colorCustomizations": { - "activityBar.background": "#462511", - "titleBar.activeBackground": "#613417", - "titleBar.activeForeground": "#FDFAF8" - } + ] } diff --git a/Cargo.lock b/Cargo.lock index 1606594..4d6bb41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -123,6 +123,17 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "async-trait" +version = "0.1.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + [[package]] name = "atoi" version = "1.0.0" @@ -644,6 +655,20 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "handlebars" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39b3bc2a8f715298032cf5087e58573809374b08160aa7d750582bdb82d2683" +dependencies = [ + "log", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -837,10 +862,12 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76bd09637ae3ec7bd605b8e135e757980b3968430ff2b1a4a94fb7769e50166d" dependencies = [ + "async-trait", "base64", "email-encoding", "email_address", "fastrand 1.9.0", + "futures-io", "futures-util", "hostname", "httpdate", @@ -852,6 +879,7 @@ dependencies = [ "quoted_printable", "socket2 0.4.9", "tokio", + "tokio-native-tls", ] [[package]] @@ -1092,6 +1120,51 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +[[package]] +name = "pest" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bee7be22ce7918f641a33f08e3f43388c7656772244e2bbb2477f44cc9021a" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1511785c5e98d79a05e8a6bc34b4ac2168a0e3e92161862030ad84daa223141" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "pest_meta" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42f0394d3123e33353ca5e1e89092e533d2cc490389f2bd6131c43c634ebc5f" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "pin-project" version = "1.1.3" @@ -1278,6 +1351,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + [[package]] name = "schannel" version = "0.1.22" @@ -1336,6 +1415,17 @@ dependencies = [ "syn 2.0.29", ] +[[package]] +name = "serde_json" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha2" version = "0.10.7" @@ -1660,6 +1750,12 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "unicode-bidi" version = "0.3.13" @@ -1726,11 +1822,13 @@ dependencies = [ "console", "dialoguer", "dirs", + "handlebars", "include_dir", "indicatif", "lazy_static", "lettre", "serde", + "serde_json", "sqlx", "tokio", "uuid", diff --git a/Cargo.toml b/Cargo.toml index d2ad292..e09355a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,11 +14,13 @@ clap = {version = "4.3.23", features = ["derive"]} console = "0.15.7" dialoguer = {version = "0.10.4", features = ["fuzzy-select", "completion"]} dirs = "5.0.1" +handlebars = "4.4.0" include_dir = "0.7.3" indicatif = "0.17.6" lazy_static = "1.4.0" -lettre = "0.10.4" +lettre = { version = "0.10.4", features = ["tokio1", "tokio1-native-tls"] } serde = {versio = "1.0.185", features = ["derive"]} +serde_json = "1.0.105" sqlx = {version = "0.6.2", features = ["runtime-tokio-native-tls", "sqlite"]} tokio = {version = "1.20.0", features = ["macros"]} uuid = {version = "1.4.1", features = ["v4", "fast-rng", "macro-diagnostics"]} diff --git a/src/assets/email.hbs b/src/assets/email.hbs deleted file mode 100644 index 6b7688a..0000000 --- a/src/assets/email.hbs +++ /dev/null @@ -1,20 +0,0 @@ -
Hi {{recipient}},
{{content}}

Best Regards,

Adeoye Adefemi

\ No newline at end of file diff --git a/src/assets/email.mjml b/src/assets/email.mjml index 3fc749b..869e591 100644 --- a/src/assets/email.mjml +++ b/src/assets/email.mjml @@ -1,16 +1,21 @@ - + + + - Hi {{recipient}}, + Hi {{recipient}}, - - {{content}} - + {{#each body}} + + {{this}} + + + {{/each}} @@ -26,6 +31,8 @@ - + + + \ No newline at end of file diff --git a/src/commands/email.rs b/src/commands/email.rs index d13c02d..6c65d15 100644 --- a/src/commands/email.rs +++ b/src/commands/email.rs @@ -3,8 +3,11 @@ use std::time::Duration; use clap::{Args, Subcommand}; use dialoguer::Confirm; use indicatif::{ProgressBar, ProgressStyle}; +use lettre::message::{header, MultiPart, SinglePart}; use serde::{Deserialize, Serialize}; +use serde_json::json; +use crate::ASSETS_DIR; // use crate::database::Database; use crate::style::PrintColoredText; use lettre::message::header::ContentType; @@ -13,9 +16,32 @@ use lettre::{Message, SmtpTransport, Transport}; #[derive(clap::Args, Debug, Serialize)] pub struct EmailCommands { + /// the email recipient + #[clap(short, long, value_parser)] + pub name: String, + /// the recipient email address + #[clap(short, long, value_parser)] + pub email: String, + /// the message content + #[clap(short, long, value_parser)] + pub subject: String, + /// body/content of the message + #[clap(short, long, value_parser)] + pub body: Vec, // /// the email history #[command(subcommand)] - pub subcommands: EmailSubCommands, + pub subcommands: Option, +} + +struct SendOptions { + /// the email recipient + pub name: String, + /// the recipient email address + pub email: String, + /// the message content + pub subject: String, + /// body/content of the message + pub body: Vec, // } /// Email History sub commands @@ -28,25 +54,6 @@ pub enum EmailSubCommands { Delete { id: String }, /// configure the SMTP parameters Config(ConfigOptions), - /// send email - New(SendOptions), -} - -/// utils email new -#[derive(Serialize, Deserialize, Debug, Clone, Args)] -pub struct SendOptions { - /// the email recipient - #[clap(short, long, value_parser)] - pub name: String, - /// the recipient email address - #[clap(short, long, value_parser)] - pub email: String, - /// the message content - #[clap(short, long, value_parser)] - pub subject: String, - /// body/content of the message - #[clap(short, long, value_parser)] - pub body: Vec, // } /// utils email config @@ -64,11 +71,24 @@ pub struct ConfigOptions { impl EmailCommands { /// parse the commands pub fn parse(&self) { - match &self.subcommands { - EmailSubCommands::History => self.list(), - EmailSubCommands::Delete { id } => self.delete(id), - EmailSubCommands::Config(config) => self.config(config), - EmailSubCommands::New(new) => self.send(new), + println!("{:?}", self); + + if self.subcommands.is_none() { + //send email + } + if let Some(command) = &self.subcommands { + match command { + EmailSubCommands::History => self.list(), + EmailSubCommands::Delete { id } => self.delete(id), + EmailSubCommands::Config(config) => self.config(config), + } + } else { + self.send(&SendOptions { + name: self.name.clone(), + email: self.email.clone(), + subject: self.subject.clone(), + body: self.body.clone(), + }) } } @@ -107,22 +127,60 @@ impl EmailCommands { .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏", "✓"]), ); pb.set_message("Please wait..."); - // send the email - let email = Message::builder() - .from("NoBody ".parse().unwrap()) - .reply_to("Yuin ".parse().unwrap()) - .to("Hei ".parse().unwrap()) - .subject("Happy new year") - .header(ContentType::TEXT_PLAIN) - .body(String::from("Be happy!")) - .unwrap(); + let recipient = format!("{}<{}>", data.name, data.email); + + // build the template + let mut email_body = handlebars::Handlebars::new(); + email_body + .register_template_file("email", "./templates/email.hbs") + .expect("error reading template file"); + let email_body = email_body.render( + "email", + &json!({"email":data.email, "body": data.body, "subject":data.subject, "recipient":data.name}), + ).ok(); + + let Some(html) = email_body else { + pb.finish_with_message( + "Oops! An error was encountered while rendering the email. PLease retry!", + ); + return; + }; - let creds = Credentials::new("smtp_username".to_owned(), "smtp_password".to_owned()); + let email = Message::builder() + .from("Adeoye Adefemi ".parse().unwrap()) + .reply_to("Adeoye Adefemi ".parse().unwrap()) + .to(recipient.parse().unwrap()) + .subject(&data.subject) + .multipart( + MultiPart::alternative() // This is composed of two parts. + .singlepart( + SinglePart::builder() + .header(header::ContentType::TEXT_PLAIN) + .body(String::from("Oops! An error was encountered while rendering the email. PLease retry!")), // Every message should have a plain text fallback. + ) + .singlepart( + SinglePart::builder() + .header(header::ContentType::TEXT_HTML) + .body(String::from(html)), + ), + ).ok(); + + let Some(email) = email else { + pb.finish_with_message( + "Oops! An error was encountered while parsing the email. Please retry!", + ); + return; + }; + + let credentials = Credentials::new( + "hey@gmail.com".to_owned(), + "hey".to_owned(), + ); // Open a remote connection to gmail let mailer = SmtpTransport::relay("smtp.gmail.com") .unwrap() - .credentials(creds) + .credentials(credentials) .build(); // Send the email @@ -131,7 +189,6 @@ impl EmailCommands { Ok(_) => pb.finish_with_message("Email successfully sent"), Err(e) => panic!("Could not send email: {e:?}"), } - // thread::sleep(Duration::from_secs(5)); } else { PrintColoredText::warning("termination...") } diff --git a/src/commands/gitignore.rs b/src/commands/gitignore.rs index a63e009..7910b51 100644 --- a/src/commands/gitignore.rs +++ b/src/commands/gitignore.rs @@ -208,8 +208,8 @@ impl GitIgnoreCommands { if let Some(file_content) = file_path { let mut file = File::create(path).unwrap(); file.write_all(file_content.contents()).unwrap(); - } - } + } + } } } diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..62efc0e --- /dev/null +++ b/src/config.rs @@ -0,0 +1,29 @@ +#[derive(Debug, Clone)] +pub struct Config { + pub smtp_host: String, + pub smtp_port: u16, + pub smtp_user: String, + pub smtp_pass: String, + pub smtp_from: String, + pub smtp_to: String, +} + +impl Config { + pub fn init() -> Config { + let smtp_host = std::env::var("SMTP_HOST").expect("SMTP_HOST must be set"); + let smtp_port = std::env::var("SMTP_PORT").expect("SMTP_PORT must be set"); + let smtp_user = std::env::var("SMTP_USER").expect("SMTP_USER must be set"); + let smtp_pass = std::env::var("SMTP_PASS").expect("SMTP_PASS must be set"); + let smtp_from = std::env::var("SMTP_FROM").expect("SMTP_FROM must be set"); + let smtp_to = std::env::var("SMTP_TO").expect("SMTP_TO must be set"); + + Config { + smtp_host, + smtp_pass, + smtp_user, + smtp_port: smtp_port.parse::().unwrap(), + smtp_from, + smtp_to, + } + } +} diff --git a/src/main.rs b/src/main.rs index 2ba3365..65ab933 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,8 @@ use lazy_static::lazy_static; use include_dir::{include_dir, Dir}; pub const SOURCE_DIR: Dir = include_dir!("src/templates"); +pub const ASSETS_DIR: Dir = include_dir!("src/assets"); + lazy_static! { pub static ref DB_URL: std::string::String = { //create "utils" directory in the home dir and / save files to $HOME/utils; @@ -14,18 +16,20 @@ lazy_static! { ); // create the path if not exist path if not exist let _ = std::fs::create_dir_all(&db_path); - format!("sqlite://{db_path}/utils.db") }; } mod commands; +mod config; mod database; mod parser; mod style; mod utils; - #[tokio::main] async fn main() { + // let src_path = "/email.hbs"; + // let file_path = ASSETS_DIR.path(); + // println!("{:?}", file_path); database::Database::init().await; parser::Utils::run().await; } diff --git a/src/parser.rs b/src/parser.rs index 7b834e4..0185f7b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -59,13 +59,13 @@ impl Utils { pub enum Commands { /// list stored data List, - /// remove remove stored data + /// remove stored data Remove { key: String }, /// store data as key value pair Store { key: String, value: String }, /// update data in the store Update { key: String, value: String }, - /// include .gitignore in a git repo + /// generate .gitignore Ignore(GitIgnoreCommands), /// download files, videos, etc Download(DownloadCommands), diff --git a/templates/email.hbs b/templates/email.hbs new file mode 100644 index 0000000..489f9bc --- /dev/null +++ b/templates/email.hbs @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+
+ +
+ + + + + + +
+ +
+ + + + + + + + + + + + +
+ + + + + + +
opeolluwa::maier
+
+
+ Hi {{recipient}},
+
+ {{#each body}} +
+ {{this}}
+ {{/each}} +
+
+
+ + + + + + +
+
+

Best Regards,

+

Adeoye Adefemi

+
+
+
+
+
+ +
+ + + + + + +
+ +
+
+
+ + + \ No newline at end of file