Skip to main content

dsc/api/
invites.rs

1use super::client::DiscourseClient;
2use super::error::http_error;
3use anyhow::{Context, Result};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7/// Distilled successful response from POST /invites.json.
8#[derive(Debug, Deserialize, Serialize, Clone)]
9pub struct InviteResult {
10    pub id: u64,
11    /// Magic-link the invitee receives.
12    #[serde(default)]
13    pub link: Option<String>,
14    #[serde(default)]
15    pub email: Option<String>,
16}
17
18impl DiscourseClient {
19    /// Create a single email invite.
20    ///
21    /// `group_ids` is sent as comma-joined `group_ids` (Discourse accepts both
22    /// `group_ids[]=...` repetition and a single comma-list; the latter is
23    /// simpler in form-encoded bodies). `topic_id` and `custom_message` are
24    /// optional. Returns the created invite (including the magic link).
25    pub fn create_invite(
26        &self,
27        email: &str,
28        group_ids: &[u64],
29        topic_id: Option<u64>,
30        custom_message: Option<&str>,
31    ) -> Result<InviteResult> {
32        let mut payload: Vec<(&str, String)> = vec![("email", email.to_string())];
33        if !group_ids.is_empty() {
34            payload.push((
35                "group_ids",
36                group_ids
37                    .iter()
38                    .map(|id| id.to_string())
39                    .collect::<Vec<_>>()
40                    .join(","),
41            ));
42        }
43        if let Some(topic) = topic_id {
44            payload.push(("topic_id", topic.to_string()));
45        }
46        if let Some(msg) = custom_message {
47            if !msg.trim().is_empty() {
48                payload.push(("custom_message", msg.to_string()));
49            }
50        }
51        let response = self.send_retrying(|| Ok(self.post("/invites.json")?.form(&payload)))?;
52        let status = response.status();
53        let text = response.text().context("reading invite response")?;
54        if !status.is_success() {
55            return Err(http_error("invite create request", status, &text));
56        }
57        // The response is sometimes the bare invite object, sometimes wrapped
58        // — accept either.
59        let value: Value =
60            serde_json::from_str(&text).context("parsing invite response json")?;
61        let target = value.get("invite").unwrap_or(&value);
62        let result: InviteResult =
63            serde_json::from_value(target.clone()).context("deserialising invite")?;
64        Ok(result)
65    }
66}