diff --git a/src/lib.rs b/src/lib.rs index 288ff87..2862005 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,6 +46,8 @@ enum NextcloudCommand { Board(NextcloudBoardCommand), #[clap(subcommand)] Stack(NextcloudStackCommand), + #[clap(subcommand)] + Card(NextcloudCardCommand), } #[derive(Parser, Debug)] @@ -64,6 +66,15 @@ enum NextcloudStackCommand { }, } +#[derive(Parser, Debug)] +enum NextcloudCardCommand { + List { + #[clap(long, action = clap::ArgAction::SetTrue)] + dump: bool, + stack_id: i64, + }, +} + #[derive(Parser, Debug)] enum TrelloCommand { #[clap(subcommand)] @@ -141,6 +152,10 @@ pub async fn run(ctx: Ctx) -> color_eyre::Result<()> { Command::Nextcloud(NextcloudCommand::Stack(NextcloudStackCommand::List { dump, })) => nextcloud::stack::list(ctx, dump).await, + Command::Nextcloud(NextcloudCommand::Card(NextcloudCardCommand::List { + dump, + stack_id, + })) => nextcloud::card::list(ctx, dump, stack_id.into()).await, } } } diff --git a/src/nextcloud/card.rs b/src/nextcloud/card.rs new file mode 100644 index 0000000..e6ed8ac --- /dev/null +++ b/src/nextcloud/card.rs @@ -0,0 +1,24 @@ +// +use crate::nextcloud::model::NextcloudStackId; +use crate::{p, FullCtx}; + +pub(crate) async fn list( + ctx: FullCtx, + dump: bool, + stack_id: NextcloudStackId, +) -> color_eyre::Result<()> { + let api_result = ctx + .deck_client() + .get_stack(ctx.cfg.nextcloud.board_id, stack_id) + .await; + if dump { + p!(ctx.prt, "{}", api_result.text); + } else { + let mut cards = api_result.result?.cards; + cards.sort_by_key(|card| card.title.clone()); + cards + .iter() + .for_each(|card| p!(ctx.prt, "{}:{}", card.id, card.title)); + } + Ok(()) +} diff --git a/src/nextcloud/mod.rs b/src/nextcloud/mod.rs index 441e8eb..cc96d8e 100644 --- a/src/nextcloud/mod.rs +++ b/src/nextcloud/mod.rs @@ -2,12 +2,18 @@ use bytes::Bytes; use kxio::net::{Net, ReqBuilder}; -use crate::{api_result::APIResult, f, FullCtx}; - -use crate::nextcloud::model::{NextcloudHostname, NextcloudPassword, NextcloudUsername}; -use model::{Board, Card, NextcloudBoardId, Stack}; +use crate::{ + api_result::APIResult, + f, + nextcloud::model::{ + Board, Card, NextcloudBoardId, NextcloudHostname, NextcloudPassword, NextcloudStackId, + NextcloudUsername, Stack, + }, + FullCtx, +}; pub mod board; +pub mod card; pub mod model; pub mod stack; @@ -101,6 +107,17 @@ impl<'ctx> DeckClient<'ctx> { .await } + pub async fn get_stack( + &self, + board_id: NextcloudBoardId, + stack_id: NextcloudStackId, + ) -> APIResult { + self.request(f!("boards/{board_id}/stacks/{stack_id}"), |net, url| { + net.get(url) + }) + .await + } + pub async fn create_card( &self, board_id: i64, diff --git a/src/nextcloud/model.rs b/src/nextcloud/model.rs index 4849160..de58143 100644 --- a/src/nextcloud/model.rs +++ b/src/nextcloud/model.rs @@ -141,9 +141,11 @@ pub struct Stack { pub board_id: NextcloudBoardId, #[serde(rename = "ETag")] pub etag: NextcloudETag, + #[serde(default)] + pub cards: Vec, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub struct Card { pub id: NextcloudCardId, pub title: NextcloudCardTitle, @@ -152,8 +154,10 @@ pub struct Card { pub stack_id: NextcloudStackId, pub order: NextcloudOrder, pub archived: bool, + #[serde(default)] pub due_date: Option, - pub labels: Vec, + #[serde(default)] + pub labels: Option>, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] diff --git a/src/nextcloud/tests.rs b/src/nextcloud/tests.rs index 1e212c5..b7cfdd3 100644 --- a/src/nextcloud/tests.rs +++ b/src/nextcloud/tests.rs @@ -1,13 +1,18 @@ // -use kxio::net::StatusCode; +use kxio::{ + fs::TempFileSystem, + net::{MockNet, StatusCode}, + print::Printer, +}; use crate::{ - config::NextcloudConfig, + config::{NextcloudConfig, TrelloConfig}, nextcloud::{ model::{ - Board, NextcloudBoardColour, NextcloudBoardId, NextcloudBoardOwner, - NextcloudBoardTitle, NextcloudETag, NextcloudHostname, NextcloudOrder, - NextcloudPassword, NextcloudStackId, NextcloudStackTitle, NextcloudUsername, Stack, + Board, Card, NextcloudBoardColour, NextcloudBoardId, NextcloudBoardOwner, + NextcloudBoardTitle, NextcloudCardId, NextcloudCardTitle, NextcloudETag, + NextcloudHostname, NextcloudOrder, NextcloudPassword, NextcloudStackId, + NextcloudStackTitle, NextcloudUsername, Stack, }, DeckClient, }, @@ -116,15 +121,7 @@ mod commands { .expect("mock request"); let fs = given::a_filesystem(); - let ctx = FullCtx { - fs: fs.as_real(), - net: mock_net.into(), - prt: given::a_printer(), - cfg: AppConfig { - trello: given::a_trello_config(), - nextcloud: given::a_nextcloud_config(), - }, - }; + let ctx = given::a_full_context(mock_net, fs); let deck_client = DeckClient::new(&ctx); //when @@ -166,15 +163,7 @@ mod commands { .expect("mock request"); let fs = given::a_filesystem(); - let ctx = FullCtx { - fs: fs.as_real(), - net: mock_net.into(), - prt: given::a_printer(), - cfg: AppConfig { - trello: given::a_trello_config(), - nextcloud: given::a_nextcloud_config(), - }, - }; + let ctx = given::a_full_context(mock_net, fs); let deck_client = DeckClient::new(&ctx); //when @@ -192,32 +181,135 @@ mod commands { title: NextcloudStackTitle::new("Done"), order: NextcloudOrder::new(2), board_id: NextcloudBoardId::new(1), - etag: NextcloudETag::new("97592874d17017ef4f620c9c2a490086") + etag: NextcloudETag::new("97592874d17017ef4f620c9c2a490086"), + cards: vec![Card { + id: NextcloudCardId::new(322), + title: NextcloudCardTitle::new("Lunch: Soup & Toast"), + description: Some(s!("")), + stack_id: NextcloudStackId::new(3), + order: NextcloudOrder::new(0), + archived: false, + due_date: None, + labels: Some(vec![]) + }] }, Stack { id: NextcloudStackId::new(2), title: NextcloudStackTitle::new("Doing"), order: NextcloudOrder::new(1), board_id: NextcloudBoardId::new(1), - etag: NextcloudETag::new("3da05f904903c88450b79e4f8f6e2160") + etag: NextcloudETag::new("3da05f904903c88450b79e4f8f6e2160"), + cards: vec![ + Card { + id: NextcloudCardId::new(319), + title: NextcloudCardTitle::new("That"), + description: Some(s!("")), + stack_id: NextcloudStackId::new(2), + order: NextcloudOrder::new(0), + archived: false, + due_date: None, + labels: Some(vec![]) + }, + Card { + id: NextcloudCardId::new(323), + title: NextcloudCardTitle::new( + "Second lunch: Poached Egg & Toasted Muffin" + ), + description: Some(s!("")), + stack_id: NextcloudStackId::new(2), + order: NextcloudOrder::new(1), + archived: false, + due_date: None, + labels: Some(vec![]) + } + ] }, Stack { id: NextcloudStackId::new(1), title: NextcloudStackTitle::new("To do"), order: NextcloudOrder::new(0), board_id: NextcloudBoardId::new(1), - etag: NextcloudETag::new("b567d287210fa4d9b108ac68d5b087c1") + etag: NextcloudETag::new("b567d287210fa4d9b108ac68d5b087c1"), + cards: vec![ + Card { + id: NextcloudCardId::new(318), + title: NextcloudCardTitle::new("This"), + description: Some(s!("")), + stack_id: NextcloudStackId::new(1), + order: NextcloudOrder::new(0), + archived: false, + due_date: None, + labels: Some(vec![]) + }, + Card { + id: NextcloudCardId::new(321), + title: NextcloudCardTitle::new("Breakfast: Cereal"), + description: Some(s!("")), + stack_id: NextcloudStackId::new(1), + order: NextcloudOrder::new(1), + archived: false, + due_date: None, + labels: Some(vec![]) + } + ] } ] ); } } + + mod card { + use super::*; + + #[tokio::test] + async fn list() { + //given + let mock_net = kxio::net::mock(); + + mock_net + .on() + .get("https://host-name/index.php/apps/deck/api/v1.0/boards/2/stacks/1") + .basic_auth("username", Some("password")) + .respond(StatusCode::OK) + .body(include_str!("../tests/responses/nextcloud-card-list.json")) + .expect("mock request"); + + let fs = given::a_filesystem(); + let ctx = given::a_full_context(mock_net, fs); + let deck_client = DeckClient::new(&ctx); + + //when + let result = deck_client + .get_stack(ctx.cfg.nextcloud.board_id, 1.into()) + .await + .result + .expect("get stacks"); + + assert_eq!( + result, + Stack { + id: NextcloudStackId::new(3), + title: NextcloudStackTitle::new("Done"), + order: NextcloudOrder::new(2), + board_id: NextcloudBoardId::new(1), + etag: NextcloudETag::new("dda386b3b247d7b4bd8917e19d38c01b"), + cards: vec![Card { + id: NextcloudCardId::new(322), + title: NextcloudCardTitle::new("Lunch: Soup & Toast"), + description: Some(s!("")), + stack_id: NextcloudStackId::new(3), + order: NextcloudOrder::new(0), + archived: false, + due_date: None, + labels: None + }] + } + ); + } + } } mod given { - use kxio::{fs::TempFileSystem, print::Printer}; - - use crate::{config::TrelloConfig, s}; use super::*; @@ -250,15 +342,15 @@ mod given { } } - // pub(crate) fn a_full_context(mock_net: MockNet, fs: TempFileSystem) -> FullCtx { - // FullCtx { - // fs: fs.as_real(), - // net: mock_net.into(), - // prt: given::a_printer(), - // cfg: AppConfig { - // trello: given::a_trello_config(), - // nextcloud: given::a_nextcloud_config(), - // }, - // } - // } + pub(crate) fn a_full_context(mock_net: MockNet, fs: TempFileSystem) -> FullCtx { + FullCtx { + fs: fs.as_real(), + net: mock_net.into(), + prt: given::a_printer(), + cfg: AppConfig { + trello: given::a_trello_config(), + nextcloud: given::a_nextcloud_config(), + }, + } + } } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 8fb92f7..f7746e8 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -123,6 +123,11 @@ mod template { mod given { use super::*; + use kxio::{ + fs::{FileSystem, TempFileSystem}, + net::{MockNet, Net}, + print::Printer, + }; pub fn a_context(fs: FileSystem, net: Net, prt: Printer) -> Ctx { Ctx { fs, net, prt } @@ -139,32 +144,4 @@ mod given { pub fn a_printer() -> Printer { kxio::print::test() } - - // pub fn a_config() -> AppConfig { - // AppConfig { - // trello: a_trello_config(), - // nextcloud: a_nextcloud_config(), - // } - // } - - // pub fn a_trello_config() -> TrelloConfig { - // TrelloConfig { - // api_key: s!("trello-api-key").into(), - // api_secret: s!("trello-api-secret").into(), - // board_name: s!("Trello Platform Changes").into(), - // } - // } - - // pub fn a_nextcloud_config() -> NextcloudConfig { - // let hostname = s!("nextcloud.example.org").into(); - // let username = s!("username").into(); - // let password = s!("password").into(); - // let board_id = NextcloudBoardId::new(2); - // NextcloudConfig { - // hostname, - // username, - // password, - // board_id, - // } - // } } diff --git a/src/tests/responses/nextcloud-card-list.json b/src/tests/responses/nextcloud-card-list.json new file mode 100644 index 0000000..412dd55 --- /dev/null +++ b/src/tests/responses/nextcloud-card-list.json @@ -0,0 +1,47 @@ +{ + "id": 3, + "title": "Done", + "boardId": 1, + "deletedAt": 0, + "lastModified": 1733515991, + "cards": [ + { + "id": 322, + "title": "Lunch: Soup & Toast", + "description": "", + "stackId": 3, + "type": "plain", + "lastModified": 1733515991, + "lastEditor": "pcampbell", + "createdAt": 1733043472, + "labels": null, + "assignedUsers": [ + { + "id": 25, + "participant": { + "primaryKey": "pcampbell", + "uid": "pcampbell", + "displayname": "Paul Campbell", + "type": 0 + }, + "cardId": 322, + "type": 0 + } + ], + "attachments": null, + "attachmentCount": 0, + "owner": "pcampbell", + "order": 0, + "archived": false, + "done": null, + "duedate": null, + "deletedAt": 0, + "commentsUnread": 0, + "commentsCount": 0, + "ETag": "dda386b3b247d7b4bd8917e19d38c01b", + "overdue": 0 + } + ], + "order": 2, + "ETag": "dda386b3b247d7b4bd8917e19d38c01b" +} \ No newline at end of file diff --git a/src/trello/tests.rs b/src/trello/tests.rs index ce28320..fcb9258 100644 --- a/src/trello/tests.rs +++ b/src/trello/tests.rs @@ -2,81 +2,42 @@ use std::collections::HashMap; use crate::{s, trello::types::auth::TrelloAuth}; +use crate::trello::{ + api::boards::TrelloBoards as _, + types::{ + TrelloBoard, TrelloBoardId, TrelloBoardName, TrelloList, TrelloListId, + TrelloListName, + }, +}; -mod commands { - mod board { - use crate::trello::{ - api::boards::TrelloBoards as _, - types::{ - TrelloBoard, TrelloBoardId, TrelloBoardName, TrelloList, TrelloListId, - TrelloListName, - }, - }; - - #[test] - fn name_returns_name() { - //given - let board = TrelloBoard::new( - TrelloBoardId::new("board-id"), - TrelloBoardName::new("board-name"), +mod board { + #[test] + fn list_of_boards_find_by_name_returns_board() { + //given + let board = TrelloBoard::new( + TrelloBoardId::new("2"), + TrelloBoardName::new("beta"), + vec![], + ); + let boards = vec![ + TrelloBoard::new( + TrelloBoardId::new("1"), + TrelloBoardName::new("alpha"), vec![], - ); - - //when - let result = board.name(); - - //then - assert_eq!(result, &TrelloBoardName::new("board-name")); - } - - #[test] - fn lists_should_return_lists() { - //given - let lists = vec![TrelloList::new( - TrelloListId::new("list-id"), - TrelloListName::new("list-name"), - )]; - let board = TrelloBoard::new( - TrelloBoardId::new("board-id"), - TrelloBoardName::new("board-name"), - lists.clone(), - ); - - //when - let result = board.lists(); - - //then - assert_eq!(result, lists); - } - - #[test] - fn list_of_boards_find_by_name_returns_board() { - //given - let board = TrelloBoard::new( - TrelloBoardId::new("2"), - TrelloBoardName::new("beta"), + ), + board.clone(), + TrelloBoard::new( + TrelloBoardId::new("3"), + TrelloBoardName::new("gamma"), vec![], - ); - let boards = vec![ - TrelloBoard::new( - TrelloBoardId::new("1"), - TrelloBoardName::new("alpha"), - vec![], - ), - board.clone(), - TrelloBoard::new( - TrelloBoardId::new("3"), - TrelloBoardName::new("gamma"), - vec![], - ), - ]; + ), + ]; - //when - let result = boards.find_by_name(board.name()); + //when + let result = boards.find_by_name(board.name()); - //then - assert_eq!(result, Some(&board)); - } + //then + assert_eq!(result, Some(&board)); } } @@ -97,6 +58,6 @@ fn trello_auth_into_hashmap() { HashMap::from([( s!("Authorization"), s!("OAuth oauth_consumer_key=\"key\", oauth_token=\"token\"") - ),]) + ), ]) ); } \ No newline at end of file