Skip to content

Commit 1e4a13c

Browse files
authored
Merge pull request #102 from 23prime/feature/49-wiki-enhancements
feat: add bl wiki count/tag/star/attachment/shared-file commands
2 parents 9f1df2b + e471241 commit 1e4a13c

19 files changed

Lines changed: 1665 additions & 22 deletions

File tree

src/api/mod.rs

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ use space_notification::SpaceNotification;
4747
use team::Team;
4848
use user::{RecentlyViewedIssue, RecentlyViewedProject, RecentlyViewedWiki, Star, StarCount, User};
4949
use watch::{Watching, WatchingCount};
50-
use wiki::{Wiki, WikiAttachment, WikiHistory, WikiListItem};
50+
use wiki::{Wiki, WikiAttachment, WikiCount, WikiHistory, WikiListItem, WikiSharedFile, WikiTag};
5151

5252
/// Abstraction over the Backlog HTTP API.
5353
///
@@ -459,6 +459,49 @@ pub trait BacklogApi {
459459
fn get_wiki_attachments(&self, _wiki_id: u64) -> Result<Vec<WikiAttachment>> {
460460
unimplemented!()
461461
}
462+
fn get_wiki_count(&self, _params: &[(String, String)]) -> Result<WikiCount> {
463+
unimplemented!()
464+
}
465+
fn get_wiki_tags(&self, _params: &[(String, String)]) -> Result<Vec<WikiTag>> {
466+
unimplemented!()
467+
}
468+
fn get_wiki_stars(&self, _wiki_id: u64) -> Result<Vec<Star>> {
469+
unimplemented!()
470+
}
471+
fn add_wiki_attachments(
472+
&self,
473+
_wiki_id: u64,
474+
_attachment_ids: &[u64],
475+
) -> Result<Vec<WikiAttachment>> {
476+
unimplemented!()
477+
}
478+
fn download_wiki_attachment(
479+
&self,
480+
_wiki_id: u64,
481+
_attachment_id: u64,
482+
) -> Result<(Vec<u8>, String)> {
483+
unimplemented!()
484+
}
485+
fn delete_wiki_attachment(&self, _wiki_id: u64, _attachment_id: u64) -> Result<WikiAttachment> {
486+
unimplemented!()
487+
}
488+
fn get_wiki_shared_files(&self, _wiki_id: u64) -> Result<Vec<WikiSharedFile>> {
489+
unimplemented!()
490+
}
491+
fn link_wiki_shared_files(
492+
&self,
493+
_wiki_id: u64,
494+
_shared_file_ids: &[u64],
495+
) -> Result<Vec<WikiSharedFile>> {
496+
unimplemented!()
497+
}
498+
fn unlink_wiki_shared_file(
499+
&self,
500+
_wiki_id: u64,
501+
_shared_file_id: u64,
502+
) -> Result<WikiSharedFile> {
503+
unimplemented!()
504+
}
462505
fn get_teams(&self, _params: &[(String, String)]) -> Result<Vec<Team>> {
463506
unimplemented!()
464507
}
@@ -1055,6 +1098,54 @@ impl BacklogApi for BacklogClient {
10551098
self.get_wiki_attachments(wiki_id)
10561099
}
10571100

1101+
fn get_wiki_count(&self, params: &[(String, String)]) -> Result<WikiCount> {
1102+
self.get_wiki_count(params)
1103+
}
1104+
1105+
fn get_wiki_tags(&self, params: &[(String, String)]) -> Result<Vec<WikiTag>> {
1106+
self.get_wiki_tags(params)
1107+
}
1108+
1109+
fn get_wiki_stars(&self, wiki_id: u64) -> Result<Vec<Star>> {
1110+
self.get_wiki_stars(wiki_id)
1111+
}
1112+
1113+
fn add_wiki_attachments(
1114+
&self,
1115+
wiki_id: u64,
1116+
attachment_ids: &[u64],
1117+
) -> Result<Vec<WikiAttachment>> {
1118+
self.add_wiki_attachments(wiki_id, attachment_ids)
1119+
}
1120+
1121+
fn download_wiki_attachment(
1122+
&self,
1123+
wiki_id: u64,
1124+
attachment_id: u64,
1125+
) -> Result<(Vec<u8>, String)> {
1126+
self.download_wiki_attachment(wiki_id, attachment_id)
1127+
}
1128+
1129+
fn delete_wiki_attachment(&self, wiki_id: u64, attachment_id: u64) -> Result<WikiAttachment> {
1130+
self.delete_wiki_attachment(wiki_id, attachment_id)
1131+
}
1132+
1133+
fn get_wiki_shared_files(&self, wiki_id: u64) -> Result<Vec<WikiSharedFile>> {
1134+
self.get_wiki_shared_files(wiki_id)
1135+
}
1136+
1137+
fn link_wiki_shared_files(
1138+
&self,
1139+
wiki_id: u64,
1140+
shared_file_ids: &[u64],
1141+
) -> Result<Vec<WikiSharedFile>> {
1142+
self.link_wiki_shared_files(wiki_id, shared_file_ids)
1143+
}
1144+
1145+
fn unlink_wiki_shared_file(&self, wiki_id: u64, shared_file_id: u64) -> Result<WikiSharedFile> {
1146+
self.unlink_wiki_shared_file(wiki_id, shared_file_id)
1147+
}
1148+
10581149
fn get_teams(&self, params: &[(String, String)]) -> Result<Vec<Team>> {
10591150
self.get_teams(params)
10601151
}

src/api/wiki.rs

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,23 @@ use anyhow::Result;
44
use serde::{Deserialize, Serialize};
55

66
use super::BacklogClient;
7+
use crate::api::user::Star;
8+
9+
#[derive(Debug, Clone, Serialize, Deserialize)]
10+
pub struct WikiCount {
11+
pub count: u64,
12+
}
13+
14+
#[derive(Debug, Clone, Serialize, Deserialize)]
15+
#[serde(rename_all = "camelCase")]
16+
pub struct WikiSharedFile {
17+
pub id: u64,
18+
pub dir: String,
19+
pub name: String,
20+
pub size: u64,
21+
#[serde(flatten)]
22+
pub extra: BTreeMap<String, serde_json::Value>,
23+
}
724

825
#[derive(Debug, Clone, Serialize, Deserialize)]
926
#[serde(rename_all = "camelCase")]
@@ -116,6 +133,82 @@ impl BacklogClient {
116133
let value = self.get(&format!("/wikis/{}/attachments", wiki_id))?;
117134
deserialize(value)
118135
}
136+
137+
pub fn get_wiki_count(&self, params: &[(String, String)]) -> Result<WikiCount> {
138+
let value = self.get_with_query("/wikis/count", params)?;
139+
deserialize(value)
140+
}
141+
142+
pub fn get_wiki_tags(&self, params: &[(String, String)]) -> Result<Vec<WikiTag>> {
143+
let value = self.get_with_query("/wikis/tags", params)?;
144+
deserialize(value)
145+
}
146+
147+
pub fn get_wiki_stars(&self, wiki_id: u64) -> Result<Vec<Star>> {
148+
let value = self.get(&format!("/wikis/{}/stars", wiki_id))?;
149+
deserialize(value)
150+
}
151+
152+
pub fn add_wiki_attachments(
153+
&self,
154+
wiki_id: u64,
155+
attachment_ids: &[u64],
156+
) -> Result<Vec<WikiAttachment>> {
157+
let params: Vec<(String, String)> = attachment_ids
158+
.iter()
159+
.map(|id| ("attachmentId[]".to_string(), id.to_string()))
160+
.collect();
161+
let value = self.post_form(&format!("/wikis/{}/attachments", wiki_id), &params)?;
162+
deserialize(value)
163+
}
164+
165+
pub fn download_wiki_attachment(
166+
&self,
167+
wiki_id: u64,
168+
attachment_id: u64,
169+
) -> Result<(Vec<u8>, String)> {
170+
self.download(&format!("/wikis/{}/attachments/{}", wiki_id, attachment_id))
171+
}
172+
173+
pub fn delete_wiki_attachment(
174+
&self,
175+
wiki_id: u64,
176+
attachment_id: u64,
177+
) -> Result<WikiAttachment> {
178+
let value =
179+
self.delete_req(&format!("/wikis/{}/attachments/{}", wiki_id, attachment_id))?;
180+
deserialize(value)
181+
}
182+
183+
pub fn get_wiki_shared_files(&self, wiki_id: u64) -> Result<Vec<WikiSharedFile>> {
184+
let value = self.get(&format!("/wikis/{}/sharedFiles", wiki_id))?;
185+
deserialize(value)
186+
}
187+
188+
pub fn link_wiki_shared_files(
189+
&self,
190+
wiki_id: u64,
191+
shared_file_ids: &[u64],
192+
) -> Result<Vec<WikiSharedFile>> {
193+
let params: Vec<(String, String)> = shared_file_ids
194+
.iter()
195+
.map(|id| ("fileId[]".to_string(), id.to_string()))
196+
.collect();
197+
let value = self.post_form(&format!("/wikis/{}/sharedFiles", wiki_id), &params)?;
198+
deserialize(value)
199+
}
200+
201+
pub fn unlink_wiki_shared_file(
202+
&self,
203+
wiki_id: u64,
204+
shared_file_id: u64,
205+
) -> Result<WikiSharedFile> {
206+
let value = self.delete_req(&format!(
207+
"/wikis/{}/sharedFiles/{}",
208+
wiki_id, shared_file_id
209+
))?;
210+
deserialize(value)
211+
}
119212
}
120213

