use crate::helloasso; use crate::paheko; use crate::{ Config, UserCache, get_proxy_from_url, get_auth_client_from_cache, parse_and_get_birthday_year, parse_normalize_phone, normalize_str }; use crate::sync_paheko::{GeneralizedAnswer, sync_paheko}; use anyhow::Result; use url::Url; /// rust how to access inner enum value #[derive(Debug, PartialEq, Clone, Copy)] enum HelloassoCustomFieldType { Email, Address, PostalCode, City, Phone, Job, Skills, Birthday, LinkedUserFirstName } impl TryFrom<&str> for HelloassoCustomFieldType { type Error = (); fn try_from(subject: &str) -> Result<Self, Self::Error> { match subject { "Prénom conjoint" => Ok(HelloassoCustomFieldType::LinkedUserFirstName), "ADRESSE" => Ok(HelloassoCustomFieldType::Address), "CODE POSTAL" => Ok(HelloassoCustomFieldType::PostalCode), "VILLE" => Ok(HelloassoCustomFieldType::City), "EMAIL" => Ok(HelloassoCustomFieldType::Email), "PROFESSION" => Ok(HelloassoCustomFieldType::Job), "TÉLÉPHONE" => Ok(HelloassoCustomFieldType::Phone), "DATE DE NAISSANCE" => Ok(HelloassoCustomFieldType::Birthday), "CENTRE D'INTÉRÊTS / COMPÉTENCES" => Ok(HelloassoCustomFieldType::Skills), _ => Err(()) } } } fn read_custom_field(form_answer: &helloasso::FormAnswer, custom_field: HelloassoCustomFieldType) -> Option<String> { // FIXME: compute the type directly at deserialization with serde form_answer.custom_fields.iter() .find(|f| HelloassoCustomFieldType::try_from(f.name.as_str()) == Ok(custom_field)) .map(|cf| cf.answer.clone()) } pub async fn sync_helloasso(paheko_client: &paheko::AuthentifiedClient, config: &Config, user_cache: &mut UserCache) -> Result<()> { let mut ha_client: helloasso::Client = helloasso::Client::new(helloasso::ClientConfig { base_url: Url::parse("https://api.helloasso.com/v5/") .expect("Expected valid helloasso API base URL"), proxy: get_proxy_from_url(&config.helloasso_proxy)?, user_agent: "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/112.0".to_string() }); let login_payload = helloasso::LoginPayload { email: config.helloasso_email.clone(), password: config.helloasso_password.clone() }; let auth_client: helloasso::AuthentifiedClient = get_auth_client_from_cache(user_cache, &mut ha_client, login_payload).await?; let org = auth_client.organization(&config.helloasso_organization_slug); let answers = org.get_form_answers(&config.helloasso_form_name).await?; println!("Got {} answers to the membership form. Processing...", &answers.len()); use email_address::*; fn choose_email(answer: &helloasso::FormAnswer) -> Option<String> { read_custom_field(answer, HelloassoCustomFieldType::Email) .and_then(|x| { if !EmailAddress::is_valid(&x) { None } else { Some(x) } }) .or(Some(answer.payer_user.email.clone())) } let mut generalized_answers: Vec<GeneralizedAnswer> = vec![]; for answer in answers { // eprintln!("Processing answer:"); let email = choose_email(&answer); // eprintln!(" email: {:?}", email); let mut generalized_answer = GeneralizedAnswer { first_name: Some(normalize_str(answer.user.first_name.clone())), last_name: normalize_str(answer.user.last_name.clone()), email, phone: parse_normalize_phone(read_custom_field(&answer, HelloassoCustomFieldType::Phone)), skills: read_custom_field(&answer, HelloassoCustomFieldType::Skills).map(normalize_str), address: read_custom_field(&answer, HelloassoCustomFieldType::Address) .map(normalize_str) .expect("Expected ha answer to have address"), postal_code: read_custom_field(&answer, HelloassoCustomFieldType::PostalCode) .expect("Expected ha answer to have postalcode"), city: read_custom_field(&answer, HelloassoCustomFieldType::City) .map(normalize_str) .expect("Expected ha answer to have city"), country: answer.payer_user.country.clone().trim()[..=1].to_string(), // we expect country code ISO 3166-1 alpha-2 job: read_custom_field(&answer, HelloassoCustomFieldType::Job).map(normalize_str), birth_year: read_custom_field(&answer, HelloassoCustomFieldType::Birthday).and_then(parse_and_get_birthday_year), inception_time: answer.order.inception_time, reference: format!("HA/{}", answer.id), donation_amount: 0, subscription_amount: answer.amount, membership_mode: serde_json::from_value(serde_json::Value::String(answer.mode.clone())) .expect("Expected a membership mode to be valid"), linked_user_first_name: read_custom_field(&answer, HelloassoCustomFieldType::LinkedUserFirstName) }; // apply custom user override // this particular answer had duplicate phone and email from another answer if answer.id == 64756582 { generalized_answer.email = None; generalized_answer.phone = None; } generalized_answers.push(generalized_answer); } println!("Generated GeneralizedAnswers"); sync_paheko( paheko_client, config, user_cache, generalized_answers, "512", "HelloAsso" ).await }