feat(helloasso): add dry run flag
This commit is contained in:
parent
700ba180b6
commit
f8a5aac75f
5 changed files with 96 additions and 24 deletions
19
src/main.rs
19
src/main.rs
|
@ -97,8 +97,7 @@ fn get_proxy_from_url(proxy_url: &Option<String>) -> Result<Option<reqwest::Prox
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn launch_adapter(source: SourceType, config: &Config) -> 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")?;
|
let mut user_cache = load_user_cache().context("Failed to load user cache")?;
|
||||||
|
|
||||||
if !&config.paheko_base_url.ends_with('/') {
|
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_id: config.paheko_client_id.clone(),
|
||||||
client_secret: config.paheko_client_secret.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 {
|
match source {
|
||||||
SourceType::Csv => sync_csv::sync_csv(&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).await?
|
SourceType::Helloasso => sync_helloasso::sync_helloasso(&paheko_client, config, &mut user_cache, dry_run).await?
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -134,7 +133,11 @@ struct App {
|
||||||
|
|
||||||
/// output debug info
|
/// output debug info
|
||||||
#[argh(switch, short = 'i')]
|
#[argh(switch, short = 'i')]
|
||||||
info: bool
|
env_info: bool,
|
||||||
|
|
||||||
|
/// dry run
|
||||||
|
#[argh(switch, short = 'd')]
|
||||||
|
dry_run: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SourceType {
|
enum SourceType {
|
||||||
|
@ -148,7 +151,7 @@ async fn main() {
|
||||||
dotenvy::dotenv().expect("Could not load dot env file");
|
dotenvy::dotenv().expect("Could not load dot env file");
|
||||||
let config: Config = envy::from_env().expect("Failed to load env vars");
|
let config: Config = envy::from_env().expect("Failed to load env vars");
|
||||||
|
|
||||||
if app.info {
|
if app.env_info {
|
||||||
dbg!(config);
|
dbg!(config);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -161,7 +164,7 @@ async fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let res = launch_adapter(source, &config).await;
|
let res = launch_adapter(source, &config, app.dry_run).await;
|
||||||
match res {
|
match res {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("Program failed, details bellow");
|
eprintln!("Program failed, details bellow");
|
||||||
|
|
|
@ -318,7 +318,8 @@ impl AuthentifiedClient {
|
||||||
{
|
{
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct Row {
|
struct Row {
|
||||||
_id: u64,
|
#[allow(dead_code)]
|
||||||
|
id: u64,
|
||||||
label: String,
|
label: String,
|
||||||
reference: String,
|
reference: String,
|
||||||
#[serde(deserialize_with = "deserialize_json_list")]
|
#[serde(deserialize_with = "deserialize_json_list")]
|
||||||
|
|
|
@ -33,7 +33,12 @@ fn process_price(value: String) -> f64 {
|
||||||
|
|
||||||
|
|
||||||
// read csv from stdin
|
// 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
|
// raw row record directly from CSV
|
||||||
#[derive(Debug, serde::Deserialize)]
|
#[derive(Debug, serde::Deserialize)]
|
||||||
struct AnswerRecord {
|
struct AnswerRecord {
|
||||||
|
|
|
@ -2,15 +2,54 @@ use crate::helloasso;
|
||||||
use crate::paheko;
|
use crate::paheko;
|
||||||
use crate::{
|
use crate::{
|
||||||
Config, UserCache,
|
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::utils::{parse_and_get_birthday_year, parse_normalize_phone, normalize_str};
|
||||||
use crate::sync_paheko::{GeneralizedAnswer, sync_paheko};
|
use crate::sync_paheko::{GeneralizedAnswer, sync_paheko};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{Context, Result};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use email_address::EmailAddress;
|
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<helloasso::AuthentifiedClient> {
|
||||||
|
// 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<helloasso::AuthentifiedClient> {
|
||||||
|
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
|
/// rust how to access inner enum value
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
enum HelloassoCustomFieldType {
|
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 {
|
let mut ha_client: helloasso::Client = helloasso::Client::new(helloasso::ClientConfig {
|
||||||
base_url: Url::parse("https://api.helloasso.com/v5/")
|
base_url: Url::parse("https://api.helloasso.com/v5/")
|
||||||
.expect("Expected valid helloasso API base URL"),
|
.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()))
|
.or(Some(answer.payer_user.email.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut kept_original_answers: Vec<helloasso::FormAnswer> = vec![];
|
||||||
let mut generalized_answers: Vec<GeneralizedAnswer> = vec![];
|
let mut generalized_answers: Vec<GeneralizedAnswer> = vec![];
|
||||||
for answer in answers {
|
for answer in answers {
|
||||||
let email = choose_email(&answer);
|
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.email = None;
|
||||||
generalized_answer.phone = 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);
|
generalized_answers.push(generalized_answer);
|
||||||
|
kept_original_answers.push(answer);
|
||||||
}
|
}
|
||||||
println!("Generated GeneralizedAnswers");
|
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(
|
sync_paheko(
|
||||||
paheko_client,
|
paheko_client,
|
||||||
config,
|
config,
|
||||||
|
@ -145,4 +208,12 @@ pub async fn sync_helloasso(paheko_client: &paheko::AuthentifiedClient, config:
|
||||||
).await
|
).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn list_answers(original_answers: Vec<helloasso::FormAnswer>, generalized_answers: Vec<GeneralizedAnswer>) {
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -81,8 +81,7 @@ pub async fn sync_paheko(
|
||||||
|
|
||||||
// 1. get summary of existing paheko users
|
// 1. get summary of existing paheko users
|
||||||
let mut existing_users = paheko_client.get_users().await.context("Get users")?;
|
let mut existing_users = paheko_client.get_users().await.context("Get users")?;
|
||||||
// 2. get summary of transactions for that year
|
// 2. get summary of transactions for the years we will use
|
||||||
// TODO: also get for the year n-1
|
|
||||||
let existing_transactions = paheko_client.get_transactions(&config.paheko_accounting_years_ids)
|
let existing_transactions = paheko_client.get_transactions(&config.paheko_accounting_years_ids)
|
||||||
.await.context("Get transactions")?;
|
.await.context("Get transactions")?;
|
||||||
// 3. get summary of services_users for that year
|
// 3. get summary of services_users for that year
|
||||||
|
@ -116,7 +115,6 @@ pub async fn sync_paheko(
|
||||||
.filter(|t| t.reference == answer.reference)
|
.filter(|t| t.reference == answer.reference)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// dbg!(&existing_subscriptions);
|
|
||||||
// check for existing user in paheko by email
|
// check for existing user in paheko by email
|
||||||
// TODO: check user with fuzzing first name and last name
|
// TODO: check user with fuzzing first name and last name
|
||||||
let existing_user_opt = existing_users
|
let existing_user_opt = existing_users
|
||||||
|
@ -166,7 +164,6 @@ pub async fn sync_paheko(
|
||||||
eprintln!(" User is already subscribed to this activity");
|
eprintln!(" User is already subscribed to this activity");
|
||||||
} else {
|
} else {
|
||||||
// add activity for first member
|
// add activity for first member
|
||||||
// TODO: check if activity already exists
|
|
||||||
let user_registration = paheko_client.register_user_to_service(
|
let user_registration = paheko_client.register_user_to_service(
|
||||||
pk_users_summaries.get(0).unwrap(),
|
pk_users_summaries.get(0).unwrap(),
|
||||||
&pk_membership,
|
&pk_membership,
|
||||||
|
@ -191,7 +188,7 @@ pub async fn sync_paheko(
|
||||||
// add first_name
|
// add first_name
|
||||||
match answer.linked_user_first_name {
|
match answer.linked_user_first_name {
|
||||||
Some(name) => {
|
Some(name) => {
|
||||||
second_answer.first_name = Some(name);
|
second_answer.first_name = Some(normalize_first_name(name));
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
second_answer.first_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),
|
label: format!("{} {:?} via {}", pk_membership.service_name, pk_membership.mode_name, via_name),
|
||||||
amount: pk_membership.payed_amount,
|
amount: pk_membership.payed_amount,
|
||||||
reference: answer.reference.clone(),
|
reference: answer.reference.clone(),
|
||||||
// TODO: make these field configurable
|
|
||||||
credit_account_code: "756".to_string(), // cotisations account
|
credit_account_code: "756".to_string(), // cotisations account
|
||||||
debit_account_code: debit_account_code.to_string(), // helloasso account
|
debit_account_code: debit_account_code.to_string(), // helloasso account
|
||||||
inception_time: answer.inception_time,
|
inception_time: answer.inception_time,
|
||||||
kind: paheko::TransactionKind::Revenue,
|
kind: paheko::TransactionKind::Revenue,
|
||||||
linked_users: pk_users_summaries.iter().map(|x| x.id.clone()).collect(),
|
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()
|
linked_subscriptions: pk_user_service_registrations.iter().map(|x| x.id.clone()).collect()
|
||||||
};
|
};
|
||||||
paheko_client.register_transaction(transaction)
|
paheko_client.register_transaction(transaction)
|
||||||
|
@ -286,9 +281,6 @@ pub async fn sync_paheko(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO: handle donation amount
|
|
||||||
|
|
||||||
pk_memberships.push(pk_membership);
|
pk_memberships.push(pk_membership);
|
||||||
}
|
}
|
||||||
eprintln!("{via_name} sync done.");
|
eprintln!("{via_name} sync done.");
|
||||||
|
|
Loading…
Reference in a new issue