feat(nextcloud): add command 'nextcloud card list'

This commit is contained in:
Paul Campbell 2024-11-30 18:04:48 +00:00
parent 6977816a7f
commit 13f34b3439
8 changed files with 282 additions and 145 deletions

View file

@ -46,6 +46,8 @@ enum NextcloudCommand {
Board(NextcloudBoardCommand), Board(NextcloudBoardCommand),
#[clap(subcommand)] #[clap(subcommand)]
Stack(NextcloudStackCommand), Stack(NextcloudStackCommand),
#[clap(subcommand)]
Card(NextcloudCardCommand),
} }
#[derive(Parser, Debug)] #[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)] #[derive(Parser, Debug)]
enum TrelloCommand { enum TrelloCommand {
#[clap(subcommand)] #[clap(subcommand)]
@ -141,6 +152,10 @@ pub async fn run(ctx: Ctx) -> color_eyre::Result<()> {
Command::Nextcloud(NextcloudCommand::Stack(NextcloudStackCommand::List { Command::Nextcloud(NextcloudCommand::Stack(NextcloudStackCommand::List {
dump, dump,
})) => nextcloud::stack::list(ctx, dump).await, })) => nextcloud::stack::list(ctx, dump).await,
Command::Nextcloud(NextcloudCommand::Card(NextcloudCardCommand::List {
dump,
stack_id,
})) => nextcloud::card::list(ctx, dump, stack_id.into()).await,
} }
} }
} }

24
src/nextcloud/card.rs Normal file
View file

@ -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(())
}

View file

