diff --git a/src/lib.rs b/src/lib.rs index e165e3d..39f420c 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 6f59d53..e7135c1 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/tests/mod.rs b/src/tests/mod.rs index 8fb92f7..f75e76d 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -4,11 +4,6 @@ use std::collections::HashMap; // type TestResult = Result<(), Box>; use assert2::let_assert; -use kxio::{ - fs::{FileSystem, TempFileSystem}, - net::{MockNet, Net}, - print::Printer, -}; use crate::{config::AppConfig, f, init::run, Ctx, NAME}; @@ -123,6 +118,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 +139,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/api/boards.rs b/src/trello/api/boards.rs index 4056ede..3713fa3 100644 --- a/src/trello/api/boards.rs +++ b/src/trello/api/boards.rs @@ -30,12 +30,3 @@ // .await?; // Ok(board) // } - -// pub trait TrelloBoards { -// fn find_by_name(&self, board_name: &TrelloBoardName) -> Option<&TrelloBoard>; -// } -// impl TrelloBoards for Vec { -// fn find_by_name(&self, board_name: &TrelloBoardName) -> Option<&TrelloBoard> { -// self.iter().find(|b| &b.name == board_name) -// } -// } diff --git a/src/trello/api/members.rs b/src/trello/api/members.rs index dd69296..020716f 100644 --- a/src/trello/api/members.rs +++ b/src/trello/api/members.rs @@ -42,7 +42,10 @@ pub async fn get_boards_that_member_belongs_to( net: &Net, prt: &Printer, ) -> APIResult> { - let auth = TrelloAuth::new(&cfg.api_key, &cfg.api_secret); + 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()) diff --git a/src/trello/boards.rs b/src/trello/boards.rs index 3c50a13..2892dbe 100644 --- a/src/trello/boards.rs +++ b/src/trello/boards.rs @@ -1,4 +1,6 @@ // +use crate::trello::types::board::TrelloBoard; +use crate::trello::types::TrelloBoardName; use crate::{p, FullCtx}; pub(crate) async fn list(ctx: FullCtx, dump: bool) -> color_eyre::Result<()> { @@ -16,3 +18,12 @@ pub(crate) async fn list(ctx: FullCtx, dump: bool) -> color_eyre::Result<()> { } Ok(()) } + +pub trait TrelloBoards { + fn find_by_name(&self, board_name: &TrelloBoardName) -> Option<&TrelloBoard>; +} +impl TrelloBoards for Vec { + fn find_by_name(&self, board_name: &TrelloBoardName) -> Option<&TrelloBoard> { + self.iter().find(|b| &b.name == board_name) + } +} diff --git a/src/trello/types/auth.rs b/src/trello/types/auth.rs index cbb3ce4..273b189 100644 --- a/src/trello/types/auth.rs +++ b/src/trello/types/auth.rs @@ -18,29 +18,13 @@ pub struct TrelloAuth<'cfg> { pub(crate) api_key: &'cfg TrelloApiKey, pub(crate) api_secret: &'cfg TrelloApiSecret, } -impl<'cfg> TrelloAuth<'cfg> { - pub const fn new(api_key: &'cfg TrelloApiKey, api_secret: &'cfg TrelloApiSecret) -> Self { - Self { - api_key, - api_secret, - } - } - - pub const fn api_key(&self) -> &TrelloApiKey { - &self.api_key - } - pub const fn api_token(&self) -> &TrelloApiSecret { - &self.api_secret - } -} -impl<'cfg> From> for HashMap { +impl From> for HashMap { fn from(value: TrelloAuth) -> Self { HashMap::from([( "Authorization".into(), format!( r#"OAuth oauth_consumer_key="{}", oauth_token="{}""#, - value.api_key(), - value.api_token() + value.api_key, value.api_secret ), )]) }