From a6cc119449a381637d1dec0628e87d9125d358d2 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Mon, 9 Dec 2024 22:18:40 +0000 Subject: [PATCH] feat(trello): add command 'trello card get' --- README.md | 1 + src/tests/responses/trello-card-get.json | 211 +++++++++++++++++++++++ src/trello/card.rs | 36 ++++ src/trello/client.rs | 25 ++- src/trello/mod.rs | 6 + src/trello/model/card.rs | 9 +- src/trello/model/mod.rs | 19 +- src/trello/tests.rs | 80 ++++++++- 8 files changed, 363 insertions(+), 24 deletions(-) create mode 100644 src/tests/responses/trello-card-get.json create mode 100644 src/trello/card.rs diff --git a/README.md b/README.md index 2178135..28ffdf7 100644 --- a/README.md +++ b/README.md @@ -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 board get - includes list of stacks - [x] trello stack get - includes list of cards +- [x] trello card get - includes list of attachments - [ ] trello attachment get - [ ] nextcloud deck get (was board list) - [ ] nextcloud board get (was stack list) diff --git a/src/tests/responses/trello-card-get.json b/src/tests/responses/trello-card-get.json new file mode 100644 index 0000000..26b68d7 --- /dev/null +++ b/src/tests/responses/trello-card-get.json @@ -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" + } + ] +} diff --git a/src/trello/card.rs b/src/trello/card.rs new file mode 100644 index 0000000..1e595db --- /dev/null +++ b/src/trello/card.rs @@ -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(()) + } + } + } +} diff --git a/src/trello/client.rs b/src/trello/client.rs index 433fc55..e200a00 100644 --- a/src/trello/client.rs +++ b/src/trello/client.rs @@ -3,9 +3,16 @@ use std::collections::HashMap; use kxio::net::{Net, ReqBuilder}; -use super::model::{TrelloBoardId, TrelloListId}; -use crate::trello::model::card::TrelloShortCard; -use crate::{api_result::APIResult, f, s, trello::model::board::TrelloBoard, FullCtx}; +use crate::{ + api_result::APIResult, + f, s, + trello::model::{ + board::TrelloBoard, + card::{TrelloLongCard, TrelloShortCard}, + TrelloBoardId, TrelloCardId, TrelloListId, + }, + FullCtx, +}; pub(crate) struct TrelloClient<'ctx> { ctx: &'ctx FullCtx, @@ -69,11 +76,13 @@ impl<'ctx> TrelloClient<'ctx> { .await } - // // https://developer.atlassian.com/cloud/trello/rest/api-group-cards/#api-cards-id-get - // pub(crate) async fn card(&self, card_id: &TrelloCardId) -> APIResult{ - // self.request(f!("/cards/{card_id}?attachments=true"), |net, url| net.get(url)) - // .await - // } + // https://developer.atlassian.com/cloud/trello/rest/api-group-cards/#api-cards-id-get + pub(crate) async fn card(&self, card_id: &TrelloCardId) -> APIResult { + self.request(f!("/cards/{card_id}?attachments=true"), |net, url| { + net.get(url) + }) + .await + } // // https://developer.atlassian.com/cloud/trello/rest/api-group-cards/#api-cards-id-attachments-idattachment-get // pub(crate) async fn card_attachment( diff --git a/src/trello/mod.rs b/src/trello/mod.rs index eb5fdbe..677638e 100644 --- a/src/trello/mod.rs +++ b/src/trello/mod.rs @@ -5,6 +5,7 @@ use crate::{ execute::Execute, trello::{ board::TrelloBoardCommand, + card::TrelloCardCommand, member::TrelloMemberCommand, model::{ auth::{TrelloApiKey, TrelloApiSecret}, @@ -16,6 +17,7 @@ use crate::{ }; pub(crate) mod board; +pub(crate) mod card; pub(crate) mod client; pub(crate) mod member; pub(crate) mod model; @@ -34,6 +36,9 @@ pub(crate) enum TrelloCommand { #[clap(subcommand)] Stack(TrelloStackCommand), + + #[clap(subcommand)] + Card(TrelloCardCommand), } impl Execute for TrelloCommand { @@ -42,6 +47,7 @@ impl Execute for TrelloCommand { Self::Member(cmd) => cmd.execute(ctx).await, Self::Board(cmd) => cmd.execute(ctx).await, Self::Stack(cmd) => cmd.execute(ctx).await, + Self::Card(cmd) => cmd.execute(ctx).await, } } } diff --git a/src/trello/model/card.rs b/src/trello/model/card.rs index 26887d9..be0fe69 100644 --- a/src/trello/model/card.rs +++ b/src/trello/model/card.rs @@ -1,7 +1,7 @@ // use super::{ - TrelloAttachmentId, TrelloCardDescription, TrelloCardDue, TrelloCardId, TrelloCardName, - TrelloCardPosition, TrelloLabelId, TrelloListId, + TrelloAttachment, TrelloAttachmentId, TrelloCardDescription, TrelloCardDue, TrelloCardId, + TrelloCardName, TrelloCardPosition, TrelloLabelId, TrelloListId, }; #[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)] @@ -12,7 +12,7 @@ pub(crate) struct TrelloCard { 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) id: TrelloCardId, pub(crate) name: TrelloCardName, @@ -24,7 +24,7 @@ pub(crate) struct TrelloShortCard { pub(crate) pos: TrelloCardPosition, } -#[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)] +#[derive(Debug, PartialEq, Eq, serde::Deserialize)] pub(crate) struct TrelloLongCard { pub(crate) id: TrelloCardId, pub(crate) name: TrelloCardName, @@ -34,4 +34,5 @@ pub(crate) struct TrelloLongCard { pub(crate) id_attachment_cover: Option, pub(crate) labels: Vec, pub(crate) pos: TrelloCardPosition, + pub(crate) attachments: Vec, } diff --git a/src/trello/model/mod.rs b/src/trello/model/mod.rs index c87b82d..33c3a9b 100644 --- a/src/trello/model/mod.rs +++ b/src/trello/model/mod.rs @@ -31,18 +31,17 @@ newtype!(TrelloLabelId, String, Display, "Label ID"); #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub(crate) struct TrelloAttachment { pub(crate) id: String, // "5abbe4b7ddc1b351ef961414", - pub(crate) bytes: String, - date: String, //"2018-10-17T19:10:14.808Z", - #[serde(rename = "edgeColor")] - edge_color: String, //"yellow", + pub(crate) bytes: i64, + pub(crate) date: String, //"2018-10-17T19:10:14.808Z", #[serde(rename = "idMember")] - id_member: String, //"5abbe4b7ddc1b351ef961414", + pub(crate) id_member: String, //"5abbe4b7ddc1b351ef961414", #[serde(rename = "isUpload")] - is_upload: bool, //false, + pub(crate) is_upload: bool, //false, #[serde(rename = "mimeType")] - mime_type: String, //"", + pub(crate) mime_type: Option, //"", pub(crate) name: String, //"Deprecation Extension Notice", - previews: Vec, //[], - url: String, //"https://admin.typeform.com/form/RzExEM/share#/link", - pos: i64, //1638 + pub(crate) url: String, //"https://admin.typeform.com/form/RzExEM/share#/link", + pub(crate) pos: i64, //1638 + #[serde(rename = "fileName")] + pub(crate) file_name: String, } diff --git a/src/trello/tests.rs b/src/trello/tests.rs index b120851..35d621b 100644 --- a/src/trello/tests.rs +++ b/src/trello/tests.rs @@ -1,17 +1,19 @@ // -use kxio::net::StatusCode; +use kxio::{net::MockNet, net::StatusCode}; use std::collections::HashMap; use crate::{ + execute::Execute, s, tests::given, trello::{ - model::board::TrelloBoards as _, + card::TrelloCardCommand, model::{ auth::TrelloAuth, board::TrelloBoard, list::TrelloList, TrelloBoardId, TrelloBoardName, TrelloListId, TrelloListName, }, }, + Command, }; mod commands { @@ -19,6 +21,7 @@ mod commands { mod board { use super::*; + use crate::trello::model::board::TrelloBoards; #[test] 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]