diff --git a/src/main.rs b/src/main.rs index b0d57ab..a77201d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -97,8 +97,7 @@ fn get_proxy_from_url(proxy_url: &Option) -> Result Result<()> { - +async fn launch_adapter(source: SourceType, config: &Config, dry_run: bool) -> Result<()> { let mut user_cache = load_user_cache().context("Failed to load user cache")?; if !&config.paheko_base_url.ends_with('/') { @@ -114,11 +113,11 @@ async fn launch_adapter(source: SourceType, config: &Config) -> Result<()> { client_id: config.paheko_client_id.clone(), client_secret: config.paheko_client_secret.clone() }; - let paheko_client: paheko::AuthentifiedClient = paheko_client.login(paheko_credentials).await?; + let paheko_client = paheko_client.login(paheko_credentials).await.context("Paheko login")?; match source { - SourceType::Csv => sync_csv::sync_csv(&paheko_client, config, &mut user_cache).await?, - SourceType::Helloasso => sync_helloasso::sync_helloasso(&paheko_client, config, &mut user_cache).await? + SourceType::Csv => sync_csv::sync_csv(&paheko_client, config, &mut user_cache, dry_run).await?, + SourceType::Helloasso => sync_helloasso::sync_helloasso(&paheko_client, config, &mut user_cache, dry_run).await? } Ok(()) @@ -134,7 +133,11 @@ struct App { /// output debug info #[argh(switch, short = 'i')] - info: bool + env_info: bool, + + /// dry run + #[argh(switch, short = 'd')] + dry_run: bool } enum SourceType { @@ -148,7 +151,7 @@ async fn main() { dotenvy::dotenv().expect("Could not load dot env file"); let config: Config = envy::from_env().expect("Failed to load env vars"); - if app.info { + if app.env_info { dbg!(config); return; } @@ -161,7 +164,7 @@ async fn main() { return; } }; - let res = launch_adapter(source, &config).await; + let res = launch_adapter(source, &config, app.dry_run).await; match res { Err(err) => { eprintln!("Program failed, details bellow"); diff --git a/src/paheko.rs b/src/paheko.rs index 4bd9cbd..b8ee3dc 100644 --- a/src/paheko.rs +++ b/src/paheko.rs @@ -318,7 +318,8 @@ impl AuthentifiedClient { { #[derive(Debug, Deserialize)] struct Row { - _id: u64, + #[allow(dead_code)] + id: u64, label: String, reference: String, #[serde(deserialize_with = "deserialize_json_list")] diff --git a/src/sync_csv.rs b/src/sync_csv.rs index 35a8aac..ffbde7a 100644 --- a/src/sync_csv.rs +++ b/src/sync_csv.rs @@ -33,7 +33,12 @@ fn process_price(value: String) -> f64 { // read csv from stdin -pub async fn sync_csv(paheko_client: &paheko::AuthentifiedClient, config: &Config, _user_cache: &mut UserCache) -> Result<()> { +pub async fn sync_csv( + paheko_client: &paheko::AuthentifiedClient, + config: &Config, + _user_cache: &mut UserCache, + _dry_run: bool +) -> Result<()> { // raw row record directly from CSV #[derive(Debug, serde::Deserialize)] struct AnswerRecord { diff --git a/src/sync_helloasso.rs b/src/sync_helloasso.rs index 6b6cad0..615e008 100644 --- a/src/sync_helloasso.rs +++ b/src/sync_helloasso.rs @@ -2,15 +2,54 @@ use crate::helloasso; use crate::paheko; use crate::{ Config, UserCache, - get_proxy_from_url, get_auth_client_from_cache, + get_proxy_from_url, write_user_cache }; use crate::utils::{parse_and_get_birthday_year, parse_normalize_phone, normalize_str}; use crate::sync_paheko::{GeneralizedAnswer, sync_paheko}; -use anyhow::Result; +use anyhow::{Context, Result}; use url::Url; use email_address::EmailAddress; +/// Get authentified HelloAsso client +async fn get_auth_client_from_cache( + user_cache: &mut UserCache, + ha_client: &mut helloasso::Client, + login_payload: helloasso::LoginPayload +) -> Result { + // TODO: find a better way to have the logic implemented + async fn login( + user_cache: &mut UserCache, + ha_client: &mut helloasso::Client, + login_payload: helloasso::LoginPayload + ) -> Result { + let auth_client = ha_client.login(login_payload).await.context("Failed to login to HelloAsso")?; + user_cache.helloasso_session = Some(auth_client.session.clone()); + write_user_cache(user_cache).expect("Unable to write user cache"); + eprintln!("HelloAsso: Logged in and wrote token to cache"); + Ok(auth_client) + } + + eprintln!("Init HA client"); + + match &user_cache.helloasso_session { + Some(cached_session) => { + let auth_client = ha_client.authentified_client(cached_session.clone()); + + if !auth_client.verify_auth().await? { + println!("HelloAsso: Need to relog, token invalid"); + return login(user_cache, ha_client, login_payload).await + } + eprintln!("HelloAsso: Used anterior token"); + Ok(auth_client) + }, + None => { + eprintln!("HelloAsso: First time login"); + login(user_cache, ha_client, login_payload).await + } + } +} + /// rust how to access inner enum value #[derive(Debug, PartialEq, Clone, Copy)] enum HelloassoCustomFieldType { @@ -52,7 +91,14 @@ fn read_custom_field(form_answer: &helloasso::FormAnswer, custom_field: Helloass } -pub async fn sync_helloasso(paheko_client: &paheko::AuthentifiedClient, config: &Config, user_cache: &mut UserCache) -> Result<()> { +pub async fn sync_helloasso( + paheko_client: &paheko::AuthentifiedClient, + config: &Config, + user_cache: &mut UserCache, + dry_run: bool +) -> Result<()> { + + eprintln!("wow"); 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"), @@ -84,6 +130,7 @@ pub async fn sync_helloasso(paheko_client: &paheko::AuthentifiedClient, config: .or(Some(answer.payer_user.email.clone())) } + let mut kept_original_answers: Vec = vec![]; let mut generalized_answers: Vec = vec![]; for answer in answers { let email = choose_email(&answer); @@ -132,10 +179,26 @@ pub async fn sync_helloasso(paheko_client: &paheko::AuthentifiedClient, config: generalized_answer.email = None; generalized_answer.phone = None; } + if answer.id == 64756581 { + if let Some(wrong_first_name) = generalized_answer.first_name { + let compos: Vec<&str> = wrong_first_name.split(" et ").collect(); + generalized_answer.first_name = Some(compos.get(0).unwrap().to_string()); + generalized_answer.linked_user_first_name = Some(compos.get(1).unwrap().to_string()); + } + } generalized_answers.push(generalized_answer); + kept_original_answers.push(answer); } println!("Generated GeneralizedAnswers"); + + + if dry_run { + eprintln!("Will stop because dry run mode is enabled."); + list_answers(kept_original_answers, generalized_answers); + return Ok(()); + } + sync_paheko( paheko_client, config, @@ -145,4 +208,12 @@ pub async fn sync_helloasso(paheko_client: &paheko::AuthentifiedClient, config: ).await } - +fn list_answers(original_answers: Vec, generalized_answers: Vec) { + for (ha_answer, ga) in std::iter::zip(original_answers, generalized_answers) { + println!( + "{} {:?} {:?} {:?} {:?}", + ha_answer.id, ga.first_name, ga.last_name, + ga.email, ga.linked_user_first_name + ); + } +} diff --git a/src/sync_paheko.rs b/src/sync_paheko.rs index a0ab30d..be8e002 100644 --- a/src/sync_paheko.rs +++ b/src/sync_paheko.rs @@ -81,8 +81,7 @@ pub async fn sync_paheko( // 1. get summary of existing paheko users let mut existing_users = paheko_client.get_users().await.context("Get users")?; - // 2. get summary of transactions for that year - // TODO: also get for the year n-1 + // 2. get summary of transactions for the years we will use let existing_transactions = paheko_client.get_transactions(&config.paheko_accounting_years_ids) .await.context("Get transactions")?; // 3. get summary of services_users for that year @@ -116,7 +115,6 @@ pub async fn sync_paheko( .filter(|t| t.reference == answer.reference) .collect(); - // dbg!(&existing_subscriptions); // check for existing user in paheko by email // TODO: check user with fuzzing first name and last name let existing_user_opt = existing_users @@ -166,7 +164,6 @@ pub async fn sync_paheko( eprintln!(" User is already subscribed to this activity"); } else { // add activity for first member - // TODO: check if activity already exists let user_registration = paheko_client.register_user_to_service( pk_users_summaries.get(0).unwrap(), &pk_membership, @@ -191,7 +188,7 @@ pub async fn sync_paheko( // add first_name match answer.linked_user_first_name { Some(name) => { - second_answer.first_name = Some(name); + second_answer.first_name = Some(normalize_first_name(name)); }, None => { second_answer.first_name = None; @@ -237,13 +234,11 @@ pub async fn sync_paheko( label: format!("{} {:?} via {}", pk_membership.service_name, pk_membership.mode_name, via_name), amount: pk_membership.payed_amount, reference: answer.reference.clone(), - // TODO: make these field configurable credit_account_code: "756".to_string(), // cotisations account debit_account_code: debit_account_code.to_string(), // helloasso account inception_time: answer.inception_time, kind: paheko::TransactionKind::Revenue, linked_users: pk_users_summaries.iter().map(|x| x.id.clone()).collect(), - // the linked_services, depend on a patch to paheko API code to work (see https://forge.lefuturiste.fr/mbess/paheko-fork/commit/a4fdd816112f51db23a2b02ac160b0513a5b09c5) linked_subscriptions: pk_user_service_registrations.iter().map(|x| x.id.clone()).collect() }; paheko_client.register_transaction(transaction) @@ -286,9 +281,6 @@ pub async fn sync_paheko( } } - - // TODO: handle donation amount - pk_memberships.push(pk_membership); } eprintln!("{via_name} sync done.");