From 1fcf35eaa720ca4e5c05d87a187ae39794e5629c Mon Sep 17 00:00:00 2001 From: Matthieu Bessat Date: Wed, 27 Dec 2023 01:03:33 +0100 Subject: [PATCH] feat: basic import of user activity --- TODO.md | 18 +++++++++++++++ src/main.rs | 56 +++++++++++++++++++++++++++++++--------------- src/paheko.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++++++-- src/utils.rs | 6 +++++ 4 files changed, 122 insertions(+), 20 deletions(-) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..fb50ff0 --- /dev/null +++ b/TODO.md @@ -0,0 +1,18 @@ +Following the dev + +like rossman said, you need to split up things + +# schedule + +## 2023-12-23 + +- have a rust client to fetch list of users from sql + +## 2023-12-27 + +- Normalize address, cities + +all of the normalization work should not have been done here, + +we should have created our own platform so people can register and pay later (either in cash, or using 3rd party payment like stripe) + diff --git a/src/main.rs b/src/main.rs index 60f32ca..1be8965 100644 --- a/src/main.rs +++ b/src/main.rs @@ -262,12 +262,13 @@ async fn launch_adapter() -> Result<()> { } // get summary of users - let existing_users = paheko_client.get_users().await.context("Get users")?; + let mut existing_users = paheko_client.get_users().await.context("Get users")?; // get summary of transactions for that year let existing_transactions = paheko_client.get_transactions(1).await.context("Get transactions")?; // TODO: before creating any users, get the current maximum id of the users table to predict // the next auto-incrementing id. + let mut pk_next_id = paheko_client.get_user_next_id().await.context("Get paheko users next id")?; for answer in answers { eprintln!("Processing answer:"); @@ -275,8 +276,8 @@ async fn launch_adapter() -> Result<()> { eprintln!(" email: {:?}", email); - let paheko_user = paheko::User { - id: generate_id(), + let mut pk_user = paheko::User { + id: utils::Id(0), first_name: Some(normalize_str(answer.user.first_name.clone())), last_name: normalize_str(answer.user.last_name.clone()), email, @@ -295,6 +296,13 @@ async fn launch_adapter() -> Result<()> { register_time: answer.order.inception_time, }; + // apply custom user override + // this particular answer had duplicate phone and email from another answer + if answer.id == 64756582 { + pk_user.email = None; + pk_user.phone = None; + } + // check for existing transactions if let Some(_) = existing_transactions.iter().find( |summary| summary.reference == format!("HA/{}", answer.id) @@ -303,18 +311,19 @@ async fn launch_adapter() -> Result<()> { continue; } + let existing_user_opt = existing_users.iter().find(|user| user.email == pk_user.email).cloned(); + // check for existing paheko user, or create paheko user - let paheko_user_summary = match existing_users.iter().find(|user| user.email == paheko_user.email) { - Some(user) => user.clone(), + let pk_user_summary = match existing_user_opt.clone() { + Some(user) => user, None => { - let c = paheko_client.create_user(&paheko_user).await.context("Expected to create paheko user")?; + let c = paheko_client.create_user( + &pk_user, pk_next_id.clone() + ).await.context("Expected to create paheko user")?; eprintln!(" Created paheko user"); - UserSummary { - id: utils::Id(0), - first_name: paheko_user.first_name.clone(), - last_name: paheko_user.last_name.clone(), - email: paheko_user.email.clone() - } + pk_next_id += 1; + existing_users.push(c.clone()); + c } }; @@ -323,7 +332,7 @@ async fn launch_adapter() -> Result<()> { campaign: "".to_string(), inception_time: Utc::now(), mode: helloasso_to_paheko_membership(&answer.mode), - users: vec![paheko_user.id.clone()], + users: vec![pk_user.id.clone()], external_references: paheko::ExternalReferences { helloasso_ref: paheko::HelloassoReferences { answer_id: answer.id, @@ -331,12 +340,11 @@ async fn launch_adapter() -> Result<()> { } } }; - dbg!(&pk_membership.users); + // then create optional linked user - if answer.mode == helloasso::MembershipMode::Couple { - let mut second_pk_user = paheko_user.clone(); - second_pk_user.id = generate_id(); + let mut second_pk_user = pk_user.clone(); + second_pk_user.id = utils::Id(0); second_pk_user.email = None; second_pk_user.phone = None; second_pk_user.skills = None; @@ -354,10 +362,22 @@ async fn launch_adapter() -> Result<()> { } } + if existing_user_opt.is_none() { + let second_pk_user_summary = paheko_client.create_user(&second_pk_user, pk_next_id) + .await.context("Expected to create second paheko user")?; + eprintln!(" Created conjoint paheko user"); + pk_next_id += 1; + } + // TODO: get existing linked user from previous year + pk_membership.users.push(second_pk_user.id.clone()); pk_users.push(second_pk_user); } - pk_users.push(paheko_user); + + // add activity + paheko_client.register_user_to_service(&pk_user_summary).await.context("Registering user to paheko server")?; + + pk_users.push(pk_user); pk_memberships.push(pk_membership); } dbg!(&pk_users); diff --git a/src/paheko.rs b/src/paheko.rs index faca9c0..624cc73 100644 --- a/src/paheko.rs +++ b/src/paheko.rs @@ -237,6 +237,23 @@ impl AuthentifiedClient { Ok(serde_json::from_value(users_val.results)?) } + pub async fn get_user_next_id(&self) -> Result { + let query: String = r#" + SELECT id FROM users ORDER BY id DESC LIMIT 1 + "#.to_string(); + + let users_id_val = self.sql_query(query).await.context("Fetching users")?; + + #[derive(Deserialize)] + struct UserIdEntry { + id: u64 + } + + let users_ids: Vec = serde_json::from_value(users_id_val.results)?; + + Ok(users_ids.iter().nth(0).map(|x| x.id).unwrap_or(1)+1) + } + pub async fn get_transactions(&self, id_year: u32) -> Result> { @@ -249,7 +266,7 @@ impl AuthentifiedClient { Ok(serde_json::from_value(val.results)?) } - pub async fn create_user(&self, user: &User) + pub async fn create_user(&self, user: &User, next_id: u64) -> Result { // single-user import @@ -294,11 +311,52 @@ impl AuthentifiedClient { } Ok( UserSummary { - id: Id(0), + id: Id(next_id), first_name: u.first_name, last_name: u.last_name, email: u.email } ) } + + pub async fn register_user_to_service(&self, user: &UserSummary) + -> Result<()> + { + // single-user import + // create virtual file + let u = user.clone(); + + let mut csv_content: String = String::new(); + csv_content.push_str( + r#""Numéro de membre","Activité","Tarif","Date d'inscription","Date d'expiration","Montant à régler","Payé ?""#); + csv_content.push_str("\n"); + csv_content.push_str( + format!("{},{:?},{:?},{:?},{:?},{:?},{:?}\n", + u.id, + "Cotisation 2023-2024", + "Physique Individuelle", + "10/10/2023", + "10/10/2025", + "10", + "Oui" + ).as_str()); + + use reqwest::multipart::Form; + use reqwest::multipart::Part; + + let part = Part::text(csv_content).file_name("file"); + + let form = Form::new() + .part("file", part); + + let res = self.client + .post(self.base_url.join("services/subscriptions/import")?) + .multipart(form) + .send().await?; + + if res.status() != 200 { + return Err(APIClientError::InvalidStatusCode.into()); + } + Ok(()) + } } diff --git a/src/utils.rs b/src/utils.rs index f94e8f3..bc4c71f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,5 @@ use serde::{Serialize, Deserialize, Deserializer}; +use std::fmt; use rand::{thread_rng, Rng}; use chrono::prelude::{DateTime, Utc}; @@ -15,6 +16,11 @@ impl Into for Id { format!("{:x}", self.0) } } +impl fmt::Display for Id { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} pub fn generate_id() -> Id { Id(thread_rng().gen())