refactor: split import into smaller functions
Some checks failed
Test / build (map[name:stable]) (push) Successful in 1m51s
Test / build (map[name:nightly]) (push) Successful in 2m26s
Release Please / Release-plz (push) Failing after 26s

This commit is contained in:
Paul Campbell 2024-12-20 19:56:44 +00:00
parent a7bd75a426
commit 61323484a2
9 changed files with 342 additions and 149 deletions

15
src/conversion.rs Normal file
View file

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

62
src/import/attachment.rs Normal file
View file

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

75
src/import/card.rs Normal file
View file

@ -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<NextcloudLabelTitle, Label>,
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<NextcloudCardDescription> = 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(())
}

67
src/import/label.rs Normal file
View file

@ -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<NextcloudLabelTitle, Label>,
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(())
}

View file

@ -1,25 +1,20 @@
// //
use std::path::PathBuf; use std::collections::HashMap;
use std::{collections::HashMap, ops::Deref};
use all_colors::get_color_hex;
use color_eyre::eyre::eyre; use color_eyre::eyre::eyre;
use crate::{ use crate::{
f, f,
nextcloud::model::{ nextcloud::model::{Label, NextcloudBoardTitle, NextcloudLabelTitle},
Label, NextcloudBoardTitle, NextcloudCardDescription, NextcloudCardTitle, trello::model::board::{TrelloBoardId, TrelloBoardName, TrelloBoards},
NextcloudLabelColour, NextcloudLabelTitle,
},
p,
trello::model::{
attachment::TrelloAttachmentId,
board::{TrelloBoardId, TrelloBoardName, TrelloBoards},
list::TrelloListId,
},
FullCtx, FullCtx,
}; };
mod attachment;
mod card;
mod label;
mod stack;
pub(crate) async fn run(ctx: &FullCtx) -> color_eyre::Result<()> { pub(crate) async fn run(ctx: &FullCtx) -> color_eyre::Result<()> {
// get list of trello boards // get list of trello boards
let trello_client = ctx.trello_client(); 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) inquire::MultiSelect::new("Select Trello stacks to import?", trello_stack_names)
.prompt() .prompt()
.expect("select stacks"); .expect("select stacks");
let selected_trello_stacks = trello_stacks if selected_trello_stack_names.is_empty() {
.iter()
.filter(|s| selected_trello_stack_names.contains(&s.name.as_ref()))
.collect::<Vec<_>>();
if selected_trello_stacks.is_empty() {
return Err(eyre!(f!("no stacks selected"))); return Err(eyre!(f!("no stacks selected")));
} }
@ -72,6 +63,10 @@ pub(crate) async fn run(ctx: &FullCtx) -> color_eyre::Result<()> {
.prompt() .prompt()
.map(NextcloudBoardTitle::new) .map(NextcloudBoardTitle::new)
.expect("select board"); .expect("select board");
// Start importing
// get the id of the selected board
let nextcloud_board_id = nextcloud_boards let nextcloud_board_id = nextcloud_boards
.iter() .iter()
.find(|b| b.title == nextcloud_board_name) .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"); .expect("find selected board");
// get list of nextcloud stacks in the selected board // get list of nextcloud stacks in the selected board
let nextcloud_stacks = deck_client.get_stacks(nextcloud_board_id).await.result?; 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 stack::create_any_missing_stacks(
.iter() ctx,
.filter(|s| !nextcloud_stacks.iter().any(|ns| ns.title.deref() == **s)) &selected_trello_stack_names,
.cloned() nextcloud_board_id,
.collect::<Vec<_>>(); nextcloud_stacks,
if !missing_stack_names.is_empty() { )
crate::p!(ctx.prt, "Missing stacks: {:?}", missing_stack_names); .await?;
// 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");
}
// - get the list of nextcloud stacks again (with new stack ids) // - get the list of nextcloud stacks again (with new stack ids)
let nextcloud_stacks = deck_client.get_stacks(nextcloud_board_id).await.result?; 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(); .collect();
// for each selected trello stack // for each selected trello stack
for selected_trello_stack in selected_trello_stacks.into_iter() { for selected_trello_stack in trello_stacks
p!(ctx.prt, "Importing stack: {}", selected_trello_stack.name); .iter()
let nextcloud_stack_id = nextcloud_stacks .filter(|s| selected_trello_stack_names.contains(&s.name.as_ref()))
.iter() .collect::<Vec<_>>()
.find(|ns| ns.title.deref() == selected_trello_stack.name.as_ref()) .into_iter()
.map(|ns| ns.id) {
.expect("find nextcloud stack"); stack::import_stack(
// - get the list of trello cards in the stack ctx,
let trello_cards = trello_client nextcloud_board_id,
.list_cards(&TrelloListId::new(selected_trello_stack.id.as_ref())) &nextcloud_stacks,
.await &mut nextcloud_labels,
.result?; selected_trello_stack,
// - for each card in the trello stack )
for trello_card in trello_cards.into_iter() { .await?;
p!(ctx.prt, "> Importing card: {}", trello_card.name);
// - - create a nextcloud card
let title: NextcloudCardTitle = NextcloudCardTitle::new(trello_card.name);
let desc: Option<NextcloudCardDescription> = 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()?;
}
}
} }
Ok(()) Ok(())

82
src/import/stack.rs Normal file
View file

@ -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<Stack>,
) -> 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::<Vec<_>>();
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<Stack>,
nextcloud_labels: &mut HashMap<NextcloudLabelTitle, Label>,
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(())
}

View file

@ -9,6 +9,7 @@ use kxio::{fs::FileSystem, net::Net, print::Printer};
mod api_result; mod api_result;
mod check; mod check;
mod config; mod config;
mod conversion;
mod execute; mod execute;
mod import; mod import;
mod init; mod init;
@ -86,11 +87,11 @@ pub(crate) struct FullCtx {
pub cfg: AppConfig, pub cfg: AppConfig,
} }
impl FullCtx { impl FullCtx {
pub(crate) fn deck_client(&self) -> DeckClient { pub(crate) const fn deck_client(&self) -> DeckClient {
DeckClient::new(self) DeckClient::new(self)
} }
pub(crate) fn trello_client(&self) -> TrelloClient { pub(crate) const fn trello_client(&self) -> TrelloClient {
TrelloClient::new(self) TrelloClient::new(self)
} }
} }

View file

@ -31,7 +31,7 @@ pub(crate) struct DeckClient<'ctx> {
// Uses the API described here: https://deck.readthedocs.io/en/stable/API/#cards // Uses the API described here: https://deck.readthedocs.io/en/stable/API/#cards
impl<'ctx> DeckClient<'ctx> { impl<'ctx> DeckClient<'ctx> {
pub fn new(ctx: &'ctx FullCtx) -> Self { pub const fn new(ctx: &'ctx FullCtx) -> Self {
Self { Self {
ctx, ctx,
hostname: &ctx.cfg.nextcloud.hostname, hostname: &ctx.cfg.nextcloud.hostname,

View file

@ -138,7 +138,7 @@ impl<'ctx> TrelloClient<'ctx> {
} }
impl TrelloClient<'_> { impl TrelloClient<'_> {
pub(crate) fn new(ctx: &FullCtx) -> TrelloClient { pub(crate) const fn new(ctx: &FullCtx) -> TrelloClient {
TrelloClient { ctx } TrelloClient { ctx }
} }
} }