PoC of refactoring actions
This commit is contained in:
		
							parent
							
								
									6ee741fd5f
								
							
						
					
					
						commit
						e630997ff5
					
				
					 11 changed files with 158 additions and 56 deletions
				
			
		
							
								
								
									
										11
									
								
								src/actions/action.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/actions/action.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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<Self::Output>;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								src/actions/fetch_all_persons.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/actions/fetch_all_persons.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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<Person>;
 | 
			
		||||
 | 
			
		||||
    fn new(_input: Self::Input) -> Self {
 | 
			
		||||
        Self {}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn describe(&self) -> String {
 | 
			
		||||
        String::from("Fetching all persons")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn execute(&self, ctx: &Context) -> Result<Self::Output> {
 | 
			
		||||
        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())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								src/actions/mod.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/actions/mod.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
pub mod action;
 | 
			
		||||
pub mod fetch_all_persons;
 | 
			
		||||
pub mod update_person_date_of_birth;
 | 
			
		||||
							
								
								
									
										51
									
								
								src/actions/update_person_date_of_birth.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/actions/update_person_date_of_birth.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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<Self::Output> {
 | 
			
		||||
        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(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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::<Vec<_>>();
 | 
			
		||||
 | 
			
		||||
    filtered_contacts.sort_by_key(|c| c.name.clone());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<NaiveDate> {
 | 
			
		|||
        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(())
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										1
									
								
								src/models/mod.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/models/mod.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
pub mod person;
 | 
			
		||||
							
								
								
									
										21
									
								
								src/models/person.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/models/person.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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<NaiveDate>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<PersonResponseDto> 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,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,3 +1,2 @@
 | 
			
		|||
pub mod albums;
 | 
			
		||||
pub mod assets;
 | 
			
		||||
pub mod people;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,19 +0,0 @@
 | 
			
		|||
use crate::{types::PersonResponseDto, Client};
 | 
			
		||||
use color_eyre::eyre::Result;
 | 
			
		||||
 | 
			
		||||
pub async fn fetch_all_contacts(client: &Client) -> Result<Vec<PersonResponseDto>> {
 | 
			
		||||
    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)
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue