feat: add membership basic support

This commit is contained in:
Matthieu Bessat 2023-11-09 09:14:14 +01:00
parent 7a84f7d3c0
commit bbdbbbfb8a
4 changed files with 272 additions and 17 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
/target /target
.env .env
log.log log.log
tmp

175
Cargo.lock generated
View file

@ -71,6 +71,15 @@ dependencies = [
"opaque-debug", "opaque-debug",
] ]
[[package]]
name = "aho-corasick"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "android-tzdata" name = "android-tzdata"
version = "0.1.1" version = "0.1.1"
@ -297,6 +306,15 @@ version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@ -580,6 +598,12 @@ version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
name = "either"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.33" version = "0.8.33"
@ -1067,6 +1091,15 @@ dependencies = [
"waker-fn", "waker-fn",
] ]
[[package]]
name = "itertools"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.9" version = "1.0.9"
@ -1125,6 +1158,12 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.3.8" version = "0.3.8"
@ -1156,6 +1195,15 @@ dependencies = [
"value-bag", "value-bag",
] ]
[[package]]
name = "lru-cache"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
dependencies = [
"linked-hash-map",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.6.4" version = "2.6.4"
@ -1178,6 +1226,12 @@ dependencies = [
"unicase", "unicase",
] ]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.7.1" version = "0.7.1"
@ -1216,6 +1270,16 @@ dependencies = [
"tempfile", "tempfile",
] ]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.17" version = "0.2.17"
@ -1250,6 +1314,12 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "oncemutex"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d11de466f4a3006fe8a5e7ec84e93b79c70cb992ae0aa0eb631ad2df8abfe2"
[[package]] [[package]]
name = "opaque-debug" name = "opaque-debug"
version = "0.3.0" version = "0.3.0"
@ -1309,10 +1379,12 @@ dependencies = [
"clap", "clap",
"dotenvy", "dotenvy",
"envy", "envy",
"phonenumber",
"rand 0.8.5",
"reqwest", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
"strum", "strum 0.25.0",
"surf", "surf",
"thiserror", "thiserror",
"tokio", "tokio",
@ -1355,6 +1427,27 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]]
name = "phonenumber"
version = "0.3.3+8.13.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "635f3e6288e4f01c049d89332a031bd74f25d64b6fb94703ca966e819488cd06"
dependencies = [
"bincode",
"either",
"fnv",
"itertools",
"lazy_static",
"nom",
"quick-xml",
"regex",
"regex-cache",
"serde",
"serde_derive",
"strum 0.24.1",
"thiserror",
]
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.1.3" version = "1.1.3"
@ -1452,6 +1545,15 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "quick-xml"
version = "0.28.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.33" version = "1.0.33"
@ -1541,6 +1643,53 @@ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
] ]
[[package]]
name = "regex"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax 0.8.2",
]
[[package]]
name = "regex-automata"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.8.2",
]
[[package]]
name = "regex-cache"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f7b62d69743b8b94f353b6b7c3deb4c5582828328bcb8d5fedf214373808793"
dependencies = [
"lru-cache",
"oncemutex",
"regex",
"regex-syntax 0.6.29",
]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.22" version = "0.11.22"
@ -1896,13 +2045,35 @@ 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 = "strum"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
dependencies = [
"strum_macros 0.24.3",
]
[[package]] [[package]]
name = "strum" name = "strum"
version = "0.25.0" version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
dependencies = [ dependencies = [
"strum_macros", "strum_macros 0.25.2",
]
[[package]]
name = "strum_macros"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn 1.0.109",
] ]
[[package]] [[package]]

View file

@ -18,3 +18,5 @@ chrono = { version = "0.4.31", features = ["serde"] }
envy = "0.4.2" envy = "0.4.2"
strum = { version = "0.25", features = ["derive"] } strum = { version = "0.25", features = ["derive"] }
dotenvy = "0.15.7" dotenvy = "0.15.7"
rand = "0.8.5"
phonenumber = "0.3.3"

View file

