From 61323484a24724450dd09cf87521fc24c02ac37e Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Fri, 20 Dec 2024 19:56:44 +0000 Subject: [PATCH] refactor: split import into smaller functions --- src/conversion.rs | 15 ++++ src/import/attachment.rs | 62 ++++++++++++++ src/import/card.rs | 75 ++++++++++++++++ src/import/label.rs | 67 +++++++++++++++ src/import/mod.rs | 181 ++++++++------------------------------- src/import/stack.rs | 82 ++++++++++++++++++ src/lib.rs | 5 +- src/nextcloud/client.rs | 2 +- src/trello/client.rs | 2 +- 9 files changed, 342 insertions(+), 149 deletions(-) create mode 100644 src/conversion.rs create mode 100644 src/import/attachment.rs create mode 100644 src/import/card.rs create mode 100644 src/import/label.rs create mode 100644 src/import/stack.rs diff --git a/src/conversion.rs b/src/conversion.rs new file mode 100644 index 0000000..e45d16a --- /dev/null +++ b/src/conversion.rs @@ -0,0 +1,15 @@ +// +use crate::nextcloud::model::{NextcloudCardDescription, NextcloudCardTitle}; +use crate::trello::model::card::{TrelloCardDescription, TrelloCardName}; + +impl From<&TrelloCardName> for NextcloudCardTitle { + fn from(value: &TrelloCardName) -> Self { + Self::new(value.to_string()) + } +} + +impl From<&TrelloCardDescription> for NextcloudCardDescription { + fn from(value: &TrelloCardDescription) -> Self { + Self::new(value.to_string()) + } +} diff --git a/src/import/attachment.rs b/src/import/attachment.rs new file mode 100644 index 0000000..03d7662 --- /dev/null +++ b/src/import/attachment.rs @@ -0,0 +1,62 @@ +// +use std::path::PathBuf; + +use crate::{ + nextcloud::{ + client::DeckClient, + model::{Card, NextcloudBoardId, NextcloudStackId}, + }, + p, + trello::{ + client::TrelloClient, + model::{ + attachment::{TrelloAttachment, TrelloAttachmentId}, + card::TrelloShortCard, + }, + }, + FullCtx, +}; + +pub(super) async fn import_attachment( + ctx: &FullCtx, + nextcloud_board_id: NextcloudBoardId, + nextcloud_stack_id: NextcloudStackId, + trello_card: &TrelloShortCard, + nextcloud_card: &Card, + trello_attachment: &TrelloAttachment, +) -> color_eyre::Result<()> { + let trello_client: TrelloClient = ctx.trello_client(); + + let deck_client: DeckClient = ctx.deck_client(); + + p!(ctx.prt, ">> Adding attachment: {}", trello_attachment.name); + // - - - download the attachment from trello + let attachment_path = trello_client + .save_attachment( + &trello_card.id, + &TrelloAttachmentId::new(&trello_attachment.id), + Some(&PathBuf::from(&trello_attachment.id)), + ) + .await?; + let attachment_file = ctx.fs.file(&attachment_path); + // - - - upload the attachment to nextcloud card + let attachment = deck_client + .add_attachment_to_card( + nextcloud_board_id, + nextcloud_stack_id, + nextcloud_card.id, + &attachment_file, + ) + .await + .result?; + p!( + ctx.prt, + ">> Attachment added: {}:{}", + attachment.id, + attachment.attachment_type, + // attachment.extended_data.mimetype + ); + // delete local copy of attachment + attachment_file.remove()?; + Ok(()) +} diff --git a/src/import/card.rs b/src/import/card.rs new file mode 100644 index 0000000..f515da5 --- /dev/null +++ b/src/import/card.rs @@ -0,0 +1,75 @@ +// +use std::collections::HashMap; + +use crate::{ + import::{attachment, label}, + nextcloud::{ + client::DeckClient, + model::{ + Label, NextcloudBoardId, NextcloudCardDescription, NextcloudCardTitle, + NextcloudLabelTitle, NextcloudStackId, + }, + }, + p, + trello::{client::TrelloClient, model::card::TrelloShortCard}, + FullCtx, +}; + +pub(super) async fn import_card( + ctx: &FullCtx, + nextcloud_board_id: NextcloudBoardId, + nextcloud_labels: &mut HashMap, + nextcloud_stack_id: NextcloudStackId, + trello_card: &TrelloShortCard, +) -> color_eyre::Result<()> { + let trello_client: TrelloClient = ctx.trello_client(); + + let deck_client: DeckClient = ctx.deck_client(); + + p!(ctx.prt, "> Importing card: {}", trello_card.name); + // - - create a nextcloud card + let title = NextcloudCardTitle::from(&trello_card.name); + let desc: Option = match trello_card.desc.len() { + 0 => None, + _ => Some(NextcloudCardDescription::from(&trello_card.desc)), + }; + let nextcloud_card = deck_client + .create_card( + nextcloud_board_id, + nextcloud_stack_id, + &title, + desc.as_ref(), + ) + .await + .result?; + // - - for each label on the trello card + for trello_label in trello_card.labels.iter() { + label::import_label( + ctx, + nextcloud_board_id, + nextcloud_labels, + nextcloud_stack_id, + &nextcloud_card, + trello_label, + ) + .await?; + } + // - - for each attachment on the trello card + let attachments = trello_client + .card(&trello_card.id) + .await + .result? + .attachments; + for trello_attachment in attachments.iter() { + attachment::import_attachment( + ctx, + nextcloud_board_id, + nextcloud_stack_id, + trello_card, + &nextcloud_card, + trello_attachment, + ) + .await?; + } + Ok(()) +} diff --git a/src/import/label.rs b/src/import/label.rs new file mode 100644 index 0000000..c3933d3 --- /dev/null +++ b/src/import/label.rs @@ -0,0 +1,67 @@ +// +use std::collections::HashMap; + +use all_colors::get_color_hex; + +use crate::{ + nextcloud::{ + client::DeckClient, + model::{ + Card, Label, NextcloudBoardId, NextcloudLabelColour, NextcloudLabelTitle, + NextcloudStackId, + }, + }, + p, + trello::model::label::TrelloLabel, + FullCtx, +}; + +pub(super) async fn import_label( + ctx: &FullCtx, + nextcloud_board_id: NextcloudBoardId, + nextcloud_labels: &mut HashMap, + nextcloud_stack_id: NextcloudStackId, + nextcloud_card: &Card, + trello_label: &TrelloLabel, +) -> color_eyre::Result<()> { + let deck_client: DeckClient = ctx.deck_client(); + + p!( + ctx.prt, + ">> Adding label: {} ({})", + trello_label.name, + trello_label.color + ); + // - - - find the equivalent label in nextcloud + let nextcloud_label: &Label = + match nextcloud_labels.get(&NextcloudLabelTitle::new(trello_label.name.as_ref())) { + Some(label) => label, + None => { + p!(ctx.prt, ">> Label not found in nextcloud board, creating"); + + let label = deck_client + .create_label( + nextcloud_board_id, + &NextcloudLabelTitle::new(trello_label.name.as_ref()), + &NextcloudLabelColour::new(get_color_hex(trello_label.color.as_ref())), + ) + .await + .result?; + nextcloud_labels.insert(label.title.clone(), label); + nextcloud_labels + .get(&NextcloudLabelTitle::new(trello_label.name.as_ref())) + .expect("label was just inserted") + } + }; + // - - - add the label to the nextcloud card + deck_client + .add_label_to_card( + nextcloud_board_id, + nextcloud_stack_id, + nextcloud_card.id, + nextcloud_label.id, + ) + .await + .result?; + Ok(()) +} diff --git a/src/import/mod.rs b/src/import/mod.rs index 02bd699..302a7a5 100644 --- a/src/import/mod.rs +++ b/src/import/mod.rs @@ -1,25 +1,20 @@ // -use std::path::PathBuf; -use std::{collections::HashMap, ops::Deref}; +use std::collections::HashMap; -use all_colors::get_color_hex; use color_eyre::eyre::eyre; use crate::{ f, - nextcloud::model::{ - Label, NextcloudBoardTitle, NextcloudCardDescription, NextcloudCardTitle, - NextcloudLabelColour, NextcloudLabelTitle, - }, - p, - trello::model::{ - attachment::TrelloAttachmentId, - board::{TrelloBoardId, TrelloBoardName, TrelloBoards}, - list::TrelloListId, - }, + nextcloud::model::{Label, NextcloudBoardTitle, NextcloudLabelTitle}, + trello::model::board::{TrelloBoardId, TrelloBoardName, TrelloBoards}, FullCtx, }; +mod attachment; +mod card; +mod label; +mod stack; + pub(crate) async fn run(ctx: &FullCtx) -> color_eyre::Result<()> { // get list of trello boards let trello_client = ctx.trello_client(); @@ -50,11 +45,7 @@ pub(crate) async fn run(ctx: &FullCtx) -> color_eyre::Result<()> { inquire::MultiSelect::new("Select Trello stacks to import?", trello_stack_names) .prompt() .expect("select stacks"); - let selected_trello_stacks = trello_stacks - .iter() - .filter(|s| selected_trello_stack_names.contains(&s.name.as_ref())) - .collect::>(); - if selected_trello_stacks.is_empty() { + if selected_trello_stack_names.is_empty() { return Err(eyre!(f!("no stacks selected"))); } @@ -72,6 +63,10 @@ pub(crate) async fn run(ctx: &FullCtx) -> color_eyre::Result<()> { .prompt() .map(NextcloudBoardTitle::new) .expect("select board"); + + // Start importing + + // get the id of the selected board let nextcloud_board_id = nextcloud_boards .iter() .find(|b| b.title == nextcloud_board_name) @@ -79,26 +74,15 @@ pub(crate) async fn run(ctx: &FullCtx) -> color_eyre::Result<()> { .expect("find selected board"); // get list of nextcloud stacks in the selected board let nextcloud_stacks = deck_client.get_stacks(nextcloud_board_id).await.result?; - // identify any stacks by name from those selected in trello that are missing in nextcloud - let missing_stack_names = selected_trello_stack_names - .iter() - .filter(|s| !nextcloud_stacks.iter().any(|ns| ns.title.deref() == **s)) - .cloned() - .collect::>(); - if !missing_stack_names.is_empty() { - crate::p!(ctx.prt, "Missing stacks: {:?}", missing_stack_names); - // create any missing stacks in nextcloud - // for each missing stack - for missing_stack_name in missing_stack_names.into_iter() { - // - create the stack - let stack = deck_client - .create_stack(nextcloud_board_id, &missing_stack_name.clone().into()) - .await - .result?; - p!(ctx.prt, "Created stack: {}", stack.title); - } - p!(ctx.prt, "Stacks created"); - } + + stack::create_any_missing_stacks( + ctx, + &selected_trello_stack_names, + nextcloud_board_id, + nextcloud_stacks, + ) + .await?; + // - get the list of nextcloud stacks again (with new stack ids) let nextcloud_stacks = deck_client.get_stacks(nextcloud_board_id).await.result?; @@ -112,113 +96,20 @@ pub(crate) async fn run(ctx: &FullCtx) -> color_eyre::Result<()> { .collect(); // for each selected trello stack - for selected_trello_stack in selected_trello_stacks.into_iter() { - p!(ctx.prt, "Importing stack: {}", selected_trello_stack.name); - let nextcloud_stack_id = nextcloud_stacks - .iter() - .find(|ns| ns.title.deref() == selected_trello_stack.name.as_ref()) - .map(|ns| ns.id) - .expect("find nextcloud stack"); - // - get the list of trello cards in the stack - let trello_cards = trello_client - .list_cards(&TrelloListId::new(selected_trello_stack.id.as_ref())) - .await - .result?; - // - for each card in the trello stack - for trello_card in trello_cards.into_iter() { - p!(ctx.prt, "> Importing card: {}", trello_card.name); - // - - create a nextcloud card - let title: NextcloudCardTitle = NextcloudCardTitle::new(trello_card.name); - let desc: Option = match trello_card.desc.len() { - 0 => None, - _ => Some(NextcloudCardDescription::new(trello_card.desc)), - }; - let nextcloud_card = deck_client - .create_card( - nextcloud_board_id, - nextcloud_stack_id, - &title, - desc.as_ref(), - ) - .await - .result?; - // - - for each label on the trello card - for trello_label in trello_card.labels.iter() { - p!( - ctx.prt, - ">> Adding label: {} ({})", - trello_label.name, - trello_label.color - ); - // - - - find the equivalent label in nextcloud - let nextcloud_label: &Label = match nextcloud_labels - .get(&NextcloudLabelTitle::new(trello_label.name.as_ref())) - { - Some(label) => label, - None => { - p!(ctx.prt, ">> Label not found in nextcloud board, creating"); - - let label = deck_client - .create_label( - nextcloud_board_id, - &NextcloudLabelTitle::new(trello_label.name.as_ref()), - &NextcloudLabelColour::new(get_color_hex( - trello_label.color.as_ref(), - )), - ) - .await - .result?; - nextcloud_labels.insert(label.title.clone(), label); - nextcloud_labels - .get(&NextcloudLabelTitle::new(trello_label.name.as_ref())) - .expect("label was just inserted") - } - }; - // - - - add the label to the nextcloud card - deck_client - .add_label_to_card( - nextcloud_board_id, - nextcloud_stack_id, - nextcloud_card.id, - nextcloud_label.id, - ) - .await - .result?; - } - // - - for each attachment on the trello card - let full_card = trello_client.card(&trello_card.id).await.result?; - for trello_attachment in full_card.attachments.iter() { - p!(ctx.prt, ">> Adding attachment: {}", trello_attachment.name); - // - - - download the attachment from trello - let attachment_path = trello_client - .save_attachment( - &trello_card.id, - &TrelloAttachmentId::new(&trello_attachment.id), - Some(&PathBuf::from(&trello_attachment.id)), - ) - .await?; - let attachment_file = ctx.fs.file(&attachment_path); - // - - - upload the attachment to nextcloud card - let attachment = deck_client - .add_attachment_to_card( - nextcloud_board_id, - nextcloud_stack_id, - nextcloud_card.id, - &attachment_file, - ) - .await - .result?; - p!( - ctx.prt, - ">> Attachment added: {}:{}", - attachment.id, - attachment.attachment_type, - // attachment.extended_data.mimetype - ); - // delete local copy of attachment - attachment_file.remove()?; - } - } + for selected_trello_stack in trello_stacks + .iter() + .filter(|s| selected_trello_stack_names.contains(&s.name.as_ref())) + .collect::>() + .into_iter() + { + stack::import_stack( + ctx, + nextcloud_board_id, + &nextcloud_stacks, + &mut nextcloud_labels, + selected_trello_stack, + ) + .await?; } Ok(()) diff --git a/src/import/stack.rs b/src/import/stack.rs new file mode 100644 index 0000000..a66293a --- /dev/null +++ b/src/import/stack.rs @@ -0,0 +1,82 @@ +// +use std::{collections::HashMap, ops::Deref}; + +use crate::{ + import::card, + nextcloud::{ + client::DeckClient, + model::{Label, NextcloudBoardId, NextcloudLabelTitle, Stack}, + }, + p, + trello::{ + client::TrelloClient, + model::list::{TrelloList, TrelloListId}, + }, + FullCtx, +}; + +pub(super) async fn create_any_missing_stacks( + ctx: &FullCtx, + selected_trello_stack_names: &Vec<&String>, + nextcloud_board_id: NextcloudBoardId, + nextcloud_stacks: Vec, +) -> color_eyre::Result<()> { + let deck_client: DeckClient = ctx.deck_client(); + + // identify any stacks by name from those selected in trello that are missing in nextcloud + let missing_stack_names = selected_trello_stack_names + .iter() + .filter(|s| !nextcloud_stacks.iter().any(|ns| ns.title.deref() == **s)) + .cloned() + .collect::>(); + if !missing_stack_names.is_empty() { + crate::p!(ctx.prt, "Missing stacks: {:?}", missing_stack_names); + // create any missing stacks in nextcloud + // for each missing stack + for missing_stack_name in missing_stack_names.into_iter() { + // - create the stack + let stack = deck_client + .create_stack(nextcloud_board_id, &missing_stack_name.clone().into()) + .await + .result?; + p!(ctx.prt, "Created stack: {}", stack.title); + } + p!(ctx.prt, "Stacks created"); + } + Ok(()) +} + +pub(super) async fn import_stack( + ctx: &FullCtx, + nextcloud_board_id: NextcloudBoardId, + nextcloud_stacks: &Vec, + nextcloud_labels: &mut HashMap, + selected_trello_stack: &TrelloList, +) -> color_eyre::Result<()> { + let trello_client: TrelloClient = ctx.trello_client(); + + p!(ctx.prt, "Importing stack: {}", selected_trello_stack.name); + let nextcloud_stack_id = nextcloud_stacks + .iter() + .find(|ns| ns.title.deref() == selected_trello_stack.name.as_ref()) + .map(|ns| ns.id) + .expect("find nextcloud stack"); + // - get the list of trello cards in the stack + let trello_cards = trello_client + .list_cards(&TrelloListId::new(selected_trello_stack.id.as_ref())) + .await + .result?; + + // - for each card in the trello stack + for trello_card in trello_cards.into_iter() { + card::import_card( + ctx, + nextcloud_board_id, + nextcloud_labels, + nextcloud_stack_id, + &trello_card, + ) + .await?; + } + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index cb347cd..295d549 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ use kxio::{fs::FileSystem, net::Net, print::Printer}; mod api_result; mod check; mod config; +mod conversion; mod execute; mod import; mod init; @@ -86,11 +87,11 @@ pub(crate) struct FullCtx { pub cfg: AppConfig, } impl FullCtx { - pub(crate) fn deck_client(&self) -> DeckClient { + pub(crate) const fn deck_client(&self) -> DeckClient { DeckClient::new(self) } - pub(crate) fn trello_client(&self) -> TrelloClient { + pub(crate) const fn trello_client(&self) -> TrelloClient { TrelloClient::new(self) } } diff --git a/src/nextcloud/client.rs b/src/nextcloud/client.rs index f442018..3332883 100644 --- a/src/nextcloud/client.rs +++ b/src/nextcloud/client.rs @@ -31,7 +31,7 @@ pub(crate) struct DeckClient<'ctx> { // Uses the API described here: https://deck.readthedocs.io/en/stable/API/#cards impl<'ctx> DeckClient<'ctx> { - pub fn new(ctx: &'ctx FullCtx) -> Self { + pub const fn new(ctx: &'ctx FullCtx) -> Self { Self { ctx, hostname: &ctx.cfg.nextcloud.hostname, diff --git a/src/trello/client.rs b/src/trello/client.rs index b82b042..b100f9c 100644 --- a/src/trello/client.rs +++ b/src/trello/client.rs @@ -138,7 +138,7 @@ impl<'ctx> TrelloClient<'ctx> { } impl TrelloClient<'_> { - pub(crate) fn new(ctx: &FullCtx) -> TrelloClient { + pub(crate) const fn new(ctx: &FullCtx) -> TrelloClient { TrelloClient { ctx } } }