feat(trello): add command 'trello card get'

This commit is contained in:
Paul Campbell 2024-12-09 22:18:40 +00:00
parent a87250847d
commit a6cc119449
8 changed files with 363 additions and 24 deletions

View file

@ -63,6 +63,7 @@ As part of building the import server, I'm including the following commands the
- [x] trello member get - includes list of boards - [x] trello member get - includes list of boards
- [x] trello board get - includes list of stacks - [x] trello board get - includes list of stacks
- [x] trello stack get - includes list of cards - [x] trello stack get - includes list of cards
- [x] trello card get - includes list of attachments
- [ ] trello attachment get - [ ] trello attachment get
- [ ] nextcloud deck get (was board list) - [ ] nextcloud deck get (was board list)
- [ ] nextcloud board get (was stack list) - [ ] nextcloud board get (was stack list)

View file

@ -0,0 +1,211 @@
{
"id": "65ad94865aed24f70ecdcebb",
"badges": {
"attachmentsByType": {
"trello": {
"board": 0,
"card": 0
}
},
"externalSource": null,
"location": false,
"votes": 0,
"viewingMemberVoted": false,
"subscribed": false,
"lastUpdatedByAi": false,
"fogbugz": "",
"checkItems": 0,
"checkItemsChecked": 0,
"checkItemsEarliestDue": null,
"comments": 0,
"attachments": 1,
"description": true,
"due": null,
"dueComplete": false,
"start": null
},
"checkItemStates": [],
"closed": false,
"dueComplete": false,
"dateLastActivity": "2024-01-21T22:02:47.582Z",
"desc": "A list of the things we think we want to do, maybe not quite ready for work, but high likelihood of being worked on.\n\nThis is the staging area where specs should get fleshed out.\n\nNo limit on the list size, but we should reconsider if it gets long.",
"descData": null,
"due": null,
"dueReminder": null,
"email": null,
"idBoard": "65ad94865aed24f70ecdce4b",
"idChecklists": [],
"idList": "65ad94865aed24f70ecdce4c",
"idMembers": [],
"idMembersVoted": [],
"idShort": 1,
"idAttachmentCover": "65ad94875aed24f70ecdd037",
"labels": [],
"idLabels": [],
"manualCoverAttachment": false,
"name": "Backlog",
"pinned": false,
"pos": 16384,
"shortLink": "Z7CTyW2I",
"shortUrl": "https://trello.com/c/Z7CTyW2I",
"start": null,
"subscribed": false,
"url": "https://trello.com/c/Z7CTyW2I/1-backlog",
"cover": {
"idAttachment": "65ad94875aed24f70ecdd037",
"color": null,
"idUploadedBackground": null,
"size": "normal",
"brightness": "light",
"scaled": [
{
"id": "65ad94875aed24f70ecdd039",
"_id": "65ad94875aed24f70ecdd039",
"scaled": false,
"url": "https://trello.com/1/cards/65ad94865aed24f70ecdcebb/attachments/65ad94875aed24f70ecdd037/previews/65ad94875aed24f70ecdd039/download/Backlog.png",
"bytes": 1064,
"height": 50,
"width": 70
},
{
"id": "65ad94875aed24f70ecdd03b",
"_id": "65ad94875aed24f70ecdd03b",
"scaled": true,
"url": "https://trello.com/1/cards/65ad94865aed24f70ecdcebb/attachments/65ad94875aed24f70ecdd037/previews/65ad94875aed24f70ecdd03b/download/Backlog.png",
"bytes": 2721,
"height": 66,
"width": 150
},
{
"id": "65ad94875aed24f70ecdd03a",
"_id": "65ad94875aed24f70ecdd03a",
"scaled": false,
"url": "https://trello.com/1/cards/65ad94865aed24f70ecdcebb/attachments/65ad94875aed24f70ecdd037/previews/65ad94875aed24f70ecdd03a/download/Backlog.png",
"bytes": 4859,
"height": 150,
"width": 250
},
{
"id": "65ad94875aed24f70ecdd03c",
"_id": "65ad94875aed24f70ecdd03c",
"scaled": true,
"url": "https://trello.com/1/cards/65ad94865aed24f70ecdcebb/attachments/65ad94875aed24f70ecdd037/previews/65ad94875aed24f70ecdd03c/download/Backlog.png",
"bytes": 5874,
"height": 132,
"width": 300
},
{
"id": "65ad94875aed24f70ecdd03d",
"_id": "65ad94875aed24f70ecdd03d",
"scaled": true,
"url": "https://trello.com/1/cards/65ad94865aed24f70ecdcebb/attachments/65ad94875aed24f70ecdd037/previews/65ad94875aed24f70ecdd03d/download/Backlog.png",
"bytes": 12946,
"height": 264,
"width": 600
},
{
"id": "65ad94875aed24f70ecdd03e",
"_id": "65ad94875aed24f70ecdd03e",
"scaled": true,
"url": "https://trello.com/1/cards/65ad94865aed24f70ecdcebb/attachments/65ad94875aed24f70ecdd037/previews/65ad94875aed24f70ecdd03e/download/Backlog.png",
"bytes": 30062,
"height": 527,
"width": 1200
},
{
"id": "65ad94875aed24f70ecdd03f",
"_id": "65ad94875aed24f70ecdd03f",
"scaled": true,
"url": "https://trello.com/1/cards/65ad94865aed24f70ecdcebb/attachments/65ad94875aed24f70ecdd037/previews/65ad94875aed24f70ecdd03f/download/Backlog.png",
"bytes": 184198,
"height": 3558,
"width": 8100
}
],
"edgeColor": "#047cbc",
"idPlugin": null
},
"isTemplate": false,
"cardRole": null,
"mirrorSourceId": null,
"attachments": [
{
"id": "65ad94875aed24f70ecdd037",
"bytes": 184198,
"date": "2019-01-02T22:47:17.325Z",
"edgeColor": "#047cbc",
"idMember": "65ad94875aed24f70ecdd038",
"isUpload": true,
"mimeType": null,
"name": "Backlog.png",
"previews": [
{
"id": "65ad94875aed24f70ecdd039",
"_id": "65ad94875aed24f70ecdd039",
"scaled": false,
"url": "https://trello.com/1/cards/65ad94865aed24f70ecdcebb/attachments/65ad94875aed24f70ecdd037/previews/65ad94875aed24f70ecdd039/download/Backlog.png",
"bytes": 1064,
"height": 50,
"width": 70
},
{
"id": "65ad94875aed24f70ecdd03a",
"_id": "65ad94875aed24f70ecdd03a",
"scaled": false,
"url": "https://trello.com/1/cards/65ad94865aed24f70ecdcebb/attachments/65ad94875aed24f70ecdd037/previews/65ad94875aed24f70ecdd03a/download/Backlog.png",
"bytes": 4859,
"height": 150,
"width": 250
},
{
"id": "65ad94875aed24f70ecdd03b",
"_id": "65ad94875aed24f70ecdd03b",
"scaled": true,
"url": "https://trello.com/1/cards/65ad94865aed24f70ecdcebb/attachments/65ad94875aed24f70ecdd037/previews/65ad94875aed24f70ecdd03b/download/Backlog.png",
"bytes": 2721,
"height": 66,
"width": 150
},
{
"id": "65ad94875aed24f70ecdd03c",
"_id": "65ad94875aed24f70ecdd03c",
"scaled": true,
"url": "https://trello.com/1/cards/65ad94865aed24f70ecdcebb/attachments/65ad94875aed24f70ecdd037/previews/65ad94875aed24f70ecdd03c/download/Backlog.png",
"bytes": 5874,
"height": 132,
"width": 300
},
{
"id": "65ad94875aed24f70ecdd03d",
"_id": "65ad94875aed24f70ecdd03d",
"scaled": true,
"url": "https://trello.com/1/cards/65ad94865aed24f70ecdcebb/attachments/65ad94875aed24f70ecdd037/previews/65ad94875aed24f70ecdd03d/download/Backlog.png",
"bytes": 12946,
"height": 264,
"width": 600
},
{
"id": "65ad94875aed24f70ecdd03e",
"_id": "65ad94875aed24f70ecdd03e",
"scaled": true,
"url": "https://trello.com/1/cards/65ad94865aed24f70ecdcebb/attachments/65ad94875aed24f70ecdd037/previews/65ad94875aed24f70ecdd03e/download/Backlog.png",
"bytes": 30062,
"height": 527,
"width": 1200
},
{
"id": "65ad94875aed24f70ecdd03f",
"_id": "65ad94875aed24f70ecdd03f",
"scaled": true,
"url": "https://trello.com/1/cards/65ad94865aed24f70ecdcebb/attachments/65ad94875aed24f70ecdd037/previews/65ad94875aed24f70ecdd03f/download/Backlog.png",
"bytes": 184198,
"height": 3558,
"width": 8100
}
],
"url": "https://trello.com/1/cards/65ad94865aed24f70ecdcebb/attachments/65ad94875aed24f70ecdd037/download/Backlog.png",
"pos": 49152,
"fileName": "Backlog.png"
}
]
}