@ -2,12 +2,18 @@
use bytes::Bytes; use bytes::Bytes;
use kxio::net::{Net, ReqBuilder}; use kxio::net::{Net, ReqBuilder};
use crate::{api_result::APIResult, f, FullCtx}; use crate::{
api_result::APIResult,
use crate::nextcloud::model::{NextcloudHostname, NextcloudPassword, NextcloudUsername}; f,
use model::{Board, Card, NextcloudBoardId, Stack}; nextcloud::model::{
Board, Card, NextcloudBoardId, NextcloudHostname, NextcloudPassword, NextcloudStackId,
NextcloudUsername, Stack,
},
FullCtx,
};
pub mod board; pub mod board;
pub mod card;
pub mod model; pub mod model;
pub mod stack; pub mod stack;
@ -101,6 +107,17 @@ impl<'ctx> DeckClient<'ctx> {
.await .await
} }
pub async fn get_stack(
&self,
board_id: NextcloudBoardId,
stack_id: NextcloudStackId,
) -> APIResult<Stack> {
self.request(f!("boards/{board_id}/stacks/{stack_id}"), |net, url| {
net.get(url)
})
.await
}
pub async fn create_card( pub async fn create_card(
&self, &self,
board_id: i64, board_id: i64,

View file

@ -141,9 +141,11 @@ pub struct Stack {
pub board_id: NextcloudBoardId, pub board_id: NextcloudBoardId,
#[serde(rename = "ETag")] #[serde(rename = "ETag")]
pub etag: NextcloudETag, pub etag: NextcloudETag,
#[serde(default)]
pub cards: Vec<Card>,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct Card { pub struct Card {
pub id: NextcloudCardId, pub id: NextcloudCardId,
pub title: NextcloudCardTitle, pub title: NextcloudCardTitle,
@ -152,8 +154,10 @@ pub struct Card {
pub stack_id: NextcloudStackId, pub stack_id: NextcloudStackId,
pub order: NextcloudOrder, pub order: NextcloudOrder,
pub archived: bool, pub archived: bool,
#[serde(default)]
pub due_date: Option<String>, pub due_date: Option<String>,
pub labels: Vec<NextcloudLabelId>, #[serde(default)]
pub labels: Option<Vec<String>>,
} }
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]

View file

@ -1,13 +1,18 @@
// //
use kxio::net::StatusCode; use kxio::{
fs::TempFileSystem,
net::{MockNet, StatusCode},
print::Printer,
};
use crate::{ use crate::{
config::NextcloudConfig, config::{NextcloudConfig, TrelloConfig},
nextcloud::{ nextcloud::{
model::{ model::{
Board, NextcloudBoardColour, NextcloudBoardId, NextcloudBoardOwner, Board, Card, NextcloudBoardColour, NextcloudBoardId, NextcloudBoardOwner,
NextcloudBoardTitle, NextcloudETag, NextcloudHostname, NextcloudOrder, NextcloudBoardTitle, NextcloudCardId, NextcloudCardTitle, NextcloudETag,
NextcloudPassword, NextcloudStackId, NextcloudStackTitle, NextcloudUsername, Stack, NextcloudHostname, NextcloudOrder, NextcloudPassword, NextcloudStackId,
NextcloudStackTitle, NextcloudUsername, Stack,
}, },
DeckClient, DeckClient,
}, },
@ -116,15 +121,7 @@ mod commands {
.expect("mock request"); .expect("mock request");
let fs = given::a_filesystem(); let fs = given::a_filesystem();
let ctx = FullCtx { let ctx = given::a_full_context(mock_net, fs);
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 deck_client = DeckClient::new(&ctx); let deck_client = DeckClient::new(&ctx);
//when //when
@ -166,15 +163,7 @@ mod commands {
.expect("mock request"); .expect("mock request");
let fs = given::a_filesystem(); let fs = given::a_filesystem();
let ctx = FullCtx { let ctx = given::a_full_context(mock_net, fs);
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 deck_client = DeckClient::new(&ctx); let deck_client = DeckClient::new(&ctx);
//when //when
@ -192,32 +181,135 @@ mod commands {
title: NextcloudStackTitle::new("Done"), title: NextcloudStackTitle::new("Done"),
order: NextcloudOrder::new(2), order: NextcloudOrder::new(2),
board_id: NextcloudBoardId::new(1), 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 { Stack {
id: NextcloudStackId::new(2), id: NextcloudStackId::new(2),
title: NextcloudStackTitle::new("Doing"), title: NextcloudStackTitle::new("Doing"),
order: NextcloudOrder::new(1), order: NextcloudOrder::new(1),
board_id: NextcloudBoardId::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 { Stack {
id: NextcloudStackId::new(1), id: NextcloudStackId::new(1),
title: NextcloudStackTitle::new("To do"), title: NextcloudStackTitle::new("To do"),
order: NextcloudOrder::new(0), order: NextcloudOrder::new(0),
board_id: NextcloudBoardId::new(1), 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 { mod given {
use kxio::{fs::TempFileSystem, print::Printer};
use crate::{config::TrelloConfig, s};
use super::*; use super::*;
@ -250,15 +342,15 @@ mod given {
} }
} }
// pub(crate) fn a_full_context(mock_net: MockNet, fs: TempFileSystem) -> FullCtx { pub(crate) fn a_full_context(mock_net: MockNet, fs: TempFileSystem) -> FullCtx {
// FullCtx { FullCtx {
// fs: fs.as_real(), fs: fs.as_real(),
// net: mock_net.into(), net: mock_net.into(),
// prt: given::a_printer(), prt: given::a_printer(),
// cfg: AppConfig { cfg: AppConfig {
// trello: given::a_trello_config(), trello: given::a_trello_config(),
// nextcloud: given::a_nextcloud_config(), nextcloud: given::a_nextcloud_config(),
// }, },
// } }
// } }
} }

View file

@ -123,6 +123,11 @@ mod template {
mod given { mod given {
use super::*; use super::*;
use kxio::{
fs::{FileSystem, TempFileSystem},
net::{MockNet, Net},
print::Printer,
};
pub fn a_context(fs: FileSystem, net: Net, prt: Printer) -> Ctx { pub fn a_context(fs: FileSystem, net: Net, prt: Printer) -> Ctx {
Ctx { fs, net, prt } Ctx { fs, net, prt }
@ -139,32 +144,4 @@ mod given {
pub fn a_printer() -> Printer { pub fn a_printer() -> Printer {
kxio::print::test() 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,
// }
// }
} }

View file

@ -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"
}

View file

@ -2,9 +2,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::{s, trello::types::auth::TrelloAuth}; use crate::{s, trello::types::auth::TrelloAuth};
mod commands {
mod board {
use crate::trello::{ use crate::trello::{
api::boards::TrelloBoards as _, api::boards::TrelloBoards as _,
types::{ types::{
@ -13,42 +10,7 @@ mod commands {
}, },
}; };
#[test] mod board {
fn name_returns_name() {
//given
let board = TrelloBoard::new(
TrelloBoardId::new("board-id"),
TrelloBoardName::new("board-name"),
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] #[test]
fn list_of_boards_find_by_name_returns_board() { fn list_of_boards_find_by_name_returns_board() {
//given //given
@ -78,7 +40,6 @@ mod commands {
assert_eq!(result, Some(&board)); assert_eq!(result, Some(&board));
} }
} }
}
#[test] #[test]
fn trello_auth_into_hashmap() { fn trello_auth_into_hashmap() {