From e630997ff55083081fbf14fb5eb33916dad621f2 Mon Sep 17 00:00:00 2001 From: Marc Plano-Lesay Date: Wed, 6 Nov 2024 11:05:49 +1100 Subject: [PATCH] PoC of refactoring actions --- src/actions/action.rs | 11 +++++ src/actions/fetch_all_persons.rs | 38 +++++++++++++++ src/actions/mod.rs | 3 ++ src/actions/update_person_date_of_birth.rs | 51 +++++++++++++++++++ src/commands/missing_date_of_birth.rs | 10 ++-- src/commands/sync_date_of_birth.rs | 57 +++++++++------------- src/main.rs | 2 + src/models/mod.rs | 1 + src/models/person.rs | 21 ++++++++ src/utils/mod.rs | 1 - src/utils/people.rs | 19 -------- 11 files changed, 158 insertions(+), 56 deletions(-) create mode 100644 src/actions/action.rs create mode 100644 src/actions/fetch_all_persons.rs create mode 100644 src/actions/mod.rs create mode 100644 src/actions/update_person_date_of_birth.rs create mode 100644 src/models/mod.rs create mode 100644 src/models/person.rs delete mode 100644 src/utils/people.rs diff --git a/src/actions/action.rs b/src/actions/action.rs new file mode 100644 index 0000000..7a39a19 --- /dev/null +++ b/src/actions/action.rs @@ -0,0 +1,11 @@ +use crate::context::Context; +use color_eyre::eyre::Result; + +pub trait Action { + type Input; + type Output; + + fn new(input: Self::Input) -> Self; + fn describe(&self) -> String; + async fn execute(&self, ctx: &Context) -> Result; +} diff --git a/src/actions/fetch_all_persons.rs b/src/actions/fetch_all_persons.rs new file mode 100644 index 0000000..ee7f16b --- /dev/null +++ b/src/actions/fetch_all_persons.rs @@ -0,0 +1,38 @@ +use color_eyre::eyre::Result; + +use crate::{context::Context, models::person::Person}; + +use super::action::Action; + +pub struct FetchAllPersons {} + +impl Action for FetchAllPersons { + type Input = (); + type Output = Vec; + + fn new(_input: Self::Input) -> Self { + Self {} + } + + fn describe(&self) -> String { + String::from("Fetching all persons") + } + + async fn execute(&self, ctx: &Context) -> Result { + let mut all_people = Vec::new(); + let mut page_number = 1; + let mut has_next_page = true; + + while has_next_page { + let fetched = ctx + .client + .get_all_people(Some(page_number.into()), None, Some(true)) + .await?; + all_people.extend(fetched.people.clone()); + has_next_page = fetched.has_next_page.expect("Missing has_next_page"); + page_number += 1; + } + + Ok(all_people.into_iter().map(Into::into).collect()) + } +} diff --git a/src/actions/mod.rs b/src/actions/mod.rs new file mode 100644 index 0000000..03f2599 --- /dev/null +++ b/src/actions/mod.rs @@ -0,0 +1,3 @@ +pub mod action; +pub mod fetch_all_persons; +pub mod update_person_date_of_birth; diff --git a/src/actions/update_person_date_of_birth.rs b/src/actions/update_person_date_of_birth.rs new file mode 100644 index 0000000..113b777 --- /dev/null +++ b/src/actions/update_person_date_of_birth.rs @@ -0,0 +1,51 @@ +use chrono::NaiveDate; +use color_eyre::eyre::Result; +use log::info; + +use crate::{context::Context, models::person::Person, types::PersonUpdateDto}; + +use super::action::Action; + +pub struct UpdatePersonDateOfBirth { + args: UpdatePersonDateOfBirthArgs, +} + +pub struct UpdatePersonDateOfBirthArgs { + pub person: Person, + pub date_of_birth: NaiveDate, +} + +impl Action for UpdatePersonDateOfBirth { + type Input = UpdatePersonDateOfBirthArgs; + type Output = (); + + fn new(input: Self::Input) -> Self { + Self { args: input } + } + + fn describe(&self) -> String { + format!( + "Updating {}'s date of birth to {}", + self.args.person.name, self.args.date_of_birth, + ) + } + + async fn execute(&self, ctx: &Context) -> Result { + let update = PersonUpdateDto { + feature_face_asset_id: None, + is_hidden: None, + name: None, + birth_date: Some(self.args.date_of_birth), + }; + + info!("{}", self.describe()); + + if !ctx.dry_run { + ctx.client + .update_person(&self.args.person.id, &update) + .await?; + } + + Ok(()) + } +} diff --git a/src/commands/missing_date_of_birth.rs b/src/commands/missing_date_of_birth.rs index 09afe9e..8ab4083 100644 --- a/src/commands/missing_date_of_birth.rs +++ b/src/commands/missing_date_of_birth.rs @@ -1,12 +1,16 @@ -use crate::{context::Context, utils::people::fetch_all_contacts}; +use crate::{ + actions::{action::Action, fetch_all_persons::FetchAllPersons}, + context::Context, +}; use color_eyre::eyre::Result; use log::*; pub async fn missing_date_of_birth(ctx: Context) -> Result<()> { - let contacts = fetch_all_contacts(&ctx.client).await?; + let fetch_action = FetchAllPersons::new(()); + let contacts = fetch_action.execute(&ctx).await?; let mut filtered_contacts = contacts .iter() - .filter(|c| c.birth_date.is_none() && !c.name.is_empty()) + .filter(|c| c.date_of_birth.is_none() && !c.name.is_empty()) .collect::>(); filtered_contacts.sort_by_key(|c| c.name.clone()); diff --git a/src/commands/sync_date_of_birth.rs b/src/commands/sync_date_of_birth.rs index 6a85db9..0272f1f 100644 --- a/src/commands/sync_date_of_birth.rs +++ b/src/commands/sync_date_of_birth.rs @@ -1,30 +1,37 @@ use std::{fs, path::PathBuf}; use chrono::{Datelike, NaiveDate}; -use color_eyre::eyre::{ContextCompat, Result}; +use color_eyre::eyre::{Context as _, ContextCompat, Result}; use log::*; -use uuid::Uuid; use vcard4::{ parse_loose, property::{DateAndOrTime, DateTimeOrTextProperty}, }; use crate::{ + actions::{ + action::Action, + fetch_all_persons::FetchAllPersons, + update_person_date_of_birth::{UpdatePersonDateOfBirth, UpdatePersonDateOfBirthArgs}, + }, context::Context, - types::{PersonResponseDto, PersonUpdateDto}, - utils::people::fetch_all_contacts, - Client, }; pub async fn sync_date_of_birth(ctx: Context, vcard_file: &PathBuf) -> Result<()> { - let server_contacts = fetch_all_contacts(&ctx.client).await?; + let fetch_action = FetchAllPersons::new(()); + let server_contacts = fetch_action.execute(&ctx).await?; let filtered_contacts: Vec<_> = server_contacts .iter() .filter(|p| !p.name.is_empty()) .collect(); - let contacts = fs::read_to_string(vcard_file)?; + let contacts = fs::read_to_string(vcard_file).wrap_err_with(|| { + format!( + "Unable to read vcard file at {}", + vcard_file.to_string_lossy() + ) + })?; let cards = parse_loose(contacts)?; let cards_with_bday: Vec<_> = cards.iter().filter(|c| c.bday.is_some()).collect(); @@ -46,13 +53,21 @@ pub async fn sync_date_of_birth(ctx: Context, vcard_file: &PathBuf) -> Result<() Some(c) => match card.bday.as_ref().unwrap() { DateTimeOrTextProperty::DateTime(dt) => { let bday = vcard_date_to_naive_date(dt.value.first().unwrap().clone())?; - if c.birth_date.as_ref().map_or(false, |bdate| bday == *bdate) { + if c.date_of_birth + .as_ref() + .map_or(false, |bdate| bday == *bdate) + { debug!( "{} already has the proper date of birth, skipping", formatted_name ); } else if bday.year() > 0 { - update_person_bday(c, bday, &ctx.client, ctx.dry_run).await?; + UpdatePersonDateOfBirth::new(UpdatePersonDateOfBirthArgs { + person: (*c).clone(), + date_of_birth: bday, + }) + .execute(&ctx) + .await?; updated_dobs += 1; } else { debug!( @@ -90,27 +105,3 @@ fn vcard_date_to_naive_date(src: DateAndOrTime) -> Result { DateAndOrTime::Time(_time) => todo!(), } } - -async fn update_person_bday( - person: &PersonResponseDto, - bday: NaiveDate, - client: &Client, - dry_run: bool, -) -> Result<()> { - let update = PersonUpdateDto { - feature_face_asset_id: None, - is_hidden: None, - name: None, - birth_date: Some(bday), - }; - - info!("Updating {}: {:?}", person.name, update); - - if !dry_run { - client - .update_person(&Uuid::parse_str(&person.id)?, &update) - .await?; - } - - Ok(()) -} diff --git a/src/main.rs b/src/main.rs index 0c3d278..bff9e03 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,10 +20,12 @@ use figment_file_provider_adapter::FileAdapter; use log::*; use reqwest::header; +mod actions; mod args; mod commands; mod config; mod context; +mod models; mod utils; #[tokio::main] diff --git a/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..4a45102 --- /dev/null +++ b/src/models/mod.rs @@ -0,0 +1 @@ +pub mod person; diff --git a/src/models/person.rs b/src/models/person.rs new file mode 100644 index 0000000..2ec56c4 --- /dev/null +++ b/src/models/person.rs @@ -0,0 +1,21 @@ +use chrono::NaiveDate; +use uuid::Uuid; + +use crate::types::PersonResponseDto; + +#[derive(Debug, Clone)] +pub struct Person { + pub id: Uuid, + pub name: String, + pub date_of_birth: Option, +} + +impl From for Person { + fn from(value: PersonResponseDto) -> Self { + Self { + id: Uuid::parse_str(&value.id).expect("Unable to parse a person's UUID"), + name: value.name, + date_of_birth: value.birth_date, + } + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index e53cd62..07eb6a1 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,3 +1,2 @@ pub mod albums; pub mod assets; -pub mod people; diff --git a/src/utils/people.rs b/src/utils/people.rs deleted file mode 100644 index 3fdc1a0..0000000 --- a/src/utils/people.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::{types::PersonResponseDto, Client}; -use color_eyre::eyre::Result; - -pub async fn fetch_all_contacts(client: &Client) -> Result> { - let mut all_people = Vec::new(); - let mut page_number = 1; - let mut has_next_page = true; - - while has_next_page { - let fetched = client - .get_all_people(Some(page_number.into()), None, Some(true)) - .await?; - all_people.extend(fetched.people.clone()); - has_next_page = fetched.has_next_page.expect("Missing has_next_page"); - page_number += 1; - } - - Ok(all_people) -}