fixing up the api to work better for nix and move the table format

This commit is contained in:
Chris Cochrun 2025-06-23 15:13:42 -05:00
parent 774328aa5b
commit ee252aec13
7 changed files with 305 additions and 96 deletions

View file

@ -6,8 +6,8 @@ use color_eyre::eyre::{Context, Result};
use futures::FutureExt;
use lettre::{message::SinglePart, Message};
use maud::{html, Markup, DOCTYPE};
use reqwest::Client;
use tracing::{error, info};
use reqwest::{Client, Response};
use tracing::{debug, error, info};
#[derive(Debug, MultipartForm)]
struct CampForm {
@ -39,27 +39,30 @@ struct CampForm {
health_form: Text<String>,
}
impl From<&CampForm> for HashMap<i32, String> {
impl From<&CampForm> for HashMap<&str, String> {
fn from(form: &CampForm) -> Self {
let mut map = HashMap::new();
map.insert(63, format!("{} {}", form.first_name.0, form.last_name.0));
map.insert(
64,
"Full_Name",
format!("{} {}", form.first_name.0, form.last_name.0),
);
map.insert(
"Parent_Name",
format!("{} {}", form.parent_first_name.0, form.parent_last_name.0),
);
map.insert(65, form.parent_phone.0.clone());
map.insert(66, form.parent_email.0.clone().clone());
map.insert(67, form.birthdate.0.clone());
map.insert(69, form.gender.0.clone());
map.insert(70, form.street.0.clone());
map.insert(71, form.city.0.clone());
map.insert(72, form.state.0.clone());
map.insert(73, form.zip.0.clone().to_string());
map.insert(74, form.grade.0.clone());
map.insert(75, form.week.0.clone());
map.insert(76, form.shirt.0.clone());
map.insert(77, form.registration.0.clone());
map.insert(115, form.health_form.0.clone());
map.insert("Parent_Phone", form.parent_phone.0.clone());
map.insert("Parent_Email", form.parent_email.0.clone().clone());
map.insert("Birth_Date", form.birthdate.0.clone());
map.insert("Gender", form.gender.0.clone());
map.insert("Street_Address", form.street.0.clone());
map.insert("City", form.city.0.clone());
map.insert("State", form.state.0.clone());
map.insert("Zipcode", form.zip.0.clone().to_string());
map.insert("Grade", form.grade.0.clone());
map.insert("Week_Chosen", form.week.0.clone());
map.insert("Shirt_Size", form.shirt.0.clone());
map.insert("Registration", form.registration.0.clone());
map.insert("Health_Form", form.health_form.0.clone());
map
}
}
@ -292,19 +295,21 @@ pub async fn camp_form(MultipartForm(form): MultipartForm<CampForm>) -> HttpResp
}
}
async fn store_camp_form(map: HashMap<i32, String>) -> Result<()> {
async fn store_camp_form(map: HashMap<&str, String>) -> Result<Response> {
let request = Client::new();
let mut json = HashMap::new();
json.insert("data", map);
request
.post("https://staff.tfcconnection.org/apps/tables/api/1/tables/5/rows")
.basic_auth("chris", Some("2VHeGxeC^Zf9KqFK^G@Pt!zu2q^6@b"))
.header("OCS-APIRequest", "true")
let mut fields = HashMap::new();
fields.insert("fields", map);
json.insert("records", vec![fields]);
let response = request
.post("https://table.tfcconnection.org/api/docs/e8SFoTHpmJuFQsiMhRTXCi/tables/Camp_Data/records")
.bearer_auth("b8189d1b315548aa610db2fd3a43177a967cd41f")
.header("Content-Type", "application/json")
.json(&json)
.send()
.await?;
Ok(())
debug!(?response);
Ok(response)
}
#[cfg(test)]
@ -329,7 +334,7 @@ mod test {
grade: Text(String::from("junior")),
shirt: Text(String::from("medium")),
allergies: Text(String::from("Cool beans")),
week: Text(String::from("1")),
week: Text(String::from("week1")),
registration: Text(String::from("later")),
health_form: Text(String::from("I guess")),
}
@ -342,7 +347,7 @@ mod test {
let map = HashMap::from(&form);
let res = store_camp_form(map).await;
match res {
Ok(_) => assert!(true),
Ok(r) => assert!(r.status().is_success(), "Failed to store: {:?}", r),
Err(e) => assert!(false, "Failed storing test: {e}"),
}
}

View file

@ -12,7 +12,7 @@ use lettre::{
Message,
};
use maud::{html, Markup, DOCTYPE};
use reqwest::Client;
use reqwest::{Client, Response};
use tracing::{error, info};
use crate::email::send_email;
@ -75,40 +75,46 @@ struct HealthForm {
registration: Text<String>,
}
impl From<&HealthForm> for HashMap<i32, String> {
impl From<&HealthForm> for HashMap<&str, 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,
"Student_Name",
format!("{} {}", form.first_name.0, form.last_name.0),
);
map.insert(
"Parent_Name",
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("Birthdate", form.birthdate.0.clone());
map.insert("Street", form.street.0.clone());
map.insert("City", form.city.0.clone());
map.insert("State", form.state.0.clone());
map.insert("Zipcode", form.zip.0.clone());
map.insert("Parent_Phone", form.parent_cellphone.0.clone());
map.insert("Homephone", form.homephone.0.clone());
map.insert(
55,
"Emergency_Contact",
format!("{} {}", form.contact.0, form.contact_phone.0),
);
map.insert("Doctor", form.doctorname.0.clone());
map.insert("Doctor_City", form.doctorcity.0.clone());
map.insert("Doctor_Phone", form.doctorphone.0.clone());
map.insert("Medical_Coverage", form.medical.0.clone());
map.insert("Insurance_Name", form.insurance.0.clone());
map.insert("Policy_Number", form.policy_number.0.clone());
map.insert("Agreement", form.agreement.0.clone());
map.insert(
"Allergies",
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.insert("Specific_Allergies", form.specific_allergies.0.clone());
map.insert("Allergic_Treatments", form.treatment.0.clone());
map.insert("Conditions", form.conditions.0.clone());
map.insert("Tetanus_Shot_Date", form.tetanus.0.clone());
map.insert("Medication_Schedule", form.medication.0.clone());
map.insert("Other_Notes", form.notes.0.clone());
map.insert("Swimming_Ability", form.swimming.0.clone());
map
}
}
@ -431,22 +437,22 @@ pub async fn health_form(MultipartForm(mut form): MultipartForm<HealthForm>) ->
// HttpResponse::Ok().body("hi")
}
async fn store_form(map: HashMap<i32, String>) -> Result<()> {
async fn store_form(map: HashMap<&str, String>) -> Result<Response> {
let client = Client::new();
let mut json = HashMap::new();
json.insert("data", map);
let mut fields = HashMap::new();
fields.insert("fields", map);
json.insert("records", vec![fields]);
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")
.post("https://table.tfcconnection.org/api/docs/e8SFoTHpmJuFQsiMhRTXCi/tables/Health_Data/records")
.bearer_auth("b8189d1b315548aa610db2fd3a43177a967cd41f")
.header("Content-Type", "application/json")
.json(&json)
.send()
.await?;
if res.status().is_success() {
let res = res.text().await.unwrap();
Ok(())
Ok(res)
} else {
Err(eyre!(
"Problem in storing data: {:?}",
@ -454,3 +460,69 @@ async fn store_form(map: HashMap<i32, String>) -> Result<()> {
))
}
}
#[cfg(test)]
mod test {
use super::*;
use actix_web::test;
fn form() -> HealthForm {
HealthForm {
first_name: Text("Frodo".into()),
last_name: Text("Braggins".into()),
parent_first_name: Text("Bilbo".into()),
parent_last_name: Text("Braggins".into()),
birthdate: Text(String::from("1845-09-12")),
street: Text(String::from("1234 Bag End")),
city: Text(String::from("The Shire")),
state: Text(String::from("Hobbiton")),
zip: Text(String::from("88888")),
allergies: Text(String::from("Cool beans")),
registration: Text(String::from("later")),
parent_cellphone: Text(String::from("later")),
homephone: Text("test".into()),
contact: Text("test".into()),
contact_phone: Text("test".into()),
doctorname: Text("test".into()),
doctorcity: Text("test".into()),
doctorphone: Text("test".into()),
medical: Text("test".into()),
insurance: Text("test".into()),
policy_number: Text("test".into()),
allergies_other: Text("test".into()),
specific_allergies: Text("test".into()),
treatment: Text("test".into()),
conditions: Text("test".into()),
tetanus: Text("test".into()),
swimming: Text("test".into()),
medication: Text("test".into()),
notes: Text("test".into()),
agreement: Text("test".into()),
file: None,
}
}
#[test]
async fn test_nc_post() {
let form = form();
assert!(!form.first_name.is_empty());
let map = HashMap::from(&form);
let res = store_form(map).await;
match res {
Ok(r) => assert!(r.status().is_success(), "Failed to store: {:?}", r),
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() {
Ok(m) => {
assert!(crate::email::send_email(m).await.is_ok())
}
Err(e) => assert!(false, "Failed emailing test: {e}"),
}
}
}

View file

@ -7,7 +7,7 @@ use actix_web::body::MessageBody;
use actix_web::dev::{ServiceRequest, ServiceResponse};
use actix_web::{web, App, Error, HttpServer};
use api::camp_form::camp_form;
use api::contact::{self, contact_form};
use api::contact::contact_form;
use api::health_form::health_form;
use api::local_trip_form::local_form;
use api::mt_form::mt_form;
@ -15,6 +15,7 @@ use api::{
mt_church_form::mt_church_form, mt_parent_form::mt_parent_form,
mt_teacher_form::mt_teacher_form,
};
use clap::Parser;
use color_eyre::eyre::Context;
use color_eyre::Result;
use sqlx::{Connection, SqliteConnection};
@ -45,45 +46,64 @@ impl RootSpanBuilder for DomainRootSpanBuilder {
fn on_request_end<B: MessageBody>(_span: Span, _outcome: &Result<ServiceResponse<B>, Error>) {}
}
#[derive(Debug, Parser)]
#[command(name = "tfcsite", version, about)]
struct Cli {
#[arg(short, long)]
dev: bool,
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let site;
let temp;
let logs;
if Cli::parse().dev {
site = "./public";
logs = "/tmp/tfcsite";
temp = "/tmp/tfcsite";
} else {
site = "../public";
logs = "/storage/logs/tfcsite";
temp = "/tmp/tfcsite";
}
std::fs::create_dir_all(&logs)?;
std::fs::create_dir_all(&temp)?;
let timer =
tracing_subscriber::fmt::time::ChronoLocal::new("%Y-%m-%d_%I:%M:%S%.6f %P".to_owned());
let logfile = RollingFileAppender::builder()
.rotation(Rotation::DAILY)
.filename_prefix("api")
.filename_suffix("log")
.build(if DEV_MODE {
"/tmp/tfcsite"
} else {
"/storage/logs/tfcsite"
})
.build(&logs)
.expect("Shouldn't");
let filter = EnvFilter::builder()
let file_filter = EnvFilter::builder()
.with_default_directive(LevelFilter::WARN.into())
.parse_lossy("tfcapi=debug");
.parse_lossy("tfcsite=debug");
let logfile_layer = tracing_subscriber::fmt::layer()
.with_writer(logfile)
.with_line_number(true)
.with_level(true)
.with_target(true)
.with_ansi(false)
.with_timer(timer.clone());
.with_timer(timer.clone())
.with_filter(file_filter);
let stdout_filter = EnvFilter::builder()
.with_default_directive(LevelFilter::WARN.into())
.parse_lossy("tfcsite=debug");
let stdout_layer = tracing_subscriber::fmt::layer()
.pretty()
.with_line_number(true)
.with_target(true)
.with_timer(timer)
.with_filter(filter);
let filter = EnvFilter::builder()
.with_default_directive(LevelFilter::WARN.into())
.parse_lossy("tfcapi=debug");
let subscriber = tracing_subscriber::registry()
.with(logfile_layer.with_filter(filter).and_then(stdout_layer));
let _ = tracing::subscriber::set_global_default(subscriber).wrap_err("Tracing broked");
.with_filter(stdout_filter);
std::fs::create_dir_all("/tmp/tfcsite")?;
let subscriber = tracing_subscriber::registry().with(logfile_layer.and_then(stdout_layer));
let _ = tracing::subscriber::set_global_default(subscriber).wrap_err("Tracing broked");
info!("starting HTTP server at http://localhost:4242");
@ -96,11 +116,7 @@ async fn main() -> std::io::Result<()> {
App::new()
.app_data(data.clone())
.wrap(TracingLogger::<DomainRootSpanBuilder>::new())
.app_data(TempFileConfig::default().directory(if DEV_MODE {
"/tmp/tfcsite"
} else {
"/storage/logs/tfcsite"
}))
.app_data(TempFileConfig::default().directory(&temp))
.service(mt_form)
.service(health_form)
.service(mt_parent_form)
@ -109,7 +125,7 @@ async fn main() -> std::io::Result<()> {
.service(local_form)
.service(camp_form)
.service(contact_form)
.service(Files::new("/", "./public").index_file("index.html"))
.service(Files::new("/", &site).index_file("index.html"))
})
.bind(("localhost", 4242))?
.workers(4)