revolt_database/models/channel_webhooks/
model.rs

1use revolt_result::Result;
2
3use crate::events::client::EventV1;
4use crate::{Database, File};
5
6auto_derived_partial!(
7    /// Webhook
8    pub struct Webhook {
9        /// Webhook Id
10        #[serde(rename = "_id")]
11        pub id: String,
12
13        /// The name of the webhook
14        pub name: String,
15
16        /// The avatar of the webhook
17        #[serde(skip_serializing_if = "Option::is_none")]
18        pub avatar: Option<File>,
19
20        /// User that created this webhook
21        pub creator_id: String,
22
23        /// The channel this webhook belongs to
24        pub channel_id: String,
25
26        /// The permissions of the webhook
27        pub permissions: u64,
28
29        /// The private token for the webhook
30        pub token: Option<String>,
31    },
32    "PartialWebhook"
33);
34
35auto_derived!(
36    /// Optional fields on webhook object
37    pub enum FieldsWebhook {
38        Avatar,
39    }
40);
41
42#[allow(clippy::derivable_impls)]
43impl Default for Webhook {
44    fn default() -> Self {
45        Self {
46            id: Default::default(),
47            name: Default::default(),
48            avatar: None,
49            creator_id: Default::default(),
50            channel_id: Default::default(),
51            permissions: Default::default(),
52            token: Default::default(),
53        }
54    }
55}
56
57#[allow(clippy::disallowed_methods)]
58impl Webhook {
59    pub async fn create(&self, db: &Database) -> Result<()> {
60        db.insert_webhook(self).await?;
61
62        // Avoid leaking the token to people who receive the event
63        let mut webhook = self.clone();
64        webhook.token = None;
65
66        EventV1::WebhookCreate(webhook.into())
67            .p(self.channel_id.clone())
68            .await;
69
70        Ok(())
71    }
72
73    pub fn assert_token(&self, token: &str) -> Result<()> {
74        if self.token.as_deref() == Some(token) {
75            Ok(())
76        } else {
77            Err(create_error!(NotAuthenticated))
78        }
79    }
80
81    pub async fn update(
82        &mut self,
83        db: &Database,
84        mut partial: PartialWebhook,
85        remove: Vec<FieldsWebhook>,
86    ) -> Result<()> {
87        for field in &remove {
88            self.remove_field(field)
89        }
90
91        self.apply_options(partial.clone());
92
93        db.update_webhook(&self.id, &partial, &remove).await?;
94
95        partial.token = None; // Avoid leaking the token to people who receive the event
96
97        EventV1::WebhookUpdate {
98            id: self.id.clone(),
99            data: partial.into(),
100            remove: remove.into_iter().map(|v| v.into()).collect(),
101        }
102        .p(self.channel_id.clone())
103        .await;
104
105        Ok(())
106    }
107
108    pub fn remove_field(&mut self, field: &FieldsWebhook) {
109        match field {
110            FieldsWebhook::Avatar => self.avatar = None,
111        }
112    }
113
114    pub async fn delete(&self, db: &Database) -> Result<()> {
115        db.delete_webhook(&self.id).await?;
116
117        EventV1::WebhookDelete {
118            id: self.id.clone(),
119        }
120        .p(self.channel_id.clone())
121        .await;
122
123        Ok(())
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use crate::{FieldsWebhook, PartialWebhook, Webhook};
130
131    #[async_std::test]
132    async fn crud() {
133        database_test!(|db| async move {
134            let webhook_id = "webhook";
135            let channel_id = "channel";
136
137            let webhook = Webhook {
138                id: webhook_id.to_string(),
139                name: "Webhook Name".to_string(),
140                channel_id: channel_id.to_string(),
141                avatar: None,
142                ..Default::default()
143            };
144
145            webhook.create(&db).await.unwrap();
146
147            let mut updated_webhook = webhook.clone();
148            updated_webhook
149                .update(
150                    &db,
151                    PartialWebhook {
152                        name: Some("New Name".to_string()),
153                        ..Default::default()
154                    },
155                    vec![FieldsWebhook::Avatar],
156                )
157                .await
158                .unwrap();
159
160            let fetched_webhook = db.fetch_webhook(webhook_id).await.unwrap();
161            let fetched_webhooks = db.fetch_webhooks_for_channel(channel_id).await.unwrap();
162
163            assert_eq!(updated_webhook, fetched_webhook);
164            assert_ne!(webhook, fetched_webhook);
165            assert_eq!(1, fetched_webhooks.len());
166            assert_eq!(fetched_webhook, fetched_webhooks[0]);
167
168            webhook.delete(&db).await.unwrap();
169            assert!(db.fetch_webhook(webhook_id).await.is_err());
170            assert_eq!(
171                0,
172                db.fetch_webhooks_for_channel(channel_id)
173                    .await
174                    .unwrap()
175                    .len()
176            )
177        });
178    }
179}