121214
#[cfg(test)]
@@ -286,4 +379,163 @@ mod tests {
286379
let wiki: Wiki = serde_json::from_value(v).unwrap();
287380
assert!(wiki.created_user.user_id.is_none());
288381
}
382+
383+
fn shared_file_json() -> serde_json::Value {
384+
json!({
385+
"id": 1,
386+
"dir": "/docs",
387+
"name": "spec.pdf",
388+
"size": 2048_u64
389+
})
390+
}
391+
392+
fn star_json() -> serde_json::Value {
393+
json!({
394+
"id": 1,
395+
"comment": null,
396+
"url": "https://example.backlog.com/wiki/TEST/Home",
397+
"title": "Home",
398+
"presenter": {
399+
"id": 1, "userId": "john", "name": "John Doe",
400+
"roleType": 1, "mailAddress": "john@example.com"
401+
},
402+
"created": "2024-01-01T00:00:00Z"
403+
})
404+
}
405+
406+
#[test]
407+
fn get_wiki_count_returns_count() {
408+
let server = MockServer::start();
409+
server.mock(|when, then| {
410+
when.method(GET)
411+
.path("/wikis/count")
412+
.query_param("apiKey", TEST_KEY)
413+
.query_param("projectIdOrKey", "TEST");
414+
then.status(200).json_body(json!({"count": 42}));
415+
});
416+
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
417+
let result = client
418+
.get_wiki_count(&[("projectIdOrKey".to_string(), "TEST".to_string())])
419+
.unwrap();
420+
assert_eq!(result.count, 42);
421+
}
422+
423+
#[test]
424+
fn get_wiki_tags_returns_list() {
425+
let server = MockServer::start();
426+
server.mock(|when, then| {
427+
when.method(GET)
428+
.path("/wikis/tags")
429+
.query_param("apiKey", TEST_KEY);
430+
then.status(200)
431+
.json_body(json!([{"id": 1, "name": "guide"}]));
432+
});
433+
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
434+
let tags = client.get_wiki_tags(&[]).unwrap();
435+
assert_eq!(tags.len(), 1);
436+
assert_eq!(tags[0].name, "guide");
437+
}
438+
439+
#[test]
440+
fn get_wiki_stars_returns_list() {
441+
let server = MockServer::start();
442+
server.mock(|when, then| {
443+
when.method(GET)
444+
.path("/wikis/1/stars")
445+
.query_param("apiKey", TEST_KEY);
446+
then.status(200).json_body(json!([star_json()]));
447+
});
448+
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
449+
let stars = client.get_wiki_stars(1).unwrap();
450+
assert_eq!(stars.len(), 1);
451+
assert_eq!(stars[0].title, "Home");
452+
}
453+
454+
#[test]
455+
fn add_wiki_attachments_returns_list() {
456+
let server = MockServer::start();
457+
server.mock(|when, then| {
458+
when.method(POST)
459+
.path("/wikis/1/attachments")
460+
.query_param("apiKey", TEST_KEY);
461+
then.status(200).json_body(json!([attachment_json()]));
462+
});
463+
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
464+
let attachments = client.add_wiki_attachments(1, &[1]).unwrap();
465+
assert_eq!(attachments.len(), 1);
466+
assert_eq!(attachments[0].name, "image.png");
467+
}
468+
469+
#[test]
470+
fn download_wiki_attachment_returns_bytes() {
471+
let server = MockServer::start();
472+
server.mock(|when, then| {
473+
when.method(GET).path("/wikis/1/attachments/1");
474+
then.status(200)
475+
.header("Content-Disposition", "attachment; filename=\"image.png\"")
476+
.body(b"hello");
477+
});
478+
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
479+
let (bytes, filename) = client.download_wiki_attachment(1, 1).unwrap();
480+
assert_eq!(bytes, b"hello");
481+
assert_eq!(filename, "image.png");
482+
}
483+
484+
#[test]
485+
fn delete_wiki_attachment_returns_attachment() {
486+
let server = MockServer::start();
487+
server.mock(|when, then| {
488+
when.method(DELETE)
489+
.path("/wikis/1/attachments/1")
490+
.query_param("apiKey", TEST_KEY);
491+
then.status(200).json_body(attachment_json());
492+
});
493+
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
494+
let attachment = client.delete_wiki_attachment(1, 1).unwrap();
495+
assert_eq!(attachment.name, "image.png");
496+
}
497+
498+
#[test]
499+
fn get_wiki_shared_files_returns_list() {
500+
let server = MockServer::start();
501+
server.mock(|when, then| {
502+
when.method(GET)
503+
.path("/wikis/1/sharedFiles")
504+
.query_param("apiKey", TEST_KEY);
505+
then.status(200).json_body(json!([shared_file_json()]));
506+
});
507+
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
508+
let files = client.get_wiki_shared_files(1).unwrap();
509+
assert_eq!(files.len(), 1);
510+
assert_eq!(files[0].name, "spec.pdf");
511+
}
512+
513+
#[test]
514+
fn link_wiki_shared_files_returns_list() {
515+
let server = MockServer::start();
516+
server.mock(|when, then| {
517+
when.method(POST)
518+
.path("/wikis/1/sharedFiles")
519+
.query_param("apiKey", TEST_KEY);
520+
then.status(200).json_body(json!([shared_file_json()]));
521+
});
522+
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
523+
let files = client.link_wiki_shared_files(1, &[1]).unwrap();
524+
assert_eq!(files.len(), 1);
525+
assert_eq!(files[0].name, "spec.pdf");
526+
}
527+
528+
#[test]
529+
fn unlink_wiki_shared_file_returns_file() {
530+
let server = MockServer::start();
531+
server.mock(|when, then| {
532+
when.method(DELETE)
533+
.path("/wikis/1/sharedFiles/1")
534+
.query_param("apiKey", TEST_KEY);
535+
then.status(200).json_body(shared_file_json());
536+
});
537+
let client = super::super::BacklogClient::new_with(&server.base_url(), TEST_KEY).unwrap();
538+
let file = client.unlink_wiki_shared_file(1, 1).unwrap();
539+
assert_eq!(file.name, "spec.pdf");
540+
}
289541
}

0 commit comments

Comments
 (0)