tfcconnection/src/api/health_form.rs

457 lines
17 KiB
Rust

use std::{collections::HashMap, fs};
use actix_multipart::form::{tempfile::TempFile, text::Text, MultipartForm};
use actix_web::{post, HttpResponse};
use color_eyre::{
eyre::{eyre, Context},
Result,
};
use futures::FutureExt;
use lettre::{
message::{header::ContentType, Attachment, MultiPart, SinglePart},
Message,
};
use maud::{html, Markup, DOCTYPE};
use reqwest::Client;
use tracing::{error, info};
use crate::email::send_email;
#[derive(Debug, MultipartForm)]
struct HealthForm {
#[multipart(rename = "first-name")]
first_name: Text<String>,
#[multipart(rename = "last-name")]
last_name: Text<String>,
#[multipart(rename = "parent-first-name")]
parent_first_name: Text<String>,
#[multipart(rename = "parent-last-name")]
parent_last_name: Text<String>,
#[multipart(rename = "birth-date")]
birthdate: Text<String>,
street: Text<String>,
city: Text<String>,
state: Text<String>,
zip: Text<String>,
#[multipart(rename = "cell-phone")]
parent_cellphone: Text<String>,
#[multipart(rename = "home-phone")]
homephone: Text<String>,
#[multipart(rename = "additional-emergency-contact")]
contact: Text<String>,
#[multipart(rename = "additional-emergency-contact-phone")]
contact_phone: Text<String>,
#[multipart(rename = "doctor-name")]
doctorname: Text<String>,
#[multipart(rename = "doctor-city")]
doctorcity: Text<String>,
#[multipart(rename = "doctor-phone")]
doctorphone: Text<String>,
#[multipart(rename = "medical-coverage")]
medical: Text<String>,
#[multipart(rename = "insurance-name")]
insurance: Text<String>,
#[multipart(rename = "policy-number")]
policy_number: Text<String>,
allergies: Text<String>,
#[multipart(rename = "allergies-other")]
allergies_other: Text<String>,
#[multipart(rename = "specific-allergies")]
specific_allergies: Text<String>,
#[multipart(rename = "allergic-treatment")]
treatment: Text<String>,
conditions: Text<String>,
#[multipart(rename = "tetanus-shot")]
tetanus: Text<String>,
#[multipart(rename = "swimming-ability")]
swimming: Text<String>,
#[multipart(rename = "medication-schedule")]
medication: Text<String>,
#[multipart(rename = "other-notes")]
notes: Text<String>,
agreement: Text<String>,
#[multipart(rename = "image")]
file: Option<TempFile>,
registration: Text<String>,
}
impl From<&HealthForm> for HashMap<i32, String> {
fn from(form: &HealthForm) -> Self {
let mut map = HashMap::new();
map.insert(37, format!("{} {}", form.first_name.0, form.last_name.0));
map.insert(
38,
format!("{} {}", form.parent_first_name.0, form.parent_last_name.0),
);
map.insert(39, form.birthdate.0.clone());
map.insert(40, form.street.0.clone());
map.insert(41, form.city.0.clone());
map.insert(42, form.state.0.clone());
map.insert(43, form.zip.0.clone());
map.insert(44, form.parent_cellphone.0.clone());
map.insert(45, form.homephone.0.clone());
map.insert(46, format!("{} {}", form.contact.0, form.contact_phone.0));
map.insert(47, form.doctorname.0.clone());
map.insert(48, form.doctorcity.0.clone());
map.insert(49, form.doctorphone.0.clone());
map.insert(50, form.medical.0.clone());
map.insert(51, form.insurance.0.clone());
map.insert(52, form.policy_number.0.clone());
map.insert(54, form.agreement.0.clone());
map.insert(
55,
format!("{} \n {}", form.allergies.0, form.allergies_other.0),
);
map.insert(56, form.specific_allergies.0.clone());
map.insert(57, form.treatment.0.clone());
map.insert(58, form.conditions.0.clone());
map.insert(59, form.tetanus.0.clone());
map.insert(60, form.medication.0.clone());
map.insert(61, form.notes.0.clone());
map.insert(62, form.swimming.0.clone());
map
}
}
impl HealthForm {
fn build_email(&self) -> Markup {
html! {
(DOCTYPE)
meta charset="utf-8";
html {
head {
title { (self.first_name.0) " " (self.last_name.0) " filled out a health form!" }
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 { "Health form 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 { "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 { "Parent Cell Phone" }
td { (self.parent_cellphone.0) }
}
tr {
th { "Homephone" }
td { (self.homephone.0) }
}
tr {
th { "Additional Emergency Contact" }
td { (self.contact.0) }
}
tr {
th { "Emegency Contact Phone" }
td { (self.contact_phone.0) }
}
tr {
th { "Doctor" }
td { (self.doctorname.0) }
}
tr {
th { "Doctor City" }
td { (self.doctorcity.0) }
}
tr {
th { "Doctor Phone" }
td { (self.doctorphone.0) }
}
tr {
th { "Medical Coverage" }
td { (self.medical.0) }
}
tr {
th { "Insurance Provider" }
td { (self.insurance.0) }
}
tr {
th { "Policy Number" }
td { (self.policy_number.0) }
}
tr {
th { "Allergies" }
td { (self.allergies.0)
"\n\n"
(self.allergies_other.0)
}
}
tr {
th { "Specific Allergies" }
td { (self.specific_allergies.0) }
}
tr {
th { "Allergic Treatments" }
td { (self.treatment.0) }
}
tr {
th { "Conditions" }
td { (self.conditions.0) }
}
tr {
th { "Date of last Tetanus Shot" }
td { (self.tetanus.0) }
}
tr {
th { "Swimming Ability" }
td { (self.swimming.0) }
}
tr {
th { "Medication Schedule" }
td { (self.medication.0) }
}
tr {
th { "Other Notes" }
td { (self.notes.0) }
}
tr {
th { "Final Agreement" }
td { (self.agreement.0) }
}
tr {
th { "Registration" }
td { (self.registration.0) }
}
}
}
}
}
}
async fn store_form(&self) -> Result<()> {
let client = Client::new();
let map = HashMap::from(self);
let mut json = HashMap::new();
json.insert("data", map);
let link = r#"https://staff.tfcconnection.org/apps/tables/#/table/4/row/757"#;
let res = client
.post("https://staff.tfcconnection.org/ocs/v2.php/apps/tables/api/2/tables/4/rows")
.basic_auth("chris", Some("2VHeGxeC^Zf9KqFK^G@Pt!zu2q^6@b"))
.header("OCS-APIRequest", "true")
.header("Content-Type", "application/json")
.json(&json)
.send()
.await?;
if res.status().is_success() {
let res = res.text().await.unwrap();
Ok(())
} else {
Err(eyre!(
"Problem in storing data: {:?}",
res.error_for_status()
))
}
}
fn get_temp_file(&mut self) -> Option<(String, String, Option<String>)> {
let first = self.first_name.clone();
let last = self.last_name.clone();
let filename_noext = format!("{}_{}", first, last);
let (file_name, content_type) = if let Some(file) = self.file.as_ref() {
let content_type = file.content_type.clone().map(|m| m.to_string());
(file.file_name.to_owned(), content_type)
} else {
return None;
};
let filename;
let path = if let Some(file_name) = file_name {
if let Some(ext) = file_name.rsplit('.').next() {
filename = format!("{}.{}", filename_noext, ext);
format!("./tmp/{}.{}", filename_noext, ext)
} else {
filename = String::default();
format!("./tmp/{}", file_name)
}
} else {
filename = String::default();
String::default()
};
let file = self.file.take();
match file.unwrap().file.persist(path.clone()) {
Ok(f) => {
if f.metadata().unwrap().len() <= 0 {
return None;
}
info!(?f, "File saved successfully");
Some((filename, path, content_type.clone()))
}
Err(e) => {
error!("{:?}: Probably a missing image", e);
None
}
}
}
fn send_email(&mut self) -> Result<Message> {
let first = self.first_name.clone();
let last = self.last_name.clone();
let email_subject = format!("{} {} filled out a health form!", first, last);
info!("{first} {last} filled out a health form!");
let email = self.build_email();
let temp_file = self.get_temp_file();
let multi = if let Some((file, path, content_type)) = temp_file {
let filebody = fs::read(path);
let content_type =
ContentType::parse(&content_type.unwrap_or(String::from("image/jpg"))).unwrap();
let attachment = Attachment::new(file).body(filebody.unwrap(), content_type);
// info!(?attachment);
MultiPart::mixed()
.singlepart(SinglePart::html(email.into_string()))
.singlepart(attachment)
} else {
MultiPart::alternative_plain_html(String::from("Testing"), email.into_string())
};
Message::builder()
.from(
"TFC ADMIN <no-reply@mail.tfcconnection.org>"
.parse()
.unwrap(),
)
.to("Chris Cochrun <chris@tfcconnection.org>".parse().unwrap())
.to("Ethan Rose <ethan@tfcconnection.org>".parse().unwrap())
.subject(email_subject)
.multipart(multi)
.wrap_err("Email incorrect")
}
}
#[post("/api/health-form")]
pub async fn health_form(MultipartForm(mut form): MultipartForm<HealthForm>) -> HttpResponse {
info!("Starting health form work: {:?}", form);
match form.send_email() {
Ok(m) => {
actix_rt::spawn(send_email(m).map(|r| match r {
Ok(_) => info!("Email sent successfully"),
Err(e) => error!("There was an erroring sending form to nextcloud: {e}"),
}));
info!("Successfully sent email health form")
}
Err(e) => error!("There was an error sending email: {e}"),
}
let map = HashMap::from(&form);
actix_rt::spawn(store_form(map).map(|r| match r {
Ok(_) => {
info!("Successfully stored health form in nextcloud!")
}
Err(e) => error!("There was an error storing form in nextcloud: {e}"),
}));
let full_name = format!("{} {}", form.first_name.0, form.last_name.0);
match form.registration.0.as_str() {
"now" => {
info!("Sending them to pay for registration now");
HttpResponse::Ok()
.insert_header(("Access-Control-Expose-Headers", "*"))
.insert_header((
"HX-Redirect",
"https://secure.myvanco.com/L-Z772/campaign/C-13DM3",
))
.finish()
}
"full" => {
info!("Sending them to pay for the full registration now");
HttpResponse::Ok()
.insert_header(("Access-Control-Expose-Headers", "*"))
.insert_header((
"HX-Redirect",
"https://secure.myvanco.com/L-Z772/campaign/C-13JQE",
))
.finish()
}
"later" => {
info!("{} would like to pay later", full_name);
let html = html! {
div class="mt-8" {
h2 {
"Thank you, " (full_name) "!"
}
p {
class { "" }
"If you'd like to pay for your registration go to the donate tab in the top right when you are ready and find the right registration option."
}
}
};
HttpResponse::Ok().body(html.into_string())
}
_ => {
log::warn!(
"Got registration error possibly. Here is what the registration was: {}",
form.registration.0.as_str()
);
let html = html! {
div class="mt-8" {
h2 {
"Thank you, " (full_name) "!"
}
p {
class { "" }
"If you filled this out for camp or mission trip you can pay for your registration at the donate tab in the top right when you are ready and find the camp or mission trip registration option."
}
}
};
HttpResponse::Ok().body(html.into_string())
}
}
// HttpResponse::Ok().body("hi")
}
async fn store_form(map: HashMap<i32, String>) -> Result<()> {
let client = Client::new();
let mut json = HashMap::new();
json.insert("data", map);
let res = client
.post("https://staff.tfcconnection.org/ocs/v2.php/apps/tables/api/2/tables/4/rows")
.basic_auth("chris", Some("2VHeGxeC^Zf9KqFK^G@Pt!zu2q^6@b"))
.header("OCS-APIRequest", "true")
.header("Content-Type", "application/json")
.json(&json)
.send()
.await?;
if res.status().is_success() {
let res = res.text().await.unwrap();
Ok(())
} else {
Err(eyre!(
"Problem in storing data: {:?}",
res.error_for_status()
))
}
}