Skip to main content

dsc/api/
settings.rs

1use super::client::DiscourseClient;
2use super::error::http_error;
3use anyhow::{anyhow, Context, Result};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7/// A single site setting with its full metadata, as returned by
8/// `GET /admin/site_settings.json`.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct SiteSettingDetail {
11    pub setting: String,
12    #[serde(default)]
13    pub value: Value,
14    #[serde(default)]
15    pub default: Value,
16    #[serde(default)]
17    pub description: String,
18    #[serde(default)]
19    pub category: String,
20    /// API field name is `type`; renamed to avoid the Rust keyword collision.
21    #[serde(rename = "type", default)]
22    pub setting_type: String,
23}
24
25impl DiscourseClient {
26    /// Update a site setting by name (admin only).
27    pub fn update_site_setting(&self, setting: &str, value: &str) -> Result<()> {
28        let setting = setting.trim();
29        if setting.is_empty() {
30            return Err(anyhow!("missing site setting name for site setting update"));
31        }
32        if setting.chars().any(|ch| ch.is_whitespace() || ch == '/') {
33            return Err(anyhow!(
34                "site setting name contains invalid characters: {}",
35                setting
36            ));
37        }
38        let path = format!("/admin/site_settings/{}.json", setting);
39        let payload = [("value", value)];
40        let response = self.send_retrying(|| Ok(self.put(&path)?.form(&payload)))?;
41        let status = response.status();
42        let text = response
43            .text()
44            .context("reading site setting update response")?;
45        if !status.is_success() {
46            return Err(http_error("update site setting request", status, &text));
47        }
48        Ok(())
49    }
50
51    /// Fetch all site settings (admin only). Returns raw JSON value.
52    pub fn list_site_settings(&self) -> Result<Value> {
53        let response = self.get("/admin/site_settings.json")?;
54        let status = response.status();
55        let text = response
56            .text()
57            .context("reading site settings list response")?;
58        if !status.is_success() {
59            return Err(http_error("list site settings request", status, &text));
60        }
61        let value: Value =
62            serde_json::from_str(&text).context("parsing site settings list response")?;
63        Ok(value)
64    }
65
66    /// Fetch all site settings with full metadata (admin only).
67    /// Returns one `SiteSettingDetail` per setting, preserving the
68    /// `default`, `description`, `category`, and `type` fields.
69    pub fn list_site_settings_detailed(&self) -> Result<Vec<SiteSettingDetail>> {
70        let raw = self.list_site_settings()?;
71        let arr = raw
72            .get("site_settings")
73            .and_then(|v| v.as_array())
74            .ok_or_else(|| anyhow!("site_settings response missing 'site_settings' array"))?;
75        let mut out = Vec::with_capacity(arr.len());
76        for entry in arr {
77            let detail: SiteSettingDetail = serde_json::from_value(entry.clone())
78                .with_context(|| {
79                    format!(
80                        "parsing site setting entry: {}",
81                        entry.get("setting").and_then(|v| v.as_str()).unwrap_or("?")
82                    )
83                })?;
84            out.push(detail);
85        }
86        Ok(out)
87    }
88
89    /// Fetch a single site setting by name (admin only).
90    /// Returns the value as a string, or an error if not found.
91    pub fn fetch_site_setting(&self, setting: &str) -> Result<String> {
92        let setting = setting.trim();
93        if setting.is_empty() {
94            return Err(anyhow!("missing site setting name"));
95        }
96        // The admin site settings API returns all settings; we filter by name.
97        let all = self.list_site_settings()?;
98        // Response shape: { "site_settings": [ { "setting": "...", "value": ... }, ... ] }
99        let settings = all
100            .get("site_settings")
101            .and_then(|v| v.as_array())
102            .cloned()
103            .unwrap_or_default();
104        for entry in &settings {
105            let name = entry.get("setting").and_then(|v| v.as_str()).unwrap_or("");
106            if name == setting {
107                let value = entry.get("value").cloned().unwrap_or(Value::Null);
108                let display = match &value {
109                    Value::String(s) => s.clone(),
110                    Value::Null => String::new(),
111                    other => other.to_string(),
112                };
113                return Ok(display);
114            }
115        }
116        Err(anyhow!("setting not found: {}", setting))
117    }
118}