1use super::client::DiscourseClient;
2use super::error::http_error;
3use anyhow::{Context, Result, anyhow};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7#[derive(Debug, Deserialize, Serialize, Clone)]
9pub struct UserSummary {
10 pub id: u64,
11 pub username: String,
12 #[serde(default)]
13 pub name: Option<String>,
14 #[serde(default)]
15 pub email: Option<String>,
16 #[serde(default)]
17 pub trust_level: Option<u64>,
18 #[serde(default)]
19 pub admin: Option<bool>,
20 #[serde(default)]
21 pub moderator: Option<bool>,
22 #[serde(default)]
23 pub suspended: Option<bool>,
24 #[serde(default)]
25 pub silenced: Option<bool>,
26 #[serde(default)]
27 pub last_seen_at: Option<String>,
28 #[serde(default)]
29 pub created_at: Option<String>,
30}
31
32#[derive(Debug, Deserialize, Serialize, Clone)]
34pub struct UserDetail {
35 pub id: u64,
36 pub username: String,
37 #[serde(default)]
38 pub name: Option<String>,
39 #[serde(default)]
40 pub email: Option<String>,
41 #[serde(default)]
42 pub trust_level: Option<u64>,
43 #[serde(default)]
44 pub admin: Option<bool>,
45 #[serde(default)]
46 pub moderator: Option<bool>,
47 #[serde(default)]
48 pub suspended_till: Option<String>,
49 #[serde(default)]
50 pub silenced_till: Option<String>,
51 #[serde(default)]
52 pub last_seen_at: Option<String>,
53 #[serde(default)]
54 pub created_at: Option<String>,
55 #[serde(default)]
56 pub post_count: Option<u64>,
57 #[serde(default)]
58 pub groups: Vec<Value>,
59}
60
61impl DiscourseClient {
62 pub fn admin_list_users(&self, listing: &str, page: u32) -> Result<Vec<UserSummary>> {
67 let path = format!(
68 "/admin/users/list/{}.json?show_emails=true&page={}",
69 listing, page
70 );
71 let response = self.get(&path)?;
72 let status = response.status();
73 let text = response.text().context("reading user list response")?;
74 if !status.is_success() {
75 return Err(http_error("admin user list request", status, &text));
76 }
77 let users: Vec<UserSummary> =
78 serde_json::from_str(&text).context("parsing user list response")?;
79 Ok(users)
80 }
81
82 pub fn fetch_user_detail(&self, username: &str) -> Result<UserDetail> {
84 let path = format!("/u/{}.json", username);
85 let response = self.get(&path)?;
86 let status = response.status();
87 let text = response.text().context("reading user detail response")?;
88 if !status.is_success() {
89 return Err(http_error("user detail request", status, &text));
90 }
91 let value: Value =
92 serde_json::from_str(&text).context("parsing user detail response")?;
93 let user = value
94 .get("user")
95 .ok_or_else(|| anyhow!("user detail response missing `user` field"))?;
96 let detail: UserDetail =
97 serde_json::from_value(user.clone()).context("deserialising user detail")?;
98 Ok(detail)
99 }
100
101 pub fn suspend_user(&self, user_id: u64, until: &str, reason: &str) -> Result<()> {
105 let path = format!("/admin/users/{}/suspend.json", user_id);
106 let payload = [
107 ("suspend_until", until),
108 ("reason", reason),
109 ];
110 let response = self.send_retrying(|| Ok(self.put(&path)?.form(&payload)))?;
111 let status = response.status();
112 if !status.is_success() {
113 let text = response
114 .text()
115 .unwrap_or_else(|_| "<failed to read response body>".to_string());
116 return Err(http_error("suspend user request", status, &text));
117 }
118 Ok(())
119 }
120
121 pub fn unsuspend_user(&self, user_id: u64) -> Result<()> {
123 let path = format!("/admin/users/{}/unsuspend.json", user_id);
124 let response = self.send_retrying(|| Ok(self.put(&path)?))?;
125 let status = response.status();
126 if !status.is_success() {
127 let text = response
128 .text()
129 .unwrap_or_else(|_| "<failed to read response body>".to_string());
130 return Err(http_error("unsuspend user request", status, &text));
131 }
132 Ok(())
133 }
134}