Skip to main content

dsc/api/
api_keys.rs

1use super::client::DiscourseClient;
2use super::error::http_error;
3use anyhow::{Context, Result};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7/// One row from /admin/api/keys.json.
8#[derive(Debug, Deserialize, Serialize, Clone)]
9pub struct ApiKeySummary {
10    pub id: u64,
11    #[serde(default)]
12    pub description: Option<String>,
13    #[serde(default, alias = "user_username")]
14    pub username: Option<String>,
15    #[serde(default)]
16    pub last_used_at: Option<String>,
17    #[serde(default)]
18    pub created_at: Option<String>,
19    #[serde(default)]
20    pub revoked_at: Option<String>,
21    #[serde(default)]
22    pub truncated_key: Option<String>,
23}
24
25/// Response from POST /admin/api/keys.json — includes the full secret `key`.
26#[derive(Debug, Deserialize, Serialize, Clone)]
27pub struct CreatedApiKey {
28    pub id: u64,
29    pub key: String,
30    #[serde(default)]
31    pub description: Option<String>,
32    #[serde(default, alias = "user_username")]
33    pub username: Option<String>,
34    #[serde(default)]
35    pub created_at: Option<String>,
36}
37
38impl DiscourseClient {
39    pub fn list_api_keys(&self) -> Result<Vec<ApiKeySummary>> {
40        let response = self.get("/admin/api/keys.json")?;
41        let status = response.status();
42        let text = response.text().context("reading api keys response")?;
43        if !status.is_success() {
44            return Err(http_error("api keys list request", status, &text));
45        }
46        let value: Value = serde_json::from_str(&text).context("parsing api keys response json")?;
47        let keys_value = value
48            .get("keys")
49            .cloned()
50            .unwrap_or(Value::Array(Vec::new()));
51        let keys: Vec<ApiKeySummary> =
52            serde_json::from_value(keys_value).context("deserialising api keys")?;
53        Ok(keys)
54    }
55
56    /// Create a new API key. `username` of `None` makes a global all-users key.
57    pub fn create_api_key(
58        &self,
59        description: &str,
60        username: Option<&str>,
61    ) -> Result<CreatedApiKey> {
62        let mut payload: Vec<(&str, &str)> = vec![("key[description]", description)];
63        if let Some(u) = username {
64            payload.push(("key[username]", u));
65        }
66        let response =
67            self.send_retrying(|| Ok(self.post("/admin/api/keys.json")?.form(&payload)))?;
68        let status = response.status();
69        let text = response.text().context("reading api key create response")?;
70        if !status.is_success() {
71            return Err(http_error("api key create request", status, &text));
72        }
73        let value: Value =
74            serde_json::from_str(&text).context("parsing api key create response")?;
75        let key_obj = value.get("key").unwrap_or(&value);
76        let created: CreatedApiKey =
77            serde_json::from_value(key_obj.clone()).context("deserialising created api key")?;
78        Ok(created)
79    }
80
81    pub fn revoke_api_key(&self, key_id: u64) -> Result<()> {
82        let path = format!("/admin/api/keys/{}.json", key_id);
83        let response = self.send_retrying(|| self.delete_builder(&path))?;
84        let status = response.status();
85        if !status.is_success() {
86            let text = response
87                .text()
88                .unwrap_or_else(|_| "<failed to read response body>".to_string());
89            return Err(http_error("api key revoke request", status, &text));
90        }
91        Ok(())
92    }
93}