diff --git a/src/api/camp_form.rs b/src/api/camp_form.rs index df47cec..64e4f0e 100644 --- a/src/api/camp_form.rs +++ b/src/api/camp_form.rs @@ -11,6 +11,8 @@ use maud::html; use maud::DOCTYPE; use reqwest::{Client, Error}; +use crate::email::send_email; + #[derive(Debug, MultipartForm, Default)] struct CampForm { #[multipart(rename = "first-name")] @@ -258,22 +260,7 @@ pub async fn camp_form(MultipartForm(form): MultipartForm) -> HttpResp .subject(email_subject) .multipart(multi) { - let sender = SmtpTransport::relay("mail.tfcconnection.org") - .ok() - .unwrap() - .credentials(Credentials::new( - "no-reply@mail.tfcconnection.org".to_owned(), - "r9f36mNZFtiW4f".to_owned(), - )) - .authentication(vec![Mechanism::Plain]) - .build(); - match sender.send(&m) { - Ok(res) => log::info!( - "Successfully sent email to server with this response: {:?}", - res - ), - Err(e) => log::error!("{e}"), - } + let _ = send_email(m); } else { log::info!("Email incorrect"); } diff --git a/src/api/mt_form.rs b/src/api/mt_form.rs index 3ded04a..213fb5a 100644 --- a/src/api/mt_form.rs +++ b/src/api/mt_form.rs @@ -1,458 +1,269 @@ use std::fs; use actix_multipart::form::{tempfile::TempFile, text::Text, MultipartForm}; +use actix_rt::Runtime; use actix_web::{post, HttpResponse}; +use color_eyre::Result; use lettre::{ message::{header::ContentType, Attachment, MultiPart, SinglePart}, transport::smtp::authentication::{Credentials, Mechanism}, Message, SmtpTransport, Transport, }; +use markup::DynRender; +use maud::{html, PreEscaped, DOCTYPE}; +use tracing::{error, info, warn}; -#[derive(Debug, MultipartForm, Default)] +#[derive(Debug, MultipartForm)] struct MtForm { #[multipart(rename = "firstname")] - first_name: Option>, + first_name: Text, #[multipart(rename = "lastname")] - last_name: Option>, + last_name: Text, #[multipart(rename = "parentfirstname")] - parent_first_name: Option>, + parent_first_name: Text, #[multipart(rename = "parentlastname")] - parent_last_name: Option>, - birthdate: Option>, - gender: Option>, - street: Option>, - city: Option>, - state: Option>, - zip: Option>, - cellphone: Option>, - parentphone: Option>, - email: Option>, - parentemail: Option>, - school: Option>, - grade: Option>, + parent_last_name: Text, + birthdate: Text, + gender: Text, + street: Text, + city: Text, + state: Text, + zip: Text, + cellphone: Text, + parentphone: Text, + email: Text, + parentemail: Text, + school: Text, + grade: Text, #[multipart(rename = "pastorfirstname")] - pastor_first_name: Option>, + pastor_first_name: Text, #[multipart(rename = "pastorlastname")] - pastor_last_name: Option>, + pastor_last_name: Text, #[multipart(rename = "churchattendance")] - church_attendance: Option>, + church_attendance: Text, #[multipart(rename = "tfcgroup")] - tfc_group: Option>, - shirt: Option>, - trip: Option>, + tfc_group: Text, + shirt: Text, + trip: Text, #[multipart(rename = "tripnotes")] - trip_notes: Option>, + trip_notes: Text, #[multipart(rename = "relationship-with-jesus")] - relationship_with_jesus: Option>, + relationship_with_jesus: Text, #[multipart(rename = "testimony")] - testimony: Option>, + testimony: Text, #[multipart(rename = "involvement-with-group")] - involvement_with_group: Option>, + involvement_with_group: Text, #[multipart(rename = "reasons-for-trip-choice")] - reasons: Option>, - strengths: Option>, - weaknesses: Option>, + reasons: Text, + strengths: Text, + weaknesses: Text, #[multipart(rename = "previous-trip-info")] - previous_trip_info: Option>, + previous_trip_info: Text, #[multipart(rename = "attitude-torward-work")] - attitude: Option>, + attitude: Text, #[multipart(rename = "relevant-notes")] - relevant_notes: Option>, + relevant_notes: Text, #[multipart(rename = "final-agreement")] - final_agreement: Option>, - registration: Option>, + final_agreement: Text, + registration: Text, #[multipart(rename = "image")] - file: Option, + file: TempFile, +} + +impl MtForm { + async fn build_email(&self) -> PreEscaped { + html! { + (DOCTYPE) + meta charset="utf-8"; + html { + head { + title { (self.first_name.0) " " (self.last_name.0) " signed up for mission trip!" } + style { + "table { border-collapse: collapse; width: 100% }" + "td, th { padding: 8px }" + "td { text-align: left; width: 70%; word-wrap: break-word }" + "th { text-align: right; border-right: 1px solid #ddd }" + "tr { border-bottom: 1px solid #ddd }" + "h1 { text-align: center }" + } + } + body { + h1 { "Mission trip self for " (self.first_name.0) " " (self.last_name.0) "!" } + hr; + table { + tr { + th { "Name" } + td { (self.first_name.0) " " (self.last_name.0) } + } + tr { + th { "Parent" } + td { (self.parent_first_name.0) (self.parent_last_name.0) } + } + tr { + th { "Birthdate" } + td { (self.birthdate.0) } + } + tr { + th { "Gender" } + td { (self.gender.0) } + } + tr { + th { "Street" } + td { (self.street.0) } + } + tr { + th { "City" } + td { (self.city.0) } + } + tr { + th { "State" } + td { (self.state.0) } + } + tr { + th { "Zip" } + td { (self.zip.0) } + } + tr { + th { "Phone" } + td { (self.cellphone.0) } + } + tr { + th { "Parent Phone" } + td { (self.parentphone.0) } + } + tr { + th { "Email" } + td { (self.email.0) } + } + tr { + th { "Parent Email" } + td { (self.parentemail.0) } + } + tr { + th { "School" } + td { (self.school.0) } + } + tr { + th { "Grade" } + td { (self.grade.0) } + } + tr { + th { "Pastor" } + td { (self.pastor_first_name.0) (self.pastor_last_name.0) } + } + tr { + th { "Church Attendance" } + td { (self.church_attendance.0) } + } + tr { + th { "TFC Group" } + td { (self.tfc_group.0) } + } + tr { + th { "T-Shirt Size" } + td { (self.shirt.0) } + } + tr { + th { "Trip Choice" } + td { (self.trip.0) } + } + tr { + th { "Extra Trip Notes" } + td { (self.trip_notes.0) } + } + tr { + th { "Relationship with Jesus" } + td { (self.relationship_with_jesus.0) } + } + tr { + th { "Testimony" } + td { (self.testimony.0) } + } + tr { + th { "Involvement with TFC or Youth Group" } + td { (self.involvement_with_group.0) } + } + tr { + th { "Reasons for trip choice" } + td { (self.reasons.0) } + } + tr { + th { "Strengths" } + td { (self.strengths.0) } + } + tr { + th { "Weaknesses" } + td { (self.weaknesses.0) } + } + tr { + th { "Previous Trips" } + td { (self.previous_trip_info.0) } + } + tr { + th { "Attitude Torward Work" } + td { (self.attitude.0) } + } + tr { + th { "Other Relevant Info" } + td { (self.relevant_notes.0) } + } + tr { + th { "Final Agreement" } + td { (self.final_agreement.0) } + } + tr { + th { "Registration" } + td { (self.registration.0) } + } + } + } + } + } + } } #[post("/mt-form")] pub async fn mt_form(MultipartForm(form): MultipartForm) -> HttpResponse { - let first = form - .first_name - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let last = form - .last_name - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); + let first = form.first_name.clone(); + let last = form.last_name.clone(); let email_subject = format!("{} {} signed up for mission trip!", first, last); let filename_noext = format!("{}_{}", first, last); - let parent = format!( - "{} {}", - form.parent_first_name - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(), - form.parent_last_name - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone() - ); - let birthdate = form - .birthdate - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let gender = form - .gender - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let street = form - .street - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let city = form - .city - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let state = form - .state - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let zip = form.zip.as_ref().unwrap_or(&Text(0)).0; - let cellphone = form - .cellphone - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let parentphone = form - .parentphone - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let email = form - .email - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let parentemail = form - .parentemail - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let school = form - .school - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let grade = form - .grade - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let pastor = format!( - "{} {}", - form.pastor_first_name - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(), - form.pastor_last_name - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone() - ); - let church_attendance = form - .church_attendance - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let tfc_group = form - .tfc_group - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let shirt = form - .shirt - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let trip = form - .trip - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let trip_notes = form - .trip_notes - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let relationship = form - .relationship_with_jesus - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let testimony = form - .testimony - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let involvement = form - .involvement_with_group - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let reasons = form - .reasons - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let strengths = form - .strengths - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let weaknesses = form - .weaknesses - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let previous_trip = form - .previous_trip_info - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let attitude = form - .attitude - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let relevant = form - .relevant_notes - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let final_agreement = form - .final_agreement - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); - let registration = form - .registration - .as_ref() - .unwrap_or(&Text(String::from(""))) - .0 - .clone(); log::info!("{first} {last} signed up for mission trip!"); - let email = markup::new! { - @markup::doctype() - html { - head { - title { @format!("{} {} signed up for mission trip!", first, last) } - style { - "table { border-collapse: collapse; width: 100% }" - "td, th { padding: 8px }" - "td { text-align: left; width: 70%; word-wrap: break-word }" - "th { text-align: right; border-right: 1px solid #ddd }" - "tr { border-bottom: 1px solid #ddd }" - "h1 { text-align: center }" - } - } - body { - h1 { @format!("Mission trip form for {} {}!", first, last) } - hr; - table { - tr { - th { "Name" } - td { @format!("{} {}", first, last) } - } - tr { - th { "Parent" } - td { @parent } - } - tr { - th { "Birthdate" } - td { @birthdate } - } - tr { - th { "Gender" } - td { @gender } - } - tr { - th { "Street" } - td { @street } - } - tr { - th { "City" } - td { @city } - } - tr { - th { "State" } - td { @state } - } - tr { - th { "Zip" } - td { @zip } - } - tr { - th { "Phone" } - td { @cellphone } - } - tr { - th { "Parent Phone" } - td { @parentphone } - } - tr { - th { "Email" } - td { @email } - } - tr { - th { "Parent Email" } - td { @parentemail } - } - tr { - th { "School" } - td { @school } - } - tr { - th { "Grade" } - td { @grade } - } - tr { - th { "Pastor" } - td { @pastor } - } - tr { - th { "Church Attendance" } - td { @church_attendance } - } - tr { - th { "TFC Group" } - td { @tfc_group } - } - tr { - th { "T-Shirt Size" } - td { @shirt } - } - tr { - th { "Trip Choice" } - td { @trip } - } - tr { - th { "Extra Trip Notes" } - td { @trip_notes } - } - tr { - th { "Relationship with Jesus" } - td { @relationship } - } - tr { - th { "Testimony" } - td { @testimony } - } - tr { - th { "Involvement with TFC or Youth Group" } - td { @involvement } - } - tr { - th { "Reasons for trip choice" } - td { @reasons } - } - tr { - th { "Strengths" } - td { @strengths } - } - tr { - th { "Weaknesses" } - td { @weaknesses } - } - tr { - th { "Previous Trips" } - td { @previous_trip } - } - tr { - th { "Attitude Torward Work" } - td { @attitude } - } - tr { - th { "Other Relevant Info" } - td { @relevant } - } - tr { - th { "Final Agreement" } - td { @final_agreement } - } - tr { - th { "Registration" } - td { @registration } - } - } - } - } - }; - let mut path: Option = Some(String::from("")); + let email = form.build_email().await; + let mut path = String::from(""); let mut file_exists = false; let mut filename = String::from(""); - log::info!("{:?}", file_exists); - if let Some(f) = form.file { - if let Some(file) = f.file_name { - if let Some(ext) = file.rsplit('.').next() { - filename = format!("{}.{}", filename_noext, ext); - path = Some(format!("./tmp/{}.{}", filename_noext, ext)); - } else { - path = Some(format!("./tmp/{}", file)); - } - // let path = format!("./tmp/{}", file); - log::info!("saving to {}", path.clone().unwrap()); - match f.file.persist(path.clone().unwrap()) { - Ok(f) => { - log::info!("{:?}", f); - if f.metadata().unwrap().len() > 0 { - file_exists = true; - } + info!("{:?}", file_exists); + if let Some(file) = form.file.file_name { + if let Some(ext) = file.rsplit('.').next() { + filename = format!("{}.{}", filename_noext, ext); + path = format!("./tmp/{}.{}", filename_noext, ext); + } else { + path = format!("./tmp/{}", file); + } + // let path = format!("./tmp/{}", file); + info!("saving to {}", path); + match form.file.file.persist(&path) { + Ok(f) => { + info!("{:?}", f); + if f.metadata().unwrap().len() > 0 { + file_exists = true; } - Err(e) => log::info!("{:?}: Probably a missing image", e), } + Err(e) => log::info!("{:?}: Probably a missing image", e), } } let multi = if file_exists { - let filebody = fs::read(path.clone().unwrap_or_default()); + let filebody = fs::read(path); let content_type = ContentType::parse("image/jpg").unwrap(); let attachment = Attachment::new(filename).body(filebody.unwrap(), content_type); - log::info!("{:?}", attachment); + info!("{:?}", attachment); MultiPart::mixed() - .singlepart(SinglePart::html(email.to_string())) + .singlepart(SinglePart::html(email.into_string())) .singlepart(attachment) } else { - MultiPart::alternative_plain_html(String::from("Testing"), email.to_string()) + MultiPart::alternative_plain_html(String::from("Testing"), email.into_string()) }; if let Ok(m) = Message::builder() @@ -466,21 +277,9 @@ pub async fn mt_form(MultipartForm(form): MultipartForm) -> HttpResponse .subject(email_subject) .multipart(multi) { - let sender = SmtpTransport::relay("mail.tfcconnection.org") - .ok() - .unwrap() - .credentials(Credentials::new( - "no-reply@mail.tfcconnection.org".to_owned(), - "r9f36mNZFtiW4f".to_owned(), - )) - .authentication(vec![Mechanism::Plain]) - .build(); - match sender.send(&m) { - Ok(res) => log::info!("{:?}", res), - Err(e) => log::info!("{e}"), - } + let _ = crate::email::send_email(m).await; } else { - log::info!("Email incorrect"); + error!("Email incorrect"); } HttpResponse::Ok().body("thankyou") diff --git a/src/email.rs b/src/email.rs new file mode 100644 index 0000000..4ca21e7 --- /dev/null +++ b/src/email.rs @@ -0,0 +1,27 @@ +use color_eyre::Result; +use lettre::{ + transport::smtp::authentication::{Credentials, Mechanism}, + Message, SmtpTransport, Transport, +}; +use tracing::{error, info}; + +pub async fn send_email(message: Message) -> Result<()> { + let sender = SmtpTransport::relay("mail.tfcconnection.org") + .ok() + .unwrap() + .credentials(Credentials::new( + "no-reply@mail.tfcconnection.org".to_owned(), + "r9f36mNZFtiW4f".to_owned(), + )) + .authentication(vec![Mechanism::Plain]) + .build(); + + match sender.send(&message) { + Ok(res) => info!( + "Successfully sent email to server with this response: {:?}", + res + ), + Err(e) => error!("There was an error sending the email: {e}"), + } + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 9566677..cfe8c73 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ mod api; +pub mod email; use actix_files::Files; use actix_multipart::form::tempfile::TempFileConfig; @@ -28,7 +29,9 @@ impl RootSpanBuilder for DomainRootSpanBuilder { let info = request.connection_info(); let ip = info.realip_remote_addr().expect("hi"); let location = request.path(); - info!(?method, ip, location); + if location.ends_with("/") { + info!(?method, ip, location); + } tracing_actix_web::root_span!(request) // let client_id: &str = todo!("Somehow extract it from the authorization header");