From 65f186fadce68e5410f3c206780771ce8a2590db Mon Sep 17 00:00:00 2001 From: Chris Cochrun Date: Mon, 8 Jan 2024 14:12:29 -0600 Subject: [PATCH] adding the health form api and validation --- layouts/shortcodes/health-form.html | 15 +- src/api/health_form.rs | 477 ++++++++++++++++++++++++++++ src/api/mod.rs | 1 + src/api/mt_form.rs | 2 +- src/main.rs | 2 + 5 files changed, 491 insertions(+), 6 deletions(-) create mode 100644 src/api/health_form.rs diff --git a/layouts/shortcodes/health-form.html b/layouts/shortcodes/health-form.html index 3fd6ff4..c9da5fd 100644 --- a/layouts/shortcodes/health-form.html +++ b/layouts/shortcodes/health-form.html @@ -48,6 +48,7 @@ document.getElementById('warning').style.visibility = 'visible'; document.getElementById('warning').style.height = ''; document.getElementById('warning').style.margin = ''; + return false; } }; @@ -62,15 +63,17 @@ data.append("age", age); /* data.delete("image"); */ - validate(data); + if (!validate(data)) { + return + }; let obj = {}; data.forEach((value, key) => obj[key] = value); // For use in dev // Can now start using this in production IF, // I get the server running on the server - let base = "https://api.tfcconnection.org/health-form"; - /* let base = "http://localhost:4242/health-form"; */ + /* let base = "https://api.tfcconnection.org/health-form"; */ + let base = "http://localhost:4242/health-form"; fetch(base, { method: "POST", body: data @@ -326,13 +329,15 @@
+ class="flex-none form-input {{ $formClasses }} checked" + required>
+ class="flex-none form-input {{ $formClasses }} " + checked>
diff --git a/src/api/health_form.rs b/src/api/health_form.rs new file mode 100644 index 0000000..56dc61a --- /dev/null +++ b/src/api/health_form.rs @@ -0,0 +1,477 @@ +use std::fs; + +use actix_multipart::form::{tempfile::TempFile, text::Text, MultipartForm}; +use actix_web::{post, HttpResponse}; +use lettre::{ + message::{header::ContentType, Attachment, MultiPart}, + transport::smtp::authentication::{Credentials, Mechanism}, + Message, SmtpTransport, Transport, +}; + +#[derive(Debug, MultipartForm, Default)] +struct HealthForm { + #[multipart(rename = "firstname")] + first_name: Option>, + #[multipart(rename = "lastname")] + last_name: Option>, + #[multipart(rename = "parentfirstname")] + parent_first_name: Option>, + #[multipart(rename = "parentlastname")] + parent_last_name: Option>, + birthdate: Option>, + gender: Option>, + street: Option>, + city: Option>, + state: Option>, + zip: Option>, + #[multipart(rename = "cellphone")] + parent_cellphone: Option>, + homephone: Option>, + #[multipart(rename = "add-emergency-contact")] + contact: Option>, + #[multipart(rename = "add-emergency-contact-phone")] + contact_phone: Option>, + doctorname: Option>, + doctorcity: Option>, + doctorphone: Option>, + #[multipart(rename = "medical-coverage")] + medical: Option>, + #[multipart(rename = "insurance-name")] + insurance: Option>, + #[multipart(rename = "policy-number")] + policy_number: Option>, + allergies: Option>, + #[multipart(rename = "allergies-other")] + allergies_other: Option>, + #[multipart(rename = "specific-allergies")] + specific_allergies: Option>, + #[multipart(rename = "allergic-treatment")] + treatment: Option>, + conditions: Option>, + #[multipart(rename = "tetanus-shot")] + tetanus: Option>, + #[multipart(rename = "swimming-ability")] + swimming: Option>, + #[multipart(rename = "medication-schedule")] + medication: Option>, + #[multipart(rename = "other-notes")] + notes: Option>, + agreement: Option>, + #[multipart(rename = "image")] + file: Option, +} + +#[post("/health-form")] +pub async fn health_form(MultipartForm(form): MultipartForm) -> HttpResponse { + let first = form.first_name.as_ref().unwrap().0.clone(); + let last = form.last_name.as_ref().unwrap().0.clone(); + let email_subject = format!("{} {} filled out a health form!", first, last); + let filename_noext = String::from(format!("{}_{}", first, last)); + let parent = format!( + "{} {}", + form.parent_first_name.as_ref().unwrap().0.clone(), + form.parent_last_name.as_ref().unwrap().0.clone() + ); + let birthdate = form + .birthdate + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let gender = form + .gender + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let street = form + .street + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let city = form + .city + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let state = form + .state + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let zip = form.zip.as_ref().unwrap_or(&Text { 0: 0 }).0.clone(); + let parent_cellphone = form + .parent_cellphone + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let homephone = form + .homephone + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let contact = form + .contact + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let contact_phone = form + .contact_phone + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let doctorname = form + .doctorname + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let doctorcity = form + .doctorcity + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let doctorphone = form + .doctorphone + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let medical = form + .medical + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let insurance = form + .insurance + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let policy_number = form + .policy_number + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let agreement = form + .agreement + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let allergies = form + .allergies + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let allergies_other = form + .allergies_other + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let specific_allergies = form + .specific_allergies + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let treatment = form + .treatment + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let conditions = form + .conditions + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let tetanus = form + .tetanus + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let swimming = form + .swimming + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let medication = form + .medication + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + let notes = form + .notes + .as_ref() + .unwrap_or(&Text { + 0: String::from(""), + }) + .0 + .clone(); + log::info!("{first} {last} filled out a health form!"); + let email = markup::new! { + @markup::doctype() + html { + head { + title { @format!("{} {} filled out a health form!", 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!("Health 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 { @parent_cellphone } + } + tr { + th { "Home Phone" } + td { @homephone } + } + tr { + th { "Additional Emergency Contact" } + td { @contact } + } + tr { + th { "Contact Phone" } + td { @contact_phone } + } + tr { + th { "Doctor" } + td { @doctorname } + } + tr { + th { "Doctor City" } + td { @doctorcity } + } + tr { + th { "Doctor Phone" } + td { @doctorphone } + } + tr { + th { "Medical Coverage" } + td { @medical } + } + tr { + th { "Insurance Provider" } + td { @insurance } + } + tr { + th { "Policy Number" } + td { @policy_number } + } + tr { + th { "Allergies" } + td { @allergies } + } + tr { + th { "Other Allergies" } + td { @allergies_other } + } + tr { + th { "Specific Allergies" } + td { @specific_allergies } + } + tr { + th { "Treatments" } + td { @treatment } + } + tr { + th { "Physical or mental conditions" } + td { @conditions } + } + tr { + th { "Last tetanus shot" } + td { @tetanus } + } + tr { + th { "Swimming Ability" } + td { @swimming } + } + tr { + th { "Medication Schedule" } + td { @medication } + } + tr { + th { "Other Relevant Info" } + td { @notes } + } + tr { + th { "Final Agreement" } + td { @agreement } + } + } + } + } + }; + let mut path: Option = Some(String::from("")); + let mut file_exists = false; + log::info!("{:?}", form); + log::info!("{:?}", file_exists); + if let Some(f) = form.file { + if let Some(file) = f.file_name { + if let Some(ext) = file.rsplit(".").next() { + 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; + } + } + Err(e) => log::info!("{:?}: Probably a missing image", e), + } + } + } + + let multi = if file_exists { + let filebody = fs::read(path.clone().unwrap_or_default()); + let content_type = ContentType::parse("image/jpg").unwrap(); + let attachment = + Attachment::new(path.unwrap_or_default()).body(filebody.unwrap(), content_type); + log::info!("{:?}", attachment); + MultiPart::alternative_plain_html(String::from("Testing"), email.to_string()) + .singlepart(attachment) + } else { + MultiPart::alternative_plain_html(String::from("Testing"), email.to_string()) + }; + + if let Ok(m) = Message::builder() + .from( + "TFC ADMIN " + .parse() + .unwrap(), + ) + .to("Chris Cochrun ".parse().unwrap()) + // .to("Ethan Rose ".parse().unwrap()) + .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}"), + } + } else { + log::info!("Email incorrect"); + } + + HttpResponse::Ok().body("hi") +} diff --git a/src/api/mod.rs b/src/api/mod.rs index 98fecb8..3ac1875 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1 +1,2 @@ +pub mod health_form; pub mod mt_form; diff --git a/src/api/mt_form.rs b/src/api/mt_form.rs index 2d2a74b..083e4ff 100644 --- a/src/api/mt_form.rs +++ b/src/api/mt_form.rs @@ -522,5 +522,5 @@ pub async fn mt_form(MultipartForm(form): MultipartForm) -> HttpResponse log::info!("Email incorrect"); } - HttpResponse::Ok().body("hi") + HttpResponse::Ok().body("thankyou") } diff --git a/src/main.rs b/src/main.rs index 5fa453d..a0aa1ba 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod api; use actix_multipart::form::tempfile::TempFileConfig; use actix_web::{middleware, App, HttpServer}; +use api::health_form::health_form; use api::mt_form::mt_form; #[actix_web::main] @@ -18,6 +19,7 @@ async fn main() -> std::io::Result<()> { .wrap(middleware::Logger::default()) .app_data(TempFileConfig::default().directory("./tmp")) .service(mt_form) + .service(health_form) }) .bind(("127.0.0.1", 4242))? .workers(2)