feat: add command 'import'
This commit is contained in:
parent
e8c03ee925
commit
a7bd75a426
14 changed files with 260 additions and 158 deletions
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
all-colors = "0.0"
|
||||||
bytes = "1.9"
|
bytes = "1.9"
|
||||||
clap = { version = "4.5", features = ["cargo", "derive"] }
|
clap = { version = "4.5", features = ["cargo", "derive"] }
|
||||||
color-eyre = "0.6"
|
color-eyre = "0.6"
|
||||||
|
@ -13,6 +14,7 @@ derive_more = { version = "1.0", features = [
|
||||||
"deref",
|
"deref",
|
||||||
"from",
|
"from",
|
||||||
] }
|
] }
|
||||||
|
inquire = "0.7"
|
||||||
# kxio = {path = "../kxio/"}
|
# kxio = {path = "../kxio/"}
|
||||||
kxio = "4.0"
|
kxio = "4.0"
|
||||||
reqwest = { version = "0.12" , features = ["multipart", "stream"]}
|
reqwest = { version = "0.12" , features = ["multipart", "stream"]}
|
||||||
|
|
|
@ -13,7 +13,7 @@ impl Execute for crate::Command {
|
||||||
match self {
|
match self {
|
||||||
Self::Init => Err(eyre!("Config file already exists. Not overwriting it.")),
|
Self::Init => Err(eyre!("Config file already exists. Not overwriting it.")),
|
||||||
Self::Check => crate::check::run(ctx).await,
|
Self::Check => crate::check::run(ctx).await,
|
||||||
Self::Import => todo!(), //crate::import::run(ctx).await,
|
Self::Import => crate::import::run(ctx).await,
|
||||||
Self::Trello(cmd) => cmd.execute(ctx).await,
|
Self::Trello(cmd) => cmd.execute(ctx).await,
|
||||||
Self::Nextcloud(cmd) => cmd.execute(ctx).await,
|
Self::Nextcloud(cmd) => cmd.execute(ctx).await,
|
||||||
}
|
}
|
||||||
|
|
225
src/import/mod.rs
Normal file
225
src/import/mod.rs
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
//
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::{collections::HashMap, ops::Deref};
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
FullCtx,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(crate) async fn run(ctx: &FullCtx) -> color_eyre::Result<()> {
|
||||||
|
// get list of trello boards
|
||||||
|
let trello_client = ctx.trello_client();
|
||||||
|
let trello_boards = trello_client.boards().await.result?;
|
||||||
|
// prompt user to select a board
|
||||||
|
let trello_board_name = inquire::Select::new(
|
||||||
|
"Select a source Trello board?",
|
||||||
|
trello_boards
|
||||||
|
.iter()
|
||||||
|
.map(|b| b.name.as_ref())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.prompt()
|
||||||
|
.map(TrelloBoardName::new)?;
|
||||||
|
let trello_board_id = trello_boards
|
||||||
|
.find_by_name(&trello_board_name)
|
||||||
|
.map(|b| b.id.as_ref())
|
||||||
|
.map(TrelloBoardId::new)
|
||||||
|
.expect("find selected board");
|
||||||
|
// get list of trello stacks for the selected board
|
||||||
|
let trello_stacks = trello_client.board(&trello_board_id).await.result?.lists;
|
||||||
|
// prompt user to select some stacks
|
||||||
|
let trello_stack_names = trello_stacks
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.name.as_ref())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let selected_trello_stack_names =
|
||||||
|
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::<Vec<_>>();
|
||||||
|
if selected_trello_stacks.is_empty() {
|
||||||
|
return Err(eyre!(f!("no stacks selected")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// get list of nextcloud boards
|
||||||
|
let deck_client = ctx.deck_client();
|
||||||
|
let nextcloud_boards = deck_client.get_boards().await.result?;
|
||||||
|
// prompt user to select a board
|
||||||
|
let nextcloud_board_name = inquire::Select::new(
|
||||||
|
"Select a destination Nextcloud board?",
|
||||||
|
nextcloud_boards
|
||||||
|
.iter()
|
||||||
|
.map(|b| b.title.as_ref())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.prompt()
|
||||||
|
.map(NextcloudBoardTitle::new)
|
||||||
|
.expect("select board");
|
||||||
|
let nextcloud_board_id = nextcloud_boards
|
||||||
|
.iter()
|
||||||
|
.find(|b| b.title == nextcloud_board_name)
|
||||||
|
.map(|b| b.id)
|
||||||
|
.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::<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");
|
||||||
|
}
|
||||||
|
// - get the list of nextcloud stacks again (with new stack ids)
|
||||||
|
let nextcloud_stacks = deck_client.get_stacks(nextcloud_board_id).await.result?;
|
||||||
|
|
||||||
|
let mut nextcloud_labels: HashMap<NextcloudLabelTitle, Label> = deck_client
|
||||||
|
.get_board(nextcloud_board_id)
|
||||||
|
.await
|
||||||
|
.result?
|
||||||
|
.labels
|
||||||
|
.iter()
|
||||||
|
.map(|l| (l.title.clone(), l.clone()))
|
||||||
|
.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<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(())
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ mod api_result;
|
||||||
mod check;
|
mod check;
|
||||||
mod config;
|
mod config;
|
||||||
mod execute;
|
mod execute;
|
||||||
|
mod import;
|
||||||
mod init;
|
mod init;
|
||||||
mod macros;
|
mod macros;
|
||||||
mod nextcloud;
|
mod nextcloud;
|
||||||
|
|
|
@ -139,7 +139,7 @@ impl Execute for NextcloudCardCommand {
|
||||||
(*board_id).into(),
|
(*board_id).into(),
|
||||||
(*stack_id).into(),
|
(*stack_id).into(),
|
||||||
(*card_id).into(),
|
(*card_id).into(),
|
||||||
ctx.fs.file(file),
|
&ctx.fs.file(file),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
if *dump {
|
if *dump {
|
||||||
|
@ -148,12 +148,12 @@ impl Execute for NextcloudCardCommand {
|
||||||
let attachment = api_result.result?;
|
let attachment = api_result.result?;
|
||||||
p!(
|
p!(
|
||||||
ctx.prt,
|
ctx.prt,
|
||||||
"{}:{}:{}:{}:{}",
|
"{}:{}:{}:{}",
|
||||||
board_id,
|
board_id,
|
||||||
stack_id,
|
stack_id,
|
||||||
card_id,
|
card_id,
|
||||||
attachment.id,
|
attachment.id,
|
||||||
attachment.extended_data.path
|
// attachment.extended_data.path
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -238,7 +238,7 @@ impl<'ctx> DeckClient<'ctx> {
|
||||||
board_id: NextcloudBoardId,
|
board_id: NextcloudBoardId,
|
||||||
stack_id: NextcloudStackId,
|
stack_id: NextcloudStackId,
|
||||||
card_id: NextcloudCardId,
|
card_id: NextcloudCardId,
|
||||||
file: FileHandle,
|
file: &FileHandle,
|
||||||
) -> APIResult<Attachment> {
|
) -> APIResult<Attachment> {
|
||||||
let form: multipart::Form = multipart::Form::new();
|
let form: multipart::Form = multipart::Form::new();
|
||||||
let form = form.text("type", "file");
|
let form = form.text("type", "file");
|
||||||
|
|
|
@ -60,6 +60,7 @@ newtype!(
|
||||||
i64,
|
i64,
|
||||||
Copy,
|
Copy,
|
||||||
Display,
|
Display,
|
||||||
|
Hash,
|
||||||
PartialOrd,
|
PartialOrd,
|
||||||
Ord,
|
Ord,
|
||||||
"ID of a Nextcloud Label"
|
"ID of a Nextcloud Label"
|
||||||
|
@ -68,6 +69,7 @@ newtype!(
|
||||||
NextcloudLabelTitle,
|
NextcloudLabelTitle,
|
||||||
String,
|
String,
|
||||||
Display,
|
Display,
|
||||||
|
Hash,
|
||||||
PartialOrd,
|
PartialOrd,
|
||||||
Ord,
|
Ord,
|
||||||
"Title of a Nextcloud Label"
|
"Title of a Nextcloud Label"
|
||||||
|
@ -76,6 +78,7 @@ newtype!(
|
||||||
NextcloudLabelColour,
|
NextcloudLabelColour,
|
||||||
String,
|
String,
|
||||||
Display,
|
Display,
|
||||||
|
Hash,
|
||||||
PartialOrd,
|
PartialOrd,
|
||||||
Ord,
|
Ord,
|
||||||
"Colour of a Nextcloud Label"
|
"Colour of a Nextcloud Label"
|
||||||
|
@ -136,24 +139,11 @@ newtype!(
|
||||||
"Description of the Card"
|
"Description of the Card"
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub(crate) struct NextcloudBoardOwner {
|
|
||||||
#[serde(rename = "primaryKey")]
|
|
||||||
pub(crate) primary_key: String,
|
|
||||||
pub(crate) uid: String,
|
|
||||||
#[serde(rename = "displayname")]
|
|
||||||
pub(crate) display_name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub(crate) struct Board {
|
pub(crate) struct Board {
|
||||||
pub(crate) id: NextcloudBoardId,
|
pub(crate) id: NextcloudBoardId,
|
||||||
pub(crate) title: NextcloudBoardTitle,
|
pub(crate) title: NextcloudBoardTitle,
|
||||||
pub(crate) owner: NextcloudBoardOwner,
|
|
||||||
pub(crate) color: NextcloudBoardColour,
|
|
||||||
pub(crate) archived: bool,
|
|
||||||
pub(crate) labels: Vec<Label>,
|
pub(crate) labels: Vec<Label>,
|
||||||
pub(crate) acl: Vec<Acl>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
@ -161,10 +151,6 @@ pub(crate) struct Stack {
|
||||||
pub(crate) id: NextcloudStackId,
|
pub(crate) id: NextcloudStackId,
|
||||||
pub(crate) title: NextcloudStackTitle,
|
pub(crate) title: NextcloudStackTitle,
|
||||||
pub(crate) order: NextcloudOrder,
|
pub(crate) order: NextcloudOrder,
|
||||||
#[serde(rename = "boardId")]
|
|
||||||
pub(crate) board_id: NextcloudBoardId,
|
|
||||||
#[serde(rename = "ETag")]
|
|
||||||
pub(crate) etag: NextcloudETag,
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub(crate) cards: Vec<Card>,
|
pub(crate) cards: Vec<Card>,
|
||||||
}
|
}
|
||||||
|
@ -173,83 +159,19 @@ pub(crate) struct Stack {
|
||||||
pub(crate) struct Card {
|
pub(crate) struct Card {
|
||||||
pub(crate) id: NextcloudCardId,
|
pub(crate) id: NextcloudCardId,
|
||||||
pub(crate) title: NextcloudCardTitle,
|
pub(crate) title: NextcloudCardTitle,
|
||||||
pub(crate) description: Option<NextcloudCardDescription>,
|
|
||||||
#[serde(rename = "stackId")]
|
|
||||||
pub(crate) stack_id: NextcloudStackId,
|
|
||||||
pub(crate) order: NextcloudOrder,
|
pub(crate) order: NextcloudOrder,
|
||||||
pub(crate) archived: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub(crate) due_date: Option<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub(crate) labels: Option<Vec<Label>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub(crate) struct Label {
|
pub(crate) struct Label {
|
||||||
pub(crate) id: NextcloudLabelId,
|
pub(crate) id: NextcloudLabelId,
|
||||||
pub(crate) title: NextcloudLabelTitle,
|
pub(crate) title: NextcloudLabelTitle,
|
||||||
pub(crate) color: NextcloudLabelColour,
|
pub(crate) color: NextcloudLabelColour,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub(crate) struct Acl {
|
|
||||||
pub(crate) participant: String,
|
|
||||||
pub(crate) permission_edit: bool,
|
|
||||||
pub(crate) permission_share: bool,
|
|
||||||
pub(crate) permission_manage: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
// {"id":92,"cardId":332,"type":"file","data":"async-rust-book (2).webp","lastModified":1734506800,"createdAt":1734506800,"createdBy":"pcampbell","
|
|
||||||
// deletedAt":0,"extendedData":{"path":"\/Deck\/async-rust-book (2).webp","fileid":746424,"data":"async-rust-book (2).webp","filesize":137128,"mimetype":"image\/webp","info":{"dirname":".","basename":
|
|
||||||
// "async-rust-book (2).webp","extension":"webp","filename":"async-rust-book (2)"},"hasPreview":true,"permissions":1,"attachmentCreator":{"displayName":"Paul Campbell","id":"pcampbell","email":"pcampb
|
|
||||||
// ell@kemitix.net"}}}
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub(crate) struct Attachment {
|
pub(crate) struct Attachment {
|
||||||
pub(crate) id: i64,
|
pub(crate) id: i64,
|
||||||
#[serde(rename = "cardId")]
|
|
||||||
pub(crate) card_id: i64,
|
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub(crate) attachment_type: String,
|
pub(crate) attachment_type: String,
|
||||||
pub(crate) data: String,
|
|
||||||
#[serde(rename = "lastModified")]
|
|
||||||
pub(crate) last_modified: i64,
|
|
||||||
#[serde(rename = "createdAt")]
|
|
||||||
pub(crate) created_at: i64,
|
|
||||||
#[serde(rename = "createdBy")]
|
|
||||||
pub(crate) created_by: String,
|
|
||||||
#[serde(rename = "deletedAt")]
|
|
||||||
pub(crate) deleted_at: i64,
|
|
||||||
#[serde(rename = "extendedData")]
|
|
||||||
pub(crate) extended_data: AttachmentExtendedData,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub(crate) struct AttachmentExtendedData {
|
|
||||||
pub(crate) path: String,
|
|
||||||
pub(crate) fileid: i64,
|
|
||||||
pub(crate) data: String,
|
|
||||||
pub(crate) filesize: i64,
|
|
||||||
pub(crate) mimetype: String,
|
|
||||||
pub(crate) info: AttachmentInfo,
|
|
||||||
#[serde(rename = "hasPreview")]
|
|
||||||
pub(crate) has_preview: bool,
|
|
||||||
pub(crate) permissions: i64,
|
|
||||||
#[serde(rename = "attachmentCreator")]
|
|
||||||
pub(crate) attachment_creator: AttachmentCreator,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub(crate) struct AttachmentInfo {
|
|
||||||
pub(crate) dirname: String,
|
|
||||||
pub(crate) basename: String,
|
|
||||||
pub(crate) extension: String,
|
|
||||||
pub(crate) filename: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub(crate) struct AttachmentCreator {
|
|
||||||
#[serde(rename = "displayName")]
|
|
||||||
pub(crate) display_name: String,
|
|
||||||
pub(crate) id: String,
|
|
||||||
pub(crate) email: String,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,5 +2,4 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
mod create_label;
|
mod create_label;
|
||||||
mod get;
|
|
||||||
mod labels;
|
mod labels;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::f;
|
use crate::f;
|
||||||
use crate::nextcloud::model::{
|
use crate::nextcloud::model::{
|
||||||
Attachment, AttachmentCreator, AttachmentExtendedData, AttachmentInfo,
|
Attachment, //AttachmentCreator, //AttachmentExtendedData, AttachmentInfo,
|
||||||
};
|
};
|
||||||
use kxio::fs::TempFileSystem;
|
use kxio::fs::TempFileSystem;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
@ -45,36 +45,10 @@ fn ctx_path() -> (FullCtx, PathBuf, TempFileSystem) {
|
||||||
"{hostname}/index.php/apps/deck/api/v1.0/boards/{board_id}/stacks/{stack_id}/cards/{card_id}/attachments",
|
"{hostname}/index.php/apps/deck/api/v1.0/boards/{board_id}/stacks/{stack_id}/cards/{card_id}/attachments",
|
||||||
))
|
))
|
||||||
.respond(StatusCode::OK)
|
.respond(StatusCode::OK)
|
||||||
.body(serde_json::to_string(&Attachment {
|
.body(serde_json::to_string(&Attachment {
|
||||||
id: 102,
|
id: 102,
|
||||||
card_id: 332,
|
attachment_type: "file".to_string(),
|
||||||
attachment_type: "file".to_string(),
|
}).expect("json attachment"))
|
||||||
data: "async-rust-book.webp".to_string(),
|
|
||||||
last_modified: 1734511846,
|
|
||||||
created_at: 1734511846,
|
|
||||||
created_by: "pcampbell".to_string(),
|
|
||||||
deleted_at: 0,
|
|
||||||
extended_data: AttachmentExtendedData {
|
|
||||||
path: "/Deck/async-rust-book.webp".to_string(),
|
|
||||||
fileid: 746505,
|
|
||||||
data: "async-rust-book.webp".to_string(),
|
|
||||||
filesize: 137128,
|
|
||||||
mimetype: "image/webp".to_string(),
|
|
||||||
info: AttachmentInfo {
|
|
||||||
dirname: ".".to_string(),
|
|
||||||
basename: "async-rust-book.webp".to_string(),
|
|
||||||
extension: "webp".to_string(),
|
|
||||||
filename: "async-rust-book".to_string(),
|
|
||||||
},
|
|
||||||
has_preview: true,
|
|
||||||
permissions: 1,
|
|
||||||
attachment_creator: AttachmentCreator {
|
|
||||||
display_name: "Paul Campbell".to_string(),
|
|
||||||
id: "pcampbell".to_string(),
|
|
||||||
email: "pcampbell@kemitix.net".to_string(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).expect("json attachment"))
|
|
||||||
.expect("mock request");
|
.expect("mock request");
|
||||||
|
|
||||||
(
|
(
|
||||||
|
@ -156,5 +130,5 @@ async fn no_dump(
|
||||||
|
|
||||||
//then
|
//then
|
||||||
let output = prt.output();
|
let output = prt.output();
|
||||||
assert_eq!(output.trim(), "2:1:1:102:/Deck/async-rust-book.webp");
|
assert_eq!(output.trim(), "2:1:1:102");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"id":102,"cardId":332,"type":"file","data":"async-rust-book.webp","lastModified":1734511846,"createdAt":1734511846,"createdBy":"pcampbell","deletedAt":0,"extendedData":{"path":"/Deck/async-rust-book.webp","fileid":746505,"data":"async-rust-book.webp","filesize":137128,"mimetype":"image/webp","info":{"dirname":".","basename":"async-rust-book.webp","extension":"webp","filename":"async-rust-book"},"hasPreview":true,"permissions":1,"attachmentCreator":{"displayName":"Paul Campbell","id":"pcampbell","email":"pcampbell@kemitix.net"}}}
|
{"id":102,"type":"file"}
|
|
@ -76,10 +76,7 @@ impl<'ctx> TrelloClient<'ctx> {
|
||||||
let file_name = file_name
|
let file_name = file_name
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_else(|| PathBuf::from(attachment.file_name));
|
.unwrap_or_else(|| PathBuf::from(attachment.file_name));
|
||||||
crate::e!(self.ctx.prt, "file_name: {}", file_name.display());
|
|
||||||
crate::e!(self.ctx.prt, "base: {}", self.ctx.fs.base().display());
|
|
||||||
let file_name = self.ctx.fs.base().join(file_name);
|
let file_name = self.ctx.fs.base().join(file_name);
|
||||||
crate::e!(self.ctx.prt, "file_name: {}", file_name.display());
|
|
||||||
let resp = self
|
let resp = self
|
||||||
.ctx
|
.ctx
|
||||||
.net
|
.net
|
||||||
|
|
|
@ -8,18 +8,9 @@ newtype!(TrelloAttachmentId, String, Display, "Card Attachment ID");
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub(crate) struct TrelloAttachment {
|
pub(crate) struct TrelloAttachment {
|
||||||
pub(crate) id: String, // "5abbe4b7ddc1b351ef961414",
|
pub(crate) id: String, // "5abbe4b7ddc1b351ef961414",
|
||||||
pub(crate) bytes: i64,
|
|
||||||
pub(crate) date: String, //"2018-10-17T19:10:14.808Z",
|
|
||||||
#[serde(rename = "idMember")]
|
|
||||||
pub(crate) id_member: String, //"5abbe4b7ddc1b351ef961414",
|
|
||||||
#[serde(rename = "isUpload")]
|
|
||||||
pub(crate) is_upload: bool, //false,
|
|
||||||
#[serde(rename = "mimeType")]
|
|
||||||
pub(crate) mime_type: Option<String>, //"",
|
|
||||||
pub(crate) name: String, //"Deprecation Extension Notice",
|
pub(crate) name: String, //"Deprecation Extension Notice",
|
||||||
pub(crate) url: String, //"https://admin.typeform.com/form/RzExEM/share#/link",
|
pub(crate) url: String, //"https://admin.typeform.com/form/RzExEM/share#/link",
|
||||||
pub(crate) pos: i64, //1638
|
|
||||||
#[serde(rename = "fileName")]
|
#[serde(rename = "fileName")]
|
||||||
pub(crate) file_name: String,
|
pub(crate) file_name: String,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
//
|
//
|
||||||
use derive_more::derive::Display;
|
use derive_more::derive::Display;
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Deserializer};
|
||||||
|
|
||||||
use super::{
|
use super::attachment::TrelloAttachment;
|
||||||
attachment::{TrelloAttachment, TrelloAttachmentId},
|
|
||||||
list::TrelloListId,
|
|
||||||
};
|
|
||||||
use crate::newtype;
|
use crate::newtype;
|
||||||
use crate::trello::model::label::TrelloLabel;
|
use crate::trello::model::label::TrelloLabel;
|
||||||
|
|
||||||
|
@ -13,14 +10,20 @@ newtype!(TrelloCardId, String, Display, "Card ID");
|
||||||
newtype!(TrelloCardName, String, Display, "Card Name");
|
newtype!(TrelloCardName, String, Display, "Card Name");
|
||||||
newtype!(TrelloCardDescription, String, Display, "Card Description");
|
newtype!(TrelloCardDescription, String, Display, "Card Description");
|
||||||
newtype!(TrelloCardDue, String, Display, "Card Due");
|
newtype!(TrelloCardDue, String, Display, "Card Due");
|
||||||
newtype!(TrelloCardPosition, i64, Display, "Card Position");
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Display)]
|
||||||
pub(crate) struct TrelloCard {
|
pub struct TrelloCardPosition(i64);
|
||||||
pub(crate) id: TrelloCardId,
|
|
||||||
pub(crate) name: TrelloCardName,
|
impl<'de> Deserialize<'de> for TrelloCardPosition {
|
||||||
#[serde(rename = "idList")]
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
pub(crate) id_list: TrelloListId,
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
// First deserialize as f64
|
||||||
|
let value = f64::deserialize(deserializer)?;
|
||||||
|
// Convert to i64 by rounding
|
||||||
|
Ok(TrelloCardPosition(value.round() as i64))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||||
|
@ -28,22 +31,11 @@ pub(crate) struct TrelloShortCard {
|
||||||
pub(crate) id: TrelloCardId,
|
pub(crate) id: TrelloCardId,
|
||||||
pub(crate) name: TrelloCardName,
|
pub(crate) name: TrelloCardName,
|
||||||
pub(crate) desc: TrelloCardDescription,
|
pub(crate) desc: TrelloCardDescription,
|
||||||
pub(crate) due: Option<TrelloCardDue>, // format date
|
|
||||||
#[serde(rename = "idAttachmentCover")]
|
|
||||||
pub(crate) id_attachment_cover: Option<TrelloAttachmentId>,
|
|
||||||
pub(crate) labels: Vec<TrelloLabel>,
|
pub(crate) labels: Vec<TrelloLabel>,
|
||||||
pub(crate) pos: TrelloCardPosition,
|
pub(crate) pos: TrelloCardPosition,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||||
pub(crate) struct TrelloLongCard {
|
pub(crate) struct TrelloLongCard {
|
||||||
pub(crate) id: TrelloCardId,
|
|
||||||
pub(crate) name: TrelloCardName,
|
|
||||||
pub(crate) desc: TrelloCardDescription,
|
|
||||||
pub(crate) due: Option<TrelloCardDue>, // format date
|
|
||||||
#[serde(rename = "idAttachmentCover")]
|
|
||||||
pub(crate) id_attachment_cover: Option<TrelloAttachmentId>,
|
|
||||||
pub(crate) labels: Vec<TrelloLabel>,
|
|
||||||
pub(crate) pos: TrelloCardPosition,
|
|
||||||
pub(crate) attachments: Vec<TrelloAttachment>,
|
pub(crate) attachments: Vec<TrelloAttachment>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ newtype!(TrelloLabelColor, String, Display, "Label Colour");
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Deserialize)]
|
||||||
pub(crate) struct TrelloLabel {
|
pub(crate) struct TrelloLabel {
|
||||||
pub id: TrelloLabelId,
|
|
||||||
pub name: TrelloLabelName,
|
pub name: TrelloLabelName,
|
||||||
pub color: TrelloLabelColor,
|
pub color: TrelloLabelColor,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue