1use anyhow::{Context, Result, anyhow};
2use serde_json::{Value, json};
3use std::path::Path;
4
5use super::client::DiscourseClient;
6use super::error::http_error;
7
8impl DiscourseClient {
9 pub fn list_themes(&self) -> Result<Value> {
11 let response = self.get("/admin/themes.json")?;
12 let status = response.status();
13 let text = response.text().context("reading themes response body")?;
14 if !status.is_success() {
15 return Err(http_error("themes request", status, &text));
16 }
17 let value: Value = serde_json::from_str(&text).context("parsing themes response")?;
18 Ok(value)
19 }
20
21 pub fn fetch_theme(&self, theme_id: u64) -> Result<Value> {
23 let response = self.get(&format!("/admin/themes/{}.json", theme_id))?;
24 let status = response.status();
25 let text = response.text().context("reading theme response body")?;
26 if !status.is_success() {
27 return Err(http_error("theme request", status, &text));
28 }
29 let value: Value = serde_json::from_str(&text).context("parsing theme response")?;
30 Ok(value)
31 }
32
33 pub fn create_theme(&self, theme: &Value) -> Result<u64> {
35 let payload = json!({ "theme": theme });
36 let response =
37 self.send_retrying(|| Ok(self.post("/admin/themes.json")?.json(&payload)))?;
38 let status = response.status();
39 let text = response.text().context("reading create theme response")?;
40 if !status.is_success() {
41 return Err(http_error("create theme request", status, &text));
42 }
43 let value: Value = serde_json::from_str(&text).context("parsing create theme response")?;
44 let id = value
45 .get("theme")
46 .and_then(|v| v.get("id"))
47 .or_else(|| value.get("id"))
48 .and_then(|v| v.as_u64())
49 .ok_or_else(|| anyhow!("missing theme id in create response"))?;
50 Ok(id)
51 }
52
53 pub fn delete_theme(&self, theme_id: u64) -> Result<()> {
55 let response = self.delete(&format!("/admin/themes/{}.json", theme_id))?;
56 let status = response.status();
57 let text = response.text().context("reading delete theme response")?;
58 if !status.is_success() {
59 return Err(http_error("delete theme request", status, &text));
60 }
61 Ok(())
62 }
63
64 pub fn update_theme(&self, theme_id: u64, theme: &Value) -> Result<()> {
66 let payload = json!({ "theme": theme });
67 let path = format!("/admin/themes/{}.json", theme_id);
68 let response = self.send_retrying(|| Ok(self.put(&path)?.json(&payload)))?;
69 let status = response.status();
70 let text = response.text().context("reading update theme response")?;
71 if !status.is_success() {
72 return Err(http_error("update theme request", status, &text));
73 }
74 Ok(())
75 }
76
77 pub fn set_theme_setting(&self, theme_id: u64, name: &str, value: &str) -> Result<()> {
82 let path = format!("/admin/themes/{}/setting.json", theme_id);
83 let payload = [("name", name), ("value", value)];
84 let response = self.send_retrying(|| Ok(self.put(&path)?.form(&payload)))?;
85 let status = response.status();
86 let text = response.text().context("reading theme setting response")?;
87 if !status.is_success() {
88 return Err(http_error("theme setting update request", status, &text));
89 }
90 Ok(())
91 }
92
93 pub fn put_theme_flag(&self, theme_id: u64, flag: &str) -> Result<Value> {
98 let payload = json!({ "theme": { flag: true } });
99 let path = format!("/admin/themes/{}.json", theme_id);
100 let response = self.send_retrying(|| Ok(self.put(&path)?.json(&payload)))?;
101 let status = response.status();
102 let text = response.text().context("reading theme flag response")?;
103 if !status.is_success() {
104 return Err(http_error("theme flag update request", status, &text));
105 }
106 let value: Value = serde_json::from_str(&text).context("parsing theme flag response")?;
107 Ok(value)
108 }
109
110 pub fn import_theme_remote(&self, remote: &str, branch: Option<&str>) -> Result<Value> {
116 let mut form: Vec<(&str, &str)> = vec![("remote", remote)];
117 if let Some(b) = branch.filter(|b| !b.is_empty()) {
118 form.push(("branch", b));
119 }
120 let response =
121 self.send_retrying(|| Ok(self.post("/admin/themes/import.json")?.form(&form)))?;
122 let status = response.status();
123 let text = response.text().context("reading theme import response")?;
124 if !status.is_success() {
125 return Err(http_error("theme import request", status, &text));
126 }
127 serde_json::from_str(&text).context("parsing theme import response")
128 }
129
130 pub fn import_theme_bundle(&self, file: &Path) -> Result<Value> {
133 let make_form = || -> Result<reqwest::blocking::multipart::Form> {
134 let bytes =
135 std::fs::read(file).with_context(|| format!("reading {}", file.display()))?;
136 let filename = file
137 .file_name()
138 .and_then(|s| s.to_str())
139 .ok_or_else(|| anyhow!("theme bundle path missing filename: {}", file.display()))?
140 .to_string();
141 let part = reqwest::blocking::multipart::Part::bytes(bytes).file_name(filename);
142 Ok(reqwest::blocking::multipart::Form::new().part("bundle", part))
143 };
144 let response = self.send_retrying(|| {
145 Ok(self
146 .post("/admin/themes/import.json")?
147 .multipart(make_form()?))
148 })?;
149 let status = response.status();
150 let text = response
151 .text()
152 .context("reading theme bundle import response")?;
153 if !status.is_success() {
154 return Err(http_error("theme bundle import request", status, &text));
155 }
156 serde_json::from_str(&text).context("parsing theme bundle import response")
157 }
158}