use std::{ collections::{BTreeMap, HashMap}, fs, }; use actix_multipart::form::{tempfile::TempFile, text::Text, MultipartForm}; use actix_web::{post, web, HttpResponse}; use color_eyre::{eyre::eyre, Result}; use lettre::{ message::{header::ContentType, Attachment, MultiPart, SinglePart}, Message, }; use maud::{html, Markup, PreEscaped, DOCTYPE}; use reqwest::Client; use serde_json::json; use sqlx::SqliteConnection; use tracing::{error, info}; #[derive(Debug, MultipartForm)] struct MtTeacherForm { #[multipart(rename = "firstname")] first_name: Text, #[multipart(rename = "lastname")] last_name: Text, #[multipart(rename = "studentfirstname")] student_first_name: Text, #[multipart(rename = "studentlastname")] student_last_name: Text, relationship: Text, positive: Text, negative: Text, attitudes: Text, #[multipart(rename = "team-challenges")] challenges: Text, behavior: Text, #[multipart(rename = "extra-info")] extra_info: Text, } impl From<&MtTeacherForm> for HashMap { fn from(form: &MtTeacherForm) -> Self { let mut map = HashMap::new(); map.insert(150, format!("{} {}", form.first_name.0, form.last_name.0)); map.insert( 151, format!("{} {}", form.student_first_name.0, form.student_last_name.0), ); map.insert(152, form.relationship.0.clone()); map.insert(153, form.positive.0.clone()); map.insert(154, form.negative.0.clone()); map.insert(155, form.attitudes.0.clone()); map.insert(156, form.challenges.0.clone()); map.insert(157, form.extra_info.0.clone()); map.insert(177, form.behavior.0.clone()); map } } impl MtTeacherForm { async 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 teacher reference form for " (self.student_first_name.0) " " (self.student_last_name.0) "!" } 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 { "Teacher reference form for " (self.student_first_name.0) " " (self.student_last_name.0) "!" } hr; table { tr { th { "Name" } td { (self.first_name.0) " " (self.last_name.0) } } tr { th { "Student" } td { (self.student_first_name.0) " " (self.student_last_name.0) } } tr { th { "Relationship with student" } td { (self.relationship.0) } } tr { th { "Positive characteristics" } td { (self.positive.0) } } tr { th { "Negative characteristics" } td { (self.negative.0) } } tr { th { "Attitudes" } td { (self.attitudes.0) } } tr { th { "Teamwork" } td { (self.challenges.0) } } tr { th { "Behavior in school" } td { (self.behavior.0) } } tr { th { "Other Relevant Info" } td { (self.extra_info.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/12/row/757"#; let res = client .post("https://staff.tfcconnection.org/ocs/v2.php/apps/tables/api/2/tables/12/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() )) } } async fn send_email(&mut self) -> Result<()> { let first = self.student_first_name.clone(); let last = self.student_last_name.clone(); let email_subject = format!("Teacher reference form for {} {}!", first, last); info!("{first} {last} teacher reference form!"); let email = self.build_email().await; let email = SinglePart::html(email.into_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) .singlepart(email) { crate::email::send_email(m).await } else { Err(eyre!("Email incorrect")) } } } #[post("/api/mt-teacher-form")] pub async fn mt_teacher_form( MultipartForm(mut form): MultipartForm, ) -> HttpResponse { match form.store_form().await { Ok(_) => info!("Successfully sent form to nextcloud!"), Err(e) => error!("There was an erroring sending form to nextcloud: {e}"), } match form.send_email().await { Ok(_) => info!("Successfully sent email"), Err(e) => error!("There was an error sending the email: {e}"), } HttpResponse::Ok().body("thankyou") } #[cfg(test)] mod test { use actix_web::test; use pretty_assertions::assert_eq; use sqlx::Connection; use tracing::debug; use super::*; fn form() -> MtTeacherForm { MtTeacherForm { first_name: Text(String::from("Bilbo")), last_name: Text(String::from("Braggins")), student_first_name: Text(String::from("Frodo")), student_last_name: Text(String::from("Braggins")), relationship: Text(String::from("Uncle")), positive: Text(String::from("Nimble and brave")), negative: Text(String::from("Small")), attitudes: Text(String::from("Lighthearted")), challenges: Text(String::from("Willing")), behavior: Text(String::from("Good")), extra_info: Text(String::from("Willing to take the ring")), } } #[test] async fn test_nc_post() { let form = form(); assert!(!form.first_name.is_empty()); let res = form.store_form().await; match res { Ok(_) => assert!(true, "passed storing test"), Err(e) => assert!(false, "Failed storing test: {e}"), } } #[test] async fn test_email() { let mut form = form(); assert!(!form.first_name.is_empty()); match form.send_email().await { Ok(_) => assert!(true, "passed emailing test"), Err(e) => assert!(false, "Failed emailing test: {e}"), } } }