1use super::client::DiscourseClient;
2use super::error::http_error;
3use super::models::{CategoriesResponse, CategoryInfo, CategoryResponse, CreateCategoryResponse};
4use anyhow::{Context, Result, anyhow};
5use reqwest::StatusCode;
6use serde_json::Value;
7use std::collections::HashMap;
8
9impl DiscourseClient {
10 pub fn fetch_category(&self, category_id: u64) -> Result<CategoryResponse> {
12 let path = format!("/c/{}.json", category_id);
13 let response = self.get(&path)?;
14 let status = response.status();
15 let text = response.text().context("reading category response body")?;
16 if !status.is_success() {
17 if status == StatusCode::NOT_FOUND {
18 return Err(anyhow!("category not found: {}", category_id));
19 }
20 return Err(anyhow!("category request failed with {}", status));
21 }
22 let body: CategoryResponse =
23 serde_json::from_str(&text).context("reading category json")?;
24 Ok(body)
25 }
26
27 pub fn fetch_categories(&self) -> Result<Vec<CategoryInfo>> {
29 let response = self.get("/categories.json?include_subcategories=true")?;
30 let status = response.status();
31 let text = response
32 .text()
33 .context("reading categories response body")?;
34 if !status.is_success() {
35 return Err(anyhow!("categories request failed with {}", status));
36 }
37 let body: CategoriesResponse =
38 serde_json::from_str(&text).context("reading categories json")?;
39 let mut categories = body.category_list.categories;
40 if let Ok(site_categories) = self.fetch_site_categories() {
41 let mut seen = HashMap::new();
42 for (idx, cat) in categories.iter().enumerate() {
43 if let Some(id) = cat.id {
44 seen.insert(id, idx);
45 }
46 }
47 for cat in site_categories {
48 if let Some(id) = cat.id {
49 if !seen.contains_key(&id) {
50 categories.push(cat);
51 }
52 }
53 }
54 }
55 Ok(categories)
56 }
57
58 pub fn create_category(&self, category: &CategoryInfo) -> Result<u64> {
60 let mut payload = vec![("name", category.name.clone())];
61 if !category.slug.is_empty() {
62 payload.push(("slug", category.slug.clone()));
63 }
64 if let Some(color) = category.color.clone() {
65 payload.push(("color", color));
66 }
67 if let Some(text_color) = category.text_color.clone() {
68 payload.push(("text_color", text_color));
69 }
70 let response = self
71 .post("/categories")?
72 .form(&payload)
73 .send()
74 .context("creating category")?;
75 let status = response.status();
76 let text = response.text().context("reading category response body")?;
77 if !status.is_success() {
78 return Err(anyhow!("create category failed with {}", status));
79 }
80 let body: CreateCategoryResponse =
81 serde_json::from_str(&text).context("reading category response")?;
82 Ok(body.category.id)
83 }
84
85 fn fetch_site_categories(&self) -> Result<Vec<CategoryInfo>> {
86 let response = self.get("/site.json")?;
87 let status = response.status();
88 let text = response.text().context("reading site.json response body")?;
89 if !status.is_success() {
90 return Err(http_error("site.json request", status, &text));
91 }
92 let value: Value = serde_json::from_str(&text).context("parsing site.json")?;
93 let array = value
94 .get("categories")
95 .and_then(|v| v.as_array())
96 .or_else(|| {
97 value
98 .get("site")
99 .and_then(|v| v.get("categories"))
100 .and_then(|v| v.as_array())
101 })
102 .ok_or_else(|| anyhow!("site.json missing categories list"))?;
103 let mut categories = Vec::new();
104 for item in array {
105 if let Ok(cat) = serde_json::from_value::<CategoryInfo>(item.clone()) {
106 categories.push(cat);
107 }
108 }
109 Ok(categories)
110 }
111}