files_sdk/messages/
notifications.rs

1//! Notification operations
2//!
3//! Notifications send emails when specific actions occur in folders.
4//! Emails are sent in batches at configured intervals (5 min, 15 min, hourly, daily).
5
6use crate::{FilesClient, PaginationInfo, Result};
7use serde::{Deserialize, Serialize};
8use serde_json::json;
9
10/// Notification send interval enum
11#[derive(Debug, Clone, Serialize, Deserialize)]
12#[serde(rename_all = "snake_case")]
13pub enum SendInterval {
14    /// Every 5 minutes
15    FiveMinutes,
16    /// Every 15 minutes
17    FifteenMinutes,
18    /// Hourly
19    Hourly,
20    /// Daily
21    Daily,
22}
23
24/// Unsubscribe reason enum
25#[derive(Debug, Clone, Serialize, Deserialize)]
26#[serde(rename_all = "snake_case")]
27pub enum UnsubscribedReason {
28    /// Not unsubscribed
29    None,
30    /// User clicked unsubscribe link
31    UnsubscribeLinkClicked,
32    /// Mail bounced
33    MailBounced,
34    /// Mail marked as spam
35    MailMarkedAsSpam,
36}
37
38/// A Notification entity
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct NotificationEntity {
41    /// Notification ID
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub id: Option<i64>,
44
45    /// Folder path to notify on
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub path: Option<String>,
48
49    /// Group ID to receive notifications
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub group_id: Option<i64>,
52
53    /// Group name
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub group_name: Option<String>,
56
57    /// Only notify on actions by these groups
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub triggering_group_ids: Option<Vec<i64>>,
60
61    /// Only notify on actions by these users
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub triggering_user_ids: Option<Vec<i64>>,
64
65    /// Notify on share recipient actions?
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub trigger_by_share_recipients: Option<bool>,
68
69    /// Send notifications about user's own activity?
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub notify_user_actions: Option<bool>,
72
73    /// Trigger on file copy?
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub notify_on_copy: Option<bool>,
76
77    /// Trigger on file delete?
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub notify_on_delete: Option<bool>,
80
81    /// Trigger on file download?
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub notify_on_download: Option<bool>,
84
85    /// Trigger on file move?
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub notify_on_move: Option<bool>,
88
89    /// Trigger on file upload/update?
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub notify_on_upload: Option<bool>,
92
93    /// Apply recursively to subfolders?
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub recursive: Option<bool>,
96
97    /// Email send interval
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub send_interval: Option<String>,
100
101    /// Custom message in notification emails
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub message: Option<String>,
104
105    /// Filenames to trigger on (with wildcards)
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub triggering_filenames: Option<Vec<String>>,
108
109    /// Is user unsubscribed?
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub unsubscribed: Option<bool>,
112
113    /// Unsubscribe reason
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub unsubscribed_reason: Option<String>,
116
117    /// User ID
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub user_id: Option<i64>,
120
121    /// Username
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub username: Option<String>,
124
125    /// Email suppressed due to bounce/spam?
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub suppressed_email: Option<bool>,
128}
129
130/// Handler for notification operations
131pub struct NotificationHandler {
132    client: FilesClient,
133}
134
135impl NotificationHandler {
136    /// Create a new notification handler
137    pub fn new(client: FilesClient) -> Self {
138        Self { client }
139    }
140
141    /// List notifications
142    ///
143    /// # Arguments
144    /// * `cursor` - Pagination cursor
145    /// * `per_page` - Results per page
146    /// * `path` - Filter by path
147    /// * `group_id` - Filter by group ID
148    ///
149    /// # Returns
150    /// Tuple of (notifications, pagination_info)
151    ///
152    /// # Example
153    /// ```no_run
154    /// use files_sdk::{FilesClient, NotificationHandler};
155    ///
156    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
157    /// let client = FilesClient::builder().api_key("key").build()?;
158    /// let handler = NotificationHandler::new(client);
159    /// let (notifications, _) = handler.list(None, None, None, None).await?;
160    /// # Ok(())
161    /// # }
162    /// ```
163    pub async fn list(
164        &self,
165        cursor: Option<&str>,
166        per_page: Option<i64>,
167        path: Option<&str>,
168        group_id: Option<i64>,
169    ) -> Result<(Vec<NotificationEntity>, PaginationInfo)> {
170        let mut params = vec![];
171        if let Some(c) = cursor {
172            params.push(("cursor", c.to_string()));
173        }
174        if let Some(pp) = per_page {
175            params.push(("per_page", pp.to_string()));
176        }
177        if let Some(p) = path {
178            params.push(("path", p.to_string()));
179        }
180        if let Some(gid) = group_id {
181            params.push(("group_id", gid.to_string()));
182        }
183
184        let query = if params.is_empty() {
185            String::new()
186        } else {
187            format!(
188                "?{}",
189                params
190                    .iter()
191                    .map(|(k, v)| format!("{}={}", k, v))
192                    .collect::<Vec<_>>()
193                    .join("&")
194            )
195        };
196
197        let response = self
198            .client
199            .get_raw(&format!("/notifications{}", query))
200            .await?;
201        let notifications: Vec<NotificationEntity> = serde_json::from_value(response)?;
202
203        let pagination = PaginationInfo {
204            cursor_next: None,
205            cursor_prev: None,
206        };
207
208        Ok((notifications, pagination))
209    }
210
211    /// Get a specific notification
212    ///
213    /// # Arguments
214    /// * `id` - Notification ID
215    ///
216    /// # Returns
217    /// The notification entity
218    pub async fn get(&self, id: i64) -> Result<NotificationEntity> {
219        let response = self
220            .client
221            .get_raw(&format!("/notifications/{}", id))
222            .await?;
223        Ok(serde_json::from_value(response)?)
224    }
225
226    /// Create a new notification
227    ///
228    /// # Arguments
229    /// * `path` - Folder path to monitor
230    /// * `group_id` - Group to notify
231    /// * `notify_on_upload` - Trigger on uploads
232    /// * `notify_on_download` - Trigger on downloads
233    /// * `notify_on_delete` - Trigger on deletes
234    /// * `send_interval` - Email frequency
235    /// * `recursive` - Apply to subfolders
236    ///
237    /// # Returns
238    /// The created notification
239    #[allow(clippy::too_many_arguments)]
240    pub async fn create(
241        &self,
242        path: Option<&str>,
243        group_id: Option<i64>,
244        notify_on_upload: Option<bool>,
245        notify_on_download: Option<bool>,
246        notify_on_delete: Option<bool>,
247        send_interval: Option<&str>,
248        recursive: Option<bool>,
249        message: Option<&str>,
250    ) -> Result<NotificationEntity> {
251        let mut body = json!({});
252
253        if let Some(p) = path {
254            body["path"] = json!(p);
255        }
256        if let Some(gid) = group_id {
257            body["group_id"] = json!(gid);
258        }
259        if let Some(u) = notify_on_upload {
260            body["notify_on_upload"] = json!(u);
261        }
262        if let Some(d) = notify_on_download {
263            body["notify_on_download"] = json!(d);
264        }
265        if let Some(del) = notify_on_delete {
266            body["notify_on_delete"] = json!(del);
267        }
268        if let Some(si) = send_interval {
269            body["send_interval"] = json!(si);
270        }
271        if let Some(r) = recursive {
272            body["recursive"] = json!(r);
273        }
274        if let Some(m) = message {
275            body["message"] = json!(m);
276        }
277
278        let response = self.client.post_raw("/notifications", body).await?;
279        Ok(serde_json::from_value(response)?)
280    }
281
282    /// Update a notification
283    ///
284    /// # Arguments
285    /// * `id` - Notification ID
286    /// * `notify_on_upload` - Trigger on uploads
287    /// * `notify_on_download` - Trigger on downloads
288    /// * `notify_on_delete` - Trigger on deletes
289    /// * `send_interval` - Email frequency
290    ///
291    /// # Returns
292    /// The updated notification
293    pub async fn update(
294        &self,
295        id: i64,
296        notify_on_upload: Option<bool>,
297        notify_on_download: Option<bool>,
298        notify_on_delete: Option<bool>,
299        send_interval: Option<&str>,
300    ) -> Result<NotificationEntity> {
301        let mut body = json!({});
302
303        if let Some(u) = notify_on_upload {
304            body["notify_on_upload"] = json!(u);
305        }
306        if let Some(d) = notify_on_download {
307            body["notify_on_download"] = json!(d);
308        }
309        if let Some(del) = notify_on_delete {
310            body["notify_on_delete"] = json!(del);
311        }
312        if let Some(si) = send_interval {
313            body["send_interval"] = json!(si);
314        }
315
316        let response = self
317            .client
318            .patch_raw(&format!("/notifications/{}", id), body)
319            .await?;
320        Ok(serde_json::from_value(response)?)
321    }
322
323    /// Delete a notification
324    ///
325    /// # Arguments
326    /// * `id` - Notification ID
327    pub async fn delete(&self, id: i64) -> Result<()> {
328        self.client
329            .delete_raw(&format!("/notifications/{}", id))
330            .await?;
331        Ok(())
332    }
333}
334
335#[cfg(test)]
336mod tests {
337    use super::*;
338
339    #[test]
340    fn test_handler_creation() {
341        let client = FilesClient::builder().api_key("test-key").build().unwrap();
342        let _handler = NotificationHandler::new(client);
343    }
344}