36
src/trello/card.rs Normal file
View file

@ -0,0 +1,36 @@
//
use clap::Parser;
use color_eyre::Result;
use crate::{execute::Execute, p, FullCtx};
use super::model::TrelloCardId;
#[derive(Parser, Debug)]
pub(crate) enum TrelloCardCommand {
Get {
#[clap(long, action = clap::ArgAction::SetTrue)]
dump: bool,
card_id: String,
},
}
impl Execute for TrelloCardCommand {
async fn execute(self, ctx: FullCtx) -> Result<()> {
match self {
Self::Get { dump, card_id } => {
let api_result = ctx.trello_client().card(&TrelloCardId::new(card_id)).await;
if dump {
p!(ctx.prt, "{}", api_result.text);
} else {
let attachments = api_result.result?.attachments;
for attachment in attachments {
p!(ctx.prt, "{}:{}", attachment.id, attachment.name);
}
}
Ok(())
}
}
}
}

View file

@ -3,9 +3,16 @@ use std::collections::HashMap;
use kxio::net::{Net, ReqBuilder}; use kxio::net::{Net, ReqBuilder};
use super::model::{TrelloBoardId, TrelloListId}; use crate::{
use crate::trello::model::card::TrelloShortCard; api_result::APIResult,
use crate::{api_result::APIResult, f, s, trello::model::board::TrelloBoard, FullCtx}; f, s,
trello::model::{
board::TrelloBoard,
card::{TrelloLongCard, TrelloShortCard},
TrelloBoardId, TrelloCardId, TrelloListId,
},
FullCtx,
};
pub(crate) struct TrelloClient<'ctx> { pub(crate) struct TrelloClient<'ctx> {
ctx: &'ctx FullCtx, ctx: &'ctx FullCtx,
@ -69,11 +76,13 @@ impl<'ctx> TrelloClient<'ctx> {
.await .await
} }
// // https://developer.atlassian.com/cloud/trello/rest/api-group-cards/#api-cards-id-get // https://developer.atlassian.com/cloud/trello/rest/api-group-cards/#api-cards-id-get
// pub(crate) async fn card(&self, card_id: &TrelloCardId) -> APIResult<TrelloShortCard>{ pub(crate) async fn card(&self, card_id: &TrelloCardId) -> APIResult<TrelloLongCard> {
// self.request(f!("/cards/{card_id}?attachments=true"), |net, url| net.get(url)) self.request(f!("/cards/{card_id}?attachments=true"), |net, url| {
// .await net.get(url)
// } })
.await
}
// // https://developer.atlassian.com/cloud/trello/rest/api-group-cards/#api-cards-id-attachments-idattachment-get // // https://developer.atlassian.com/cloud/trello/rest/api-group-cards/#api-cards-id-attachments-idattachment-get
// pub(crate) async fn card_attachment( // pub(crate) async fn card_attachment(