@ -1,9 +1,30 @@
use url::Url;
use serde::{Serialize, Deserialize};
use anyhow::{Context, Result, anyhow}; use anyhow::{Context, Result, anyhow};
use url::Url;
use chrono::prelude::{NaiveDate, DateTime, Utc}; use chrono::prelude::{NaiveDate, DateTime, Utc};
use strum::{Display }; use strum::Display;
use serde::{Serialize, Deserialize};
use std::collections::HashSet; use std::collections::HashSet;
use rand::{thread_rng, Rng};
use phonenumber;
/// ID
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq, Hash)]
pub struct Id(pub u64);
impl Id {
pub fn to_string(&self) -> String {
format!("{:x}", self.0)
}
}
impl Into<String> for Id {
fn into(self) -> String {
format!("{:x}", self.0)
}
}
pub fn generate_id() -> Id {
Id(thread_rng().gen())
}
/// permanent config to store long-term config /// permanent config to store long-term config
/// used to ingest env settings /// used to ingest env settings
@ -321,7 +342,7 @@ struct Organization {
slug: String slug: String
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
enum MembershipMode { enum MembershipMode {
#[serde(rename = "Individuel")] #[serde(rename = "Individuel")]
Individual, Individual,
@ -410,8 +431,9 @@ fn get_paheko_membership_from_ha_answers() {
/// for now we include the custom fields into the paheko user /// for now we include the custom fields into the paheko user
/// we don't have time to implement user settings to change the custom fields mapping /// we don't have time to implement user settings to change the custom fields mapping
/// for now, manual mapping /// for now, manual mapping
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Clone)]
struct PahekoUser { struct PahekoUser {
id: Id,
first_name: String, first_name: String,
last_name: String, last_name: String,
email: String, email: String,
@ -425,12 +447,12 @@ struct PahekoUser {
birthday: Option<NaiveDate> // we will need to validate some data before birthday: Option<NaiveDate> // we will need to validate some data before
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Clone)]
struct PahekoMembership { struct PahekoMembership {
author: PahekoUser, id: Id,
linked_users: Vec<PahekoUser>, users: Vec<Id>,
campaign: String, campaign: String,
mode: String, mode: MembershipMode,
inception_datum: DateTime<Utc> inception_datum: DateTime<Utc>
} }
@ -484,6 +506,21 @@ fn read_custom_field(form_answer: &FormAnswer, custom_field: HelloAssoCustomFiel
.and_then(|cf| Some(cf.answer.clone())) .and_then(|cf| Some(cf.answer.clone()))
} }
fn parse_normalize_phone(phone_number_opt: Option<String>) -> Option<String> {
let number_raw = phone_number_opt?;
let parsed = match phonenumber::parse(Some(phonenumber::country::Id::FR), number_raw) {
Ok(r) => {
r
},
Err(_e) => {
return None;
}
};
Some(parsed.to_string())
}
async fn launch_adapter() -> Result<()> { async fn launch_adapter() -> Result<()> {
dotenvy::dotenv()?; dotenvy::dotenv()?;
@ -509,9 +546,13 @@ async fn launch_adapter() -> Result<()> {
// dbg!(&answers); // dbg!(&answers);
println!("Got {} answers to the membership form. Processing...", &answers.len()); println!("Got {} answers to the membership form. Processing...", &answers.len());
// first, request the current list of membership in paheko that were created with helloasso
// get the list of payments associated
// first step: output a list of PahekoUser with PahekoMembership // first step: output a list of PahekoUser with PahekoMembership
let pk_memberships: Vec<PahekoMembership> = vec![]; let pk_memberships: Vec<PahekoMembership> = vec![];
let mut pk_users: Vec<PahekoUser> = vec![]; let mut pk_users: Vec<PahekoUser> = vec![];
let mut pk_memberships: Vec<PahekoMembership> = vec![];
let mut count: u64 = 0; let mut count: u64 = 0;
let mut names: HashSet<String> = HashSet::new(); let mut names: HashSet<String> = HashSet::new();
@ -523,25 +564,65 @@ async fn launch_adapter() -> Result<()> {
names.insert(custom_field.name.clone()); names.insert(custom_field.name.clone());
count += 1; count += 1;
} }
pk_users.push(PahekoUser { let paheko_user = PahekoUser {
id: generate_id(),
first_name: answer.user.first_name.clone(), first_name: answer.user.first_name.clone(),
last_name: answer.user.last_name.clone(), last_name: answer.user.last_name.clone(),
email: answer.user.email.clone(), email: answer.user.email.clone(),
phone: read_custom_field(&answer, HelloAssoCustomFieldType::Phone), phone: parse_normalize_phone(read_custom_field(&answer, HelloAssoCustomFieldType::Phone)),
skills: read_custom_field(&answer, HelloAssoCustomFieldType::Skills), skills: read_custom_field(&answer, HelloAssoCustomFieldType::Skills),
address: read_custom_field(&answer, HelloAssoCustomFieldType::Address).expect("to have address"), address: read_custom_field(&answer, HelloAssoCustomFieldType::Address).expect("to have address"),
postal_code: read_custom_field(&answer, HelloAssoCustomFieldType::PostalCode).expect("to have postal code"), postal_code: read_custom_field(&answer, HelloAssoCustomFieldType::PostalCode).expect("to have postal code"),
city: read_custom_field(&answer, HelloAssoCustomFieldType::City).expect("to have city"), city: read_custom_field(&answer, HelloAssoCustomFieldType::City).expect("to have city"),
job: read_custom_field(&answer, HelloAssoCustomFieldType::Job), job: read_custom_field(&answer, HelloAssoCustomFieldType::Job),
birthday: None birthday: None
}); };
let mut pk_membership = PahekoMembership {
id: generate_id(),
campaign: "".to_string(),
inception_datum: Utc::now(),
mode: answer.mode.clone(),
users: vec![paheko_user.id.clone()]
};
dbg!(&pk_membership.users);
// then create optional linked user // then create optional linked user
if answer.mode == MembershipMode::Couple {
let mut second_pk_user = paheko_user.clone();
second_pk_user.id = generate_id();
match read_custom_field(&answer, HelloAssoCustomFieldType::LinkedUserFirstName) {
Some(name) => {
second_pk_user.first_name = name
},
None => {
second_pk_user.first_name = "Conjoint".to_string();
eprintln!("Got a user with Couple mode but no additional name given!")
}
}
pk_membership.users.push(second_pk_user.id.clone());
pk_users.push(second_pk_user);
}
pk_users.push(paheko_user);
pk_memberships.push(pk_membership);
} }
dbg!(pk_users); dbg!(&pk_users);
dbg!(names); dbg!(&pk_memberships);
dbg!(count); dbg!(&pk_users.len());
dbg!(&pk_memberships.len());
// println!("{:?}", &pk_users.iter().map(|user| format!("{:?}", user.email)).collect::<Vec<String>>());
for u in pk_users {
println!("{} {}", u.email, u.phone.unwrap_or("".to_string()));
}
// then, request the current list of users // then, request the current list of users
// match with the email address
// we consider the email address as the id for a helloasso user
// then, upload the PahekoMembership // then, upload the PahekoMembership
Ok(()) Ok(())