mission trip form now posts to nextcloud, skeleton of sqlx

This commit is contained in:
Chris Cochrun 2024-11-11 05:50:46 -06:00
parent f78e9ea162
commit 87c0d9cb01
5 changed files with 915 additions and 105 deletions

1
.envrc
View file

@ -1 +1,2 @@
DATABASE_URL=file://./data.db
use flake

819
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -30,3 +30,5 @@ tracing-appender = "0.2.3"
actix-files = "0.6.6"
tracing-actix-web = "0.7.14"
color-eyre = "0.6.3"
pretty_assertions = "1.4.1"
sqlx = { version = "0.8.2", features = ["sqlite"] }

View file

@ -1,12 +1,19 @@
use std::fs;
use std::{
collections::{BTreeMap, HashMap},
fs,
};
use actix_multipart::form::{tempfile::TempFile, text::Text, MultipartForm};
use actix_web::{post, HttpResponse};
use actix_web::{post, web, HttpResponse};
use color_eyre::{eyre::eyre, Result};
use lettre::{
message::{header::ContentType, Attachment, MultiPart, SinglePart},
Message,
};
use maud::{html, PreEscaped, DOCTYPE};
use reqwest::Client;
use serde_json::json;
use sqlx::SqliteConnection;
use tracing::{error, info};
#[derive(Debug, MultipartForm)]
@ -63,7 +70,51 @@ struct MtForm {
final_agreement: Text<String>,
registration: Text<String>,
#[multipart(rename = "image")]
file: TempFile,
file: Option<TempFile>,
}
impl From<&MtForm> for HashMap<i32, String> {
fn from(form: &MtForm) -> Self {
let mut map = HashMap::new();
map.insert(106, format!("{} {}", form.first_name.0, form.last_name.0));
map.insert(
107,
format!("{} {}", form.parent_first_name.0, form.parent_last_name.0),
);
map.insert(109, form.gender.0.clone());
map.insert(110, form.birthdate.0.clone());
map.insert(117, form.street.0.clone());
map.insert(118, form.city.0.clone());
map.insert(119, form.zip.0.to_string());
map.insert(120, form.state.0.clone());
map.insert(121, form.cellphone.0.clone());
map.insert(122, form.email.0.clone());
map.insert(123, form.parentphone.0.clone());
map.insert(124, form.parentemail.0.clone());
map.insert(125, form.school.0.clone());
map.insert(126, form.grade.0.clone());
map.insert(
127,
format!("{} {}", form.pastor_first_name.0, form.pastor_last_name.0),
);
map.insert(128, form.church_attendance.0.clone());
map.insert(129, form.tfc_group.0.clone());
map.insert(130, form.shirt.0.clone());
map.insert(131, form.trip.0.clone());
map.insert(132, form.trip_notes.0.clone());
map.insert(133, form.relationship_with_jesus.0.clone());
map.insert(134, form.testimony.0.clone());
map.insert(135, form.involvement_with_group.0.clone());
map.insert(136, form.reasons.0.clone());
map.insert(137, form.strengths.0.clone());
map.insert(138, form.weaknesses.0.clone());
map.insert(139, form.previous_trip_info.0.clone());
map.insert(140, form.attitude.0.clone());
map.insert(141, form.relevant_notes.0.clone());
map.insert(144, form.final_agreement.0.clone());
map.insert(145, form.registration.0.clone());
map
}
}
impl MtForm {
@ -216,6 +267,33 @@ impl MtForm {
}
}
}
async fn store_form(&self) -> Result<()> {
let client = Client::new();
let map = HashMap::from(self);
let mut json = HashMap::new();
dbg!(&map);
json.insert("data", map);
let res = client
.post("https://staff.tfcconnection.org/ocs/v2.php/apps/tables/api/2/tables/9/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?;
dbg!(&res);
if res.status().is_success() {
let res = res.text().await.unwrap();
dbg!(res);
Ok(())
} else {
Err(eyre!(
"Problem in storing data: {:?}",
res.error_for_status()
))
}
}
}
#[post("/api/mt-form")]
@ -229,22 +307,30 @@ pub async fn mt_form(MultipartForm(form): MultipartForm<MtForm>) -> HttpResponse
let mut path = String::from("");
let mut file_exists = false;
let mut filename = String::from("");
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);
}
info!("saving to {}", path);
match form.file.file.persist(&path) {
Ok(f) => {
if f.metadata().unwrap().len() > 0 {
file_exists = true;
}
info!(?f, "File saved successfully");
match form.store_form().await {
Ok(_) => info!("Successfully sent form to nextcloud!"),
Err(e) => error!("There was an erroring sending form to nextcloud: {}", e),
}
if let Some(file) = form.file {
if let Some(file_str) = file.file_name {
if let Some(ext) = file_str.rsplit('.').next() {
filename = format!("{}.{}", filename_noext, ext);
path = format!("./tmp/{}.{}", filename_noext, ext);
} else {
path = format!("./tmp/{}", file_str);
}
info!("saving to {}", path);
match file.file.persist(&path) {
Ok(f) => {
if f.metadata().unwrap().len() > 0 {
file_exists = true;
}
info!(?f, "File saved successfully");
}
Err(e) => info!("{:?}: Probably a missing image", e),
}
Err(e) => info!("{:?}: Probably a missing image", e),
}
}
@ -278,3 +364,68 @@ pub async fn mt_form(MultipartForm(form): MultipartForm<MtForm>) -> HttpResponse
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() -> MtForm {
MtForm {
first_name: Text(String::from("Frodo")),
last_name: Text(String::from("Braggins")),
parent_first_name: Text(String::from("Bilbo")),
parent_last_name: Text(String::from("Braggins")),
birthdate: Text(String::from("1845-09-12")),
gender: Text(String::from("male")),
street: Text(String::from("1234 Bag End")),
city: Text(String::from("The Shire")),
state: Text(String::from("Hobbiton")),
zip: Text(88888),
cellphone: Text(String::from("7868889797")),
parentphone: Text(String::from("1234567898")),
email: Text(String::from("frodo@hobbits.com")),
parentemail: Text(String::from("bilbo@hobbits.com")),
school: Text(String::from("Shire High")),
grade: Text(String::from("junior")),
pastor_first_name: Text(String::from("Gandalf")),
pastor_last_name: Text(String::from("The White")),
church_attendance: Text(String::from("often")),
tfc_group: Text(String::from("Northern Valley")),
shirt: Text(String::from("medium")),
trip: Text(String::from("Mordor")),
trip_notes: Text(String::from("If it must happen, I'll do it.")),
relationship_with_jesus: Text(String::from("Cool beans")),
testimony: Text(String::from("Nephew of Bilbo Braggins")),
involvement_with_group: Text(String::from("Good friends with Gandalf")),
reasons: Text(String::from("Want an adventure")),
strengths: Text(String::from("Willing, brave, small, and curious")),
weaknesses: Text(String::from("Not strong, or good with weapons")),
previous_trip_info: Text(String::from("The edge of Hob Hill")),
attitude: Text(String::from("Willing")),
relevant_notes: Text(String::from("Willing to take the ring")),
final_agreement: Text(String::from("yes")),
registration: Text(String::from("later")),
file: None,
}
}
#[test]
async fn test_nc_post() {
let form = form();
assert!(!form.first_name.is_empty());
dbg!(&form);
// let conn = SqliteConnection::connect("file://./data.db")
// .await
// .expect("Couldn't connect sqlite db");
let res = form.store_form().await;
match res {
Ok(_) => assert!(true, "passed storing test"),
Err(e) => assert!(false, "Failed storing test: {}", e),
}
}
}

View file

@ -5,7 +5,7 @@ use actix_files::Files;
use actix_multipart::form::tempfile::TempFileConfig;
use actix_web::body::MessageBody;
use actix_web::dev::{ServiceRequest, ServiceResponse};
use actix_web::{App, Error, HttpServer};
use actix_web::{web, App, Error, HttpServer};
use api::camp_form::camp_form;
use api::church_form::church_form;
use api::health_form::health_form;
@ -15,6 +15,7 @@ use api::parent_form::parent_form;
use api::teacher_form::teacher_form;
use color_eyre::eyre::Context;
use color_eyre::Result;
use sqlx::{Connection, SqliteConnection};
use tracing::level_filters::LevelFilter;
use tracing::{info, Span};
use tracing_actix_web::{RootSpanBuilder, TracingLogger};
@ -76,8 +77,14 @@ async fn main() -> std::io::Result<()> {
info!("starting HTTP server at http://localhost:4242");
HttpServer::new(|| {
let conn = SqliteConnection::connect("file://./data.db")
.await
.expect("Couldn't connect sqlite db");
let data = web::Data::new(conn);
HttpServer::new(move || {
App::new()
.app_data(data.clone())
.wrap(TracingLogger::<DomainRootSpanBuilder>::new())
.app_data(TempFileConfig::default().directory("./tmp"))
.service(mt_form)