View file

@ -5,6 +5,7 @@ use crate::{
execute::Execute, execute::Execute,
trello::{ trello::{
board::TrelloBoardCommand, board::TrelloBoardCommand,
card::TrelloCardCommand,
member::TrelloMemberCommand, member::TrelloMemberCommand,
model::{ model::{
auth::{TrelloApiKey, TrelloApiSecret}, auth::{TrelloApiKey, TrelloApiSecret},
@ -16,6 +17,7 @@ use crate::{
}; };
pub(crate) mod board; pub(crate) mod board;
pub(crate) mod card;
pub(crate) mod client; pub(crate) mod client;
pub(crate) mod member; pub(crate) mod member;
pub(crate) mod model; pub(crate) mod model;
@ -34,6 +36,9 @@ pub(crate) enum TrelloCommand {
#[clap(subcommand)] #[clap(subcommand)]
Stack(TrelloStackCommand), Stack(TrelloStackCommand),
#[clap(subcommand)]
Card(TrelloCardCommand),
} }
impl Execute for TrelloCommand { impl Execute for TrelloCommand {
@ -42,6 +47,7 @@ impl Execute for TrelloCommand {
Self::Member(cmd) => cmd.execute(ctx).await, Self::Member(cmd) => cmd.execute(ctx).await,
Self::Board(cmd) => cmd.execute(ctx).await, Self::Board(cmd) => cmd.execute(ctx).await,
Self::Stack(cmd) => cmd.execute(ctx).await, Self::Stack(cmd) => cmd.execute(ctx).await,
Self::Card(cmd) => cmd.execute(ctx).await,
} }
} }
} }

View file

@ -1,7 +1,7 @@
// //
use super::{ use super::{
TrelloAttachmentId, TrelloCardDescription, TrelloCardDue, TrelloCardId, TrelloCardName, TrelloAttachment, TrelloAttachmentId, TrelloCardDescription, TrelloCardDue, TrelloCardId,
TrelloCardPosition, TrelloLabelId, TrelloListId, TrelloCardName, TrelloCardPosition, TrelloLabelId, TrelloListId,
}; };
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
@ -12,7 +12,7 @@ pub(crate) struct TrelloCard {
pub(crate) id_list: TrelloListId, pub(crate) id_list: TrelloListId,
} }
#[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)] #[derive(Debug, PartialEq, Eq, serde::Deserialize)]
pub(crate) struct TrelloShortCard { pub(crate) struct TrelloShortCard {
pub(crate) id: TrelloCardId, pub(crate) id: TrelloCardId,
pub(crate) name: TrelloCardName, pub(crate) name: TrelloCardName,
@ -24,7 +24,7 @@ pub(crate) struct TrelloShortCard {
pub(crate) pos: TrelloCardPosition, pub(crate) pos: TrelloCardPosition,
} }
#[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)] #[derive(Debug, PartialEq, Eq, serde::Deserialize)]
pub(crate) struct TrelloLongCard { pub(crate) struct TrelloLongCard {
pub(crate) id: TrelloCardId, pub(crate) id: TrelloCardId,
pub(crate) name: TrelloCardName, pub(crate) name: TrelloCardName,
@ -34,4 +34,5 @@ pub(crate) struct TrelloLongCard {
pub(crate) id_attachment_cover: Option<TrelloAttachmentId>, pub(crate) id_attachment_cover: Option<TrelloAttachmentId>,
pub(crate) labels: Vec<TrelloLabelId>, pub(crate) labels: Vec<TrelloLabelId>,
pub(crate) pos: TrelloCardPosition, pub(crate) pos: TrelloCardPosition,
pub(crate) attachments: Vec<TrelloAttachment>,
} }

View file

@ -31,18 +31,17 @@ newtype!(TrelloLabelId, String, Display, "Label ID");
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Debug, Serialize, 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: String, pub(crate) bytes: i64,
date: String, //"2018-10-17T19:10:14.808Z", pub(crate) date: String, //"2018-10-17T19:10:14.808Z",
#[serde(rename = "edgeColor")]
edge_color: String, //"yellow",
#[serde(rename = "idMember")] #[serde(rename = "idMember")]
id_member: String, //"5abbe4b7ddc1b351ef961414", pub(crate) id_member: String, //"5abbe4b7ddc1b351ef961414",
#[serde(rename = "isUpload")] #[serde(rename = "isUpload")]
is_upload: bool, //false, pub(crate) is_upload: bool, //false,
#[serde(rename = "mimeType")] #[serde(rename = "mimeType")]
mime_type: String, //"", pub(crate) mime_type: Option<String>, //"",
pub(crate) name: String, //"Deprecation Extension Notice", pub(crate) name: String, //"Deprecation Extension Notice",
previews: Vec<String>, //[], pub(crate) url: String, //"https://admin.typeform.com/form/RzExEM/share#/link",
url: String, //"https://admin.typeform.com/form/RzExEM/share#/link", pub(crate) pos: i64, //1638
pos: i64, //1638 #[serde(rename = "fileName")]
pub(crate) file_name: String,
} }

View file

@ -1,17 +1,19 @@
// //
use kxio::net::StatusCode; use kxio::{net::MockNet, net::StatusCode};
use std::collections::HashMap; use std::collections::HashMap;
use crate::{ use crate::{
execute::Execute,
s, s,
tests::given, tests::given,
trello::{ trello::{
model::board::TrelloBoards as _, card::TrelloCardCommand,
model::{ model::{
auth::TrelloAuth, board::TrelloBoard, list::TrelloList, TrelloBoardId, TrelloBoardName, auth::TrelloAuth, board::TrelloBoard, list::TrelloList, TrelloBoardId, TrelloBoardName,
TrelloListId, TrelloListName, TrelloListId, TrelloListName,
}, },
}, },
Command,
}; };
mod commands { mod commands {
@ -19,6 +21,7 @@ mod commands {
mod board { mod board {
use super::*; use super::*;
use crate::trello::model::board::TrelloBoards;
#[test] #[test]
fn list_of_boards_find_by_name_returns_board() { fn list_of_boards_find_by_name_returns_board() {
@ -102,6 +105,79 @@ mod commands {
} }
); );
} }
mod stack {
// TODO: test stack command
}
mod card {
use super::*;
use crate::trello::TrelloCommand;
#[tokio::test]
async fn get() {
//given
let mock_net = kxio::net::mock();
prep_mock_get(&mock_net);
// let fs = given::a_filesystem();
let ctx = given::a_full_context(mock_net);
let prt = ctx.prt.clone();
let prt = prt.as_test().unwrap();
let command = Command::Trello(TrelloCommand::Card(TrelloCardCommand::Get {
dump: false,
card_id: s!("65ad94865aed24f70ecdcebb"),
}));
//when
command.execute(ctx).await.expect("execute");
//then
let output = prt.output();
assert_eq!(output, s!("65ad94875aed24f70ecdd037:Backlog.png\n"));
}
#[tokio::test]
async fn get_dump() {
//given
let mock_net = kxio::net::mock();
prep_mock_get(&mock_net);
// let fs = given::a_filesystem();
let ctx = given::a_full_context(mock_net);
let prt = ctx.prt.clone();
let prt = prt.as_test().unwrap();
let command = Command::Trello(TrelloCommand::Card(TrelloCardCommand::Get {
dump: true,
card_id: s!("65ad94865aed24f70ecdcebb"),
}));
//when
command.execute(ctx).await.expect("execute");
//then
let output = prt.output();
assert_eq!(
output.trim(),
include_str!("../tests/responses/trello-card-get.json").trim()
);
}
fn prep_mock_get(mock_net: &MockNet) {
mock_net
.on()
.get("https://api.trello.com/1/cards/65ad94865aed24f70ecdcebb")
.query("attachments", "true")
.header("authorization", "OAuth oauth_consumer_key=\"trello-api-key\", oauth_token=\"trello-api-secret\"")
.header("accept", "application/json")
.header("content-type", "application/json")
.respond(StatusCode::OK)
.body(include_str!("../tests/responses/trello-card-get.json"))
.expect("mock request");
}
}
} }
#[test] #[test]