fix(paheko): retry in case of email duplication

This commit is contained in:
Matthieu Bessat 2024-01-19 15:58:07 +01:00
parent 978c00c1dc
commit 54bd32d57d
4 changed files with 54 additions and 14 deletions

21
Cargo.lock generated
View file

@ -292,6 +292,17 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "async-recursion"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.43",
]
[[package]] [[package]]
name = "async-std" name = "async-std"
version = "1.12.0" version = "1.12.0"
@ -508,7 +519,7 @@ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
"clap_lex", "clap_lex",
"strsim", "strsim 0.10.0",
] ]
[[package]] [[package]]
@ -1537,6 +1548,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"argh", "argh",
"async-recursion",
"base64_light", "base64_light",
"chrono", "chrono",
"clap", "clap",
@ -1550,6 +1562,7 @@ dependencies = [
"reqwest", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
"strsim 0.11.0",
"strum 0.25.0", "strum 0.25.0",
"surf", "surf",
"thiserror", "thiserror",
@ -2228,6 +2241,12 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strsim"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
[[package]] [[package]]
name = "strum" name = "strum"
version = "0.24.1" version = "0.24.1"

View file

@ -25,3 +25,5 @@ fully_pub = "0.1.4"
base64_light = "0.1.4" base64_light = "0.1.4"
csv = "1.3.0" csv = "1.3.0"
argh = "0.1.12" argh = "0.1.12"
strsim = "0.11.0"
async-recursion = "1.0.5"

View file

@ -1,3 +1,4 @@
use async_recursion::async_recursion;
use anyhow::{Context, Result, anyhow}; use anyhow::{Context, Result, anyhow};
use url::Url; use url::Url;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
@ -53,7 +54,8 @@ struct UserSummary {
id: Id, id: Id,
first_name: Option<String>, first_name: Option<String>,
last_name: String, last_name: String,
email: Option<String> email: Option<String>,
phone: Option<String>
} }
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
@ -293,7 +295,7 @@ impl AuthentifiedClient {
pub async fn get_users(&self) -> Result<Vec<UserSummary>> { pub async fn get_users(&self) -> Result<Vec<UserSummary>> {
let query: String = r#" let query: String = r#"
SELECT id,nom AS first_name,last_name,email FROM users; SELECT id,nom AS first_name,last_name,email,telephone AS phone FROM users;
"#.to_string(); "#.to_string();
let users_val = self.sql_query(query).await.context("Fetching users")?; let users_val = self.sql_query(query).await.context("Fetching users")?;
@ -389,6 +391,7 @@ impl AuthentifiedClient {
) )
} }
#[async_recursion]
pub async fn create_user(&self, user: &GeneralizedAnswer, next_id: u64) pub async fn create_user(&self, user: &GeneralizedAnswer, next_id: u64)
-> Result<UserSummary> -> Result<UserSummary>
{ {
@ -430,7 +433,16 @@ impl AuthentifiedClient {
.send().await?; .send().await?;
if res.status() != 200 { if res.status() != 200 {
self.show_paheko_err(res).await; let res_text = res.text().await.unwrap();
if res_text.contains("E-Mail") && res_text.contains("unique") {
eprintln!("WARN: Detected duplicated email, will retry without email");
// email detected as duplicated by paheko server
let mut new_data = user.clone();
new_data.email = None;
return self.create_user(&new_data, next_id).await;
}
// self.show_paheko_err(res).await;
return Err(APIClientError::InvalidStatusCode.into()); return Err(APIClientError::InvalidStatusCode.into());
} }
Ok( Ok(
@ -438,7 +450,8 @@ impl AuthentifiedClient {
id: Id(next_id), id: Id(next_id),
first_name: u.first_name, first_name: u.first_name,
last_name: u.last_name, last_name: u.last_name,
email: u.email email: u.email,
phone: u.phone
} }
) )
} }
@ -525,6 +538,6 @@ impl AuthentifiedClient {
} }
async fn show_paheko_err(&self, err_response: reqwest::Response) -> () { async fn show_paheko_err(&self, err_response: reqwest::Response) -> () {
eprintln!("Paheko Error Details: {:?} {:?}", err_response.status(), err_response.text().await.unwrap()) eprintln!("Paheko error details: {:?} {:?}", err_response.status(), err_response.text().await.unwrap())
} }
} }

View file

@ -51,7 +51,7 @@ struct GeneralizedAnswer {
fn get_accounting_year_for_time<'a>(accounting_years: &'a Vec<AccountingYear>, time: &'a DateTime<Utc>) -> Option<&'a AccountingYear> { fn get_accounting_year_for_time<'a>(accounting_years: &'a Vec<AccountingYear>, time: &'a DateTime<Utc>) -> Option<&'a AccountingYear> {
let date_ref = time.date_naive().clone(); let date_ref = time.date_naive().clone();
accounting_years.iter().find(|year| year.start_date < date_ref && date_ref < year.end_date) accounting_years.iter().find(|year| year.start_date <= date_ref && date_ref <= year.end_date)
} }
pub async fn sync_paheko( pub async fn sync_paheko(
@ -93,23 +93,23 @@ pub async fn sync_paheko(
let mut pk_next_user_service_id = paheko_client.get_next_id("services_users") let mut pk_next_user_service_id = paheko_client.get_next_id("services_users")
.await.context("Get paheko services_users next id")?; .await.context("Get paheko services_users next id")?;
for answer_inp in answers { for answer_inp in &answers {
let mut answer = answer_inp; let mut answer = answer_inp.clone();
answer.first_name = answer.first_name.map(normalize_first_name); answer.first_name = answer.first_name.map(normalize_first_name);
answer.last_name = normalize_last_name(answer.last_name); answer.last_name = normalize_last_name(answer.last_name);
eprintln!("Processing answer:"); eprintln!("Processing answer:");
eprintln!(" email: {:?}", answer.email);
eprintln!(" name: {} {}", &answer.last_name, answer.first_name.clone().unwrap_or("".to_string())); eprintln!(" name: {} {}", &answer.last_name, answer.first_name.clone().unwrap_or("".to_string()));
eprintln!(" email: {:?}", answer.email);
// list of users involved in this answer // list of users involved in this answer
let mut pk_users_summaries: Vec<paheko::UserSummary> = vec![]; let mut pk_users_summaries: Vec<paheko::UserSummary> = vec![];
let mut pk_user_service_registrations: Vec<paheko::UserServiceRegistration> = vec![]; let mut pk_user_service_registrations: Vec<paheko::UserServiceRegistration> = vec![];
// check for existing user in paheko by email // check for existing user in paheko by email
// TODO: check user with fuzzing // TODO: check user with fuzzing first name and last name
let existing_user_opt = existing_users let existing_user_opt = existing_users
.iter().find(|user| user.email == answer.email) .iter().find(|user| user.first_name == answer.first_name && user.last_name == answer.last_name)
.cloned(); .cloned();
// check for existing transactions // check for existing transactions
@ -123,6 +123,7 @@ pub async fn sync_paheko(
// dbg!(&existing_subscriptions); // dbg!(&existing_subscriptions);
let pk_user_summary = match existing_user_opt.clone() { let pk_user_summary = match existing_user_opt.clone() {
Some(user) => { Some(user) => {
eprintln!(" Found existing paheko user by name.");
user user
}, },
None => { None => {
@ -226,8 +227,13 @@ pub async fn sync_paheko(
// add transaction // add transaction
let transaction = paheko::SimpleTransaction { let transaction = paheko::SimpleTransaction {
accounting_year: get_accounting_year_for_time(&accounting_years, &answer.inception_time) accounting_year: match get_accounting_year_for_time(&accounting_years, &answer.inception_time) {
.expect("Cannot find an accounting year that match the date on paheko").id.clone(), None => {
eprintln!("Cannot find an accounting year on paheko that include the inception date {:?} given", &answer.inception_time);
panic!();
},
Some(s) => s
}.id.clone(),
// TODO: make the label template configurable // TODO: make the label template configurable
label: format!("{} {:?} via {}", pk_membership.service_name, pk_membership.mode_name, via_name), label: format!("{} {:?} via {}", pk_membership.service_name, pk_membership.mode_name, via_name),
amount: pk_membership.payed_amount, amount: pk_membership.payed_amount,