PoC of refactoring actions

This commit is contained in:
Marc Plano-Lesay 2024-11-06 11:05:49 +11:00
parent 6ee741fd5f
commit e630997ff5
Signed by: kernald
GPG key ID: 66A41B08CC62A6CF
11 changed files with 158 additions and 56 deletions

11
src/actions/action.rs Normal file
View 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>;
}

View 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
View file

@ -0,0 +1,3 @@
pub mod action;
pub mod fetch_all_persons;
pub mod update_person_date_of_birth;

View 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(())
}
}

View file

@ -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 color_eyre::eyre::Result;
use log::*; use log::*;
pub async fn missing_date_of_birth(ctx: Context) -> Result<()> { 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 let mut filtered_contacts = contacts
.iter() .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<_>>(); .collect::<Vec<_>>();
filtered_contacts.sort_by_key(|c| c.name.clone()); filtered_contacts.sort_by_key(|c| c.name.clone());

View file

@ -1,30 +1,37 @@
use std::{fs, path::PathBuf}; use std::{fs, path::PathBuf};
use chrono::{Datelike, NaiveDate}; use chrono::{Datelike, NaiveDate};
use color_eyre::eyre::{ContextCompat, Result}; use color_eyre::eyre::{Context as _, ContextCompat, Result};
use log::*; use log::*;
use uuid::Uuid;
use vcard4::{ use vcard4::{
parse_loose, parse_loose,
property::{DateAndOrTime, DateTimeOrTextProperty}, property::{DateAndOrTime, DateTimeOrTextProperty},
}; };
use crate::{ use crate::{
actions::{
action::Action,
fetch_all_persons::FetchAllPersons,
update_person_date_of_birth::{UpdatePersonDateOfBirth, UpdatePersonDateOfBirthArgs},
},
context::Context, context::Context,
types::{PersonResponseDto, PersonUpdateDto},
utils::people::fetch_all_contacts,
Client,
}; };
pub async fn sync_date_of_birth(ctx: Context, vcard_file: &PathBuf) -> Result<()> { 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 let filtered_contacts: Vec<_> = server_contacts
.iter() .iter()
.filter(|p| !p.name.is_empty()) .filter(|p| !p.name.is_empty())
.collect(); .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 = parse_loose(contacts)?;
let cards_with_bday: Vec<_> = cards.iter().filter(|c| c.bday.is_some()).collect(); 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() { Some(c) => match card.bday.as_ref().unwrap() {
DateTimeOrTextProperty::DateTime(dt) => { DateTimeOrTextProperty::DateTime(dt) => {
let bday = vcard_date_to_naive_date(dt.value.first().unwrap().clone())?; 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!( debug!(
"{} already has the proper date of birth, skipping", "{} already has the proper date of birth, skipping",
formatted_name formatted_name
); );
} else if bday.year() > 0 { } 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; updated_dobs += 1;
} else { } else {
debug!( debug!(
@ -90,27 +105,3 @@ fn vcard_date_to_naive_date(src: DateAndOrTime) -> Result<NaiveDate> {
DateAndOrTime::Time(_time) => todo!(), 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(())
}

View file

@ -20,10 +20,12 @@ use figment_file_provider_adapter::FileAdapter;
use log::*; use log::*;
use reqwest::header; use reqwest::header;
mod actions;
mod args; mod args;
mod commands; mod commands;
mod config; mod config;
mod context; mod context;
mod models;
mod utils; mod utils;
#[tokio::main] #[tokio::main]

1
src/models/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod person;

21
src/models/person.rs Normal file
View 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,
}
}
}

View file

@ -1,3 +1,2 @@
pub mod albums; pub mod albums;
pub mod assets; pub mod assets;
pub mod people;

View file

@ -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)
}