From 30a5e83bb4c3092ac417247d7c8a067b2ae08c84 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 8 Dec 2024 22:15:23 +0000 Subject: [PATCH] feat(trello): add command 'trello stack list' --- src/nextcloud/mod.rs | 2 +- src/nextcloud/tests/stack/list.rs | 11 +-- src/tests/mod.rs | 41 +++++++++- src/tests/responses/trello-stack-list.json | 44 ++++++++++ src/trello/api/lists.rs | 93 ++++++++++++++-------- src/trello/api/members.rs | 6 +- src/trello/api/mod.rs | 1 + src/trello/client.rs | 9 ++- src/trello/mod.rs | 7 ++ src/trello/model/auth.rs | 13 ++- src/trello/model/board.rs | 3 +- src/trello/model/card.rs | 20 ++--- src/trello/model/mod.rs | 4 +- src/trello/stack.rs | 44 ++++++++++ 14 files changed, 227 insertions(+), 71 deletions(-) create mode 100644 src/tests/responses/trello-stack-list.json create mode 100644 src/trello/stack.rs diff --git a/src/nextcloud/mod.rs b/src/nextcloud/mod.rs index 6849959..7d0aa23 100644 --- a/src/nextcloud/mod.rs +++ b/src/nextcloud/mod.rs @@ -21,7 +21,7 @@ use crate::{ }; mod board; -mod card; +pub(crate) mod card; pub(crate) mod client; pub(crate) mod model; mod stack; diff --git a/src/nextcloud/tests/stack/list.rs b/src/nextcloud/tests/stack/list.rs index f667270..71338bf 100644 --- a/src/nextcloud/tests/stack/list.rs +++ b/src/nextcloud/tests/stack/list.rs @@ -4,6 +4,7 @@ use super::*; #[rstest::fixture] fn ctx() -> FullCtx { let fs = given::a_filesystem(); + let nextcloud_config = given::a_nextcloud_config(); let mock_net = given::a_network(); @@ -20,15 +21,7 @@ fn ctx() -> FullCtx { )) .expect("mock request"); - FullCtx { - fs: fs.as_real(), - net: mock_net.into(), - prt: given::a_printer(), - cfg: AppConfig { - trello: given::a_trello_config(), - nextcloud: nextcloud_config, - }, - } + given::a_full_context(fs, mock_net) } #[rstest::rstest] diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 89cc2e2..f8b9fbf 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -61,6 +61,7 @@ mod config { mod init { use super::*; + use test_log::test; #[test] @@ -115,8 +116,13 @@ mod template { } } -mod given { +pub(crate) mod given { use super::*; + + use crate::nextcloud::NextcloudConfig; + use crate::trello::TrelloConfig; + use crate::{s, FullCtx}; + use kxio::{ fs::{FileSystem, TempFileSystem}, net::{MockNet, Net}, @@ -137,4 +143,37 @@ mod given { pub(crate) fn a_printer() -> Printer { kxio::print::test() } + + pub(crate) fn a_trello_config() -> TrelloConfig { + TrelloConfig { + api_key: s!("trello-api-key").into(), + api_secret: s!("trello-api-secret").into(), + board_name: s!("trello-board-name").into(), + } + } + + pub(crate) fn a_nextcloud_config() -> NextcloudConfig { + let hostname = s!("host-name").into(); + let username = s!("username").into(); + let password = s!("password").into(); + let board_id = 2.into(); + NextcloudConfig { + hostname, + username, + password, + board_id, + } + } + + pub(crate) fn a_full_context(fs: TempFileSystem, net: MockNet) -> FullCtx { + FullCtx { + // fs: a_filesystem(), + net: net.into(), + prt: a_printer(), + cfg: AppConfig { + trello: a_trello_config(), + nextcloud: a_nextcloud_config(), + }, + } + } } diff --git a/src/tests/responses/trello-stack-list.json b/src/tests/responses/trello-stack-list.json new file mode 100644 index 0000000..526d669 --- /dev/null +++ b/src/tests/responses/trello-stack-list.json @@ -0,0 +1,44 @@ +[ + { + "id": "65ad94865aed24f70ecdce4c", + "name": "Backlog", + "closed": false, + "color": null, + "idBoard": "65ad94865aed24f70ecdce4b", + "pos": 65535, + "subscribed": false, + "softLimit": null, + "type": null, + "datasource": { + "filter": false + } + }, + { + "id": "65ad94865aed24f70ecdce4e", + "name": "To Do", + "closed": false, + "color": null, + "idBoard": "65ad94865aed24f70ecdce4b", + "pos": 196607, + "subscribed": false, + "softLimit": null, + "type": null, + "datasource": { + "filter": false + } + }, + { + "id": "65ad94865aed24f70ecdce52", + "name": "Done 🎉", + "closed": false, + "color": null, + "idBoard": "65ad94865aed24f70ecdce4b", + "pos": 393215, + "subscribed": false, + "softLimit": null, + "type": null, + "datasource": { + "filter": false + } + } +] diff --git a/src/trello/api/lists.rs b/src/trello/api/lists.rs index af1aaf8..a56a2ef 100644 --- a/src/trello/api/lists.rs +++ b/src/trello/api/lists.rs @@ -1,47 +1,72 @@ // -use color_eyre::Result; -use kxio::net::Net; +use kxio::{net::Net, print::Printer}; use crate::{ + api_result::APIResult, f, trello::{ - types::{TrelloAuth, TrelloCard, TrelloList}, - url, + model::{auth::TrelloAuth, list::TrelloList, TrelloBoardId}, + url, TrelloConfig, }, }; -/// Get Cards in a List +/// Get Lists in a Board /// -/// https://developer.atlassian.com/cloud/trello/rest/api-group-lists/#api-lists-id-cards-get +/// GET /boards/{id}/lists /// -/// GET /lists/{id}/cards -/// -/// List the cards in a list -/// -/// Request -/// -/// Path parameters -/// -/// id TrelloID REQUIRED -/// -/// Responses -/// -/// 200 OK Success -/// -/// application/json -pub(crate) async fn get_lists_cards( - auth: &TrelloAuth, - list: &TrelloList, +/// List all lists in a board +pub(crate) async fn get_board_lists( + cfg: &TrelloConfig, + board_id: &TrelloBoardId, net: &Net, -) -> Result> { - let net_url = url(f!("/lists/{}/cards", **list.id())); - let cards = net - .get(net_url) - .headers(auth.into()) - .send() - .await? - .json() - .await?; - Ok(cards) + prt: &Printer, +) -> APIResult> { + APIResult::new( + net.get(url(f!("/boards/{}/lists", board_id))) + .headers(TrelloAuth::from(cfg).into()) + .header("accept", "application/json") + .header("content-type", "application/json") + .send() + .await, + prt, + ) + .await } + +// /// Get Cards in a List +// /// +// /// https://developer.atlassian.com/cloud/trello/rest/api-group-lists/#api-lists-id-cards-get +// /// +// /// GET /lists/{id}/cards +// /// +// /// List the cards in a list +// /// +// /// Request +// /// +// /// Path parameters +// /// +// /// id TrelloID REQUIRED +// /// +// /// Responses +// /// +// /// 200 OK Success +// /// +// /// application/json +// pub(crate) async fn get_lists_cards<'cfg>( +// cfg: &TrelloConfig, +// list: &TrelloList, +// net: &Net, +// prt: &Printer, +// ) -> APIResult> { +// APIResult::new( +// net.get(url(f!("/lists/{}/cards", list.id))) +// .headers(TrelloAuth::from(cfg).into()) +// .header("accept", "application/json") +// .header("content-type", "application/json") +// .send() +// .await, +// prt, +// ) +// .await +// } diff --git a/src/trello/api/members.rs b/src/trello/api/members.rs index 3b84b69..03cdf98 100644 --- a/src/trello/api/members.rs +++ b/src/trello/api/members.rs @@ -43,13 +43,9 @@ pub(crate) async fn get_boards_that_member_belongs_to( net: &Net, prt: &Printer, ) -> APIResult> { - let auth = TrelloAuth { - api_key: &cfg.api_key, - api_secret: &cfg.api_secret, - }; APIResult::new( net.get(url("/members/me/boards?lists=open")) - .headers((&auth).into()) + .headers(TrelloAuth::from(cfg).into()) .header("Accept", "application/json") .send() .await, diff --git a/src/trello/api/mod.rs b/src/trello/api/mod.rs index 2e14bb9..2cbe8fd 100644 --- a/src/trello/api/mod.rs +++ b/src/trello/api/mod.rs @@ -1,4 +1,5 @@ // +pub(crate) mod lists; pub(crate) mod members; #[cfg(test)] diff --git a/src/trello/client.rs b/src/trello/client.rs index f2dd728..7e36a7b 100644 --- a/src/trello/client.rs +++ b/src/trello/client.rs @@ -1,9 +1,12 @@ // use crate::api_result::APIResult; -use crate::trello::model::board::TrelloBoard; +use crate::trello::model::list::TrelloList; use crate::trello::TrelloConfig; +use crate::trello::{api::lists, model::board::TrelloBoard}; use crate::FullCtx; +use super::model::TrelloBoardId; + pub(crate) struct TrelloClient<'ctx> { ctx: &'ctx FullCtx, } @@ -13,6 +16,10 @@ impl<'ctx> TrelloClient<'ctx> { super::api::members::get_boards_that_member_belongs_to(cfg, &self.ctx.net, &self.ctx.prt) .await } + + pub(crate) async fn lists(&self, board_id: &TrelloBoardId) -> APIResult> { + lists::get_board_lists(&self.ctx.cfg.trello, board_id, &self.ctx.net, &self.ctx.prt).await + } } impl TrelloClient<'_> { diff --git a/src/trello/mod.rs b/src/trello/mod.rs index 6af0843..94ac693 100644 --- a/src/trello/mod.rs +++ b/src/trello/mod.rs @@ -3,6 +3,7 @@ pub(crate) mod api; pub(crate) mod boards; pub(crate) mod client; pub(crate) mod model; +pub(crate) mod stack; // #[cfg(test)] // mod tests; @@ -16,9 +17,11 @@ use crate::{ auth::{TrelloApiKey, TrelloApiSecret}, TrelloBoardName, }, + stack::TrelloStackCommand, }, FullCtx, }; + use clap::Parser; pub(crate) fn url(path: impl Into) -> String { @@ -31,12 +34,16 @@ pub(crate) fn url(path: impl Into) -> String { pub(crate) enum TrelloCommand { #[clap(subcommand)] Board(TrelloBoardCommand), + + #[clap(subcommand)] + Stack(TrelloStackCommand), } impl Execute for TrelloCommand { async fn execute(self, ctx: FullCtx) -> color_eyre::Result<()> { match self { Self::Board(cmd) => cmd.execute(ctx).await, + Self::Stack(cmd) => cmd.execute(ctx).await, } } } diff --git a/src/trello/model/auth.rs b/src/trello/model/auth.rs index 2cae623..3617062 100644 --- a/src/trello/model/auth.rs +++ b/src/trello/model/auth.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; use derive_more::derive::Display; use crate::newtype; +use crate::trello::TrelloConfig; newtype!(TrelloApiKey, String, Display, PartialOrd, Ord, "API Key"); newtype!( @@ -20,8 +21,16 @@ pub(crate) struct TrelloAuth<'cfg> { pub(crate) api_key: &'cfg TrelloApiKey, pub(crate) api_secret: &'cfg TrelloApiSecret, } -impl From<&TrelloAuth<'_>> for HashMap { - fn from(value: &TrelloAuth) -> Self { +impl<'cfg> From<&'cfg TrelloConfig> for TrelloAuth<'cfg> { + fn from(value: &'cfg TrelloConfig) -> Self { + TrelloAuth { + api_key: &value.api_key, + api_secret: &value.api_secret, + } + } +} +impl From> for HashMap { + fn from(value: TrelloAuth) -> Self { HashMap::from([( "Authorization".into(), format!( diff --git a/src/trello/model/board.rs b/src/trello/model/board.rs index 6d70899..d81edfd 100644 --- a/src/trello/model/board.rs +++ b/src/trello/model/board.rs @@ -1,7 +1,8 @@ // -use super::{TrelloBoardId, TrelloBoardName}; use crate::trello::model::list::TrelloList; +use super::{TrelloBoardId, TrelloBoardName}; + #[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)] pub(crate) struct TrelloBoard { pub(crate) id: TrelloBoardId, diff --git a/src/trello/model/card.rs b/src/trello/model/card.rs index fb36c42..b8ac1ea 100644 --- a/src/trello/model/card.rs +++ b/src/trello/model/card.rs @@ -1,20 +1,10 @@ // -use crate::trello::{api::cards::TrelloCardUpdate, TrelloCardId, TrelloCardName, TrelloListId}; +use super::{TrelloCardId, TrelloCardName, TrelloListId}; -#[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)] pub(crate) struct TrelloCard { - id: TrelloCardId, - name: TrelloCardName, + pub(crate) id: TrelloCardId, + pub(crate) name: TrelloCardName, #[serde(rename = "idList")] - id_list: TrelloListId, -} -impl TrelloCard { - #[cfg(test)] - pub(crate) const fn new(id: TrelloCardId, name: TrelloCardName, id_list: TrelloListId) -> Self { - Self { id, name, id_list } - } - - pub(crate) const fn list_id(&self) -> &TrelloListId { - &self.id_list - } + pub(crate) id_list: TrelloListId, } diff --git a/src/trello/model/mod.rs b/src/trello/model/mod.rs index 8d9df06..e89fa27 100644 --- a/src/trello/model/mod.rs +++ b/src/trello/model/mod.rs @@ -1,6 +1,6 @@ pub(crate) mod auth; pub(crate) mod board; -// mod card; +pub(crate) mod card; pub(crate) mod list; // mod new_card; @@ -10,7 +10,7 @@ use crate::newtype; newtype!(TrelloBoardId, String, Display, "Board ID"); newtype!(TrelloBoardName, String, Display, "Board Name"); -newtype!(TrelloListId, String, "List ID"); +newtype!(TrelloListId, String, Display, "List ID"); newtype!( TrelloListName, String, diff --git a/src/trello/stack.rs b/src/trello/stack.rs new file mode 100644 index 0000000..ad5c480 --- /dev/null +++ b/src/trello/stack.rs @@ -0,0 +1,44 @@ +// +use clap::Parser; +use color_eyre::Result; + +use crate::{execute::Execute, p, FullCtx}; + +#[derive(Parser, Debug)] +pub(crate) enum TrelloStackCommand { + /// List all stacks (lists) in the board + List { + #[clap(long, action = clap::ArgAction::SetTrue)] + dump: bool, + }, +} + +impl Execute for TrelloStackCommand { + async fn execute(self, ctx: FullCtx) -> Result<()> { + match self { + Self::List { dump } => list(ctx, dump).await, + } + } +} + +async fn list(ctx: FullCtx, dump: bool) -> std::result::Result<(), color_eyre::eyre::Error> { + let client = ctx.trello_client(); + let cfg = &ctx.cfg.trello; + let board = client + .boards(cfg) + .await + .result? + .into_iter() + .find(|b| b.name == cfg.board_name) + .ok_or_else(|| color_eyre::eyre::eyre!("Board not found"))?; + let api_result = client.lists(&board.id).await; + if dump { + p!(ctx.prt, "{}", api_result.text); + } else { + let lists = api_result.result?; + for list in lists { + p!(ctx.prt, "{}", list.name); + } + } + Ok(()) +}