files_sdk/admin/
history.rs

1//! History operations
2//!
3//! History represents activity logs and audit trails. History queries must be
4//! exported for processing.
5
6use crate::{FilesClient, PaginationInfo, Result};
7use serde::{Deserialize, Serialize};
8use serde_json::json;
9
10/// A History Export entity
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct HistoryExportEntity {
13    /// History Export ID
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub id: Option<i64>,
16
17    /// Version of the history for the export
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub history_version: Option<String>,
20
21    /// Start date/time of export range
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub start_at: Option<String>,
24
25    /// End date/time of export range
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub end_at: Option<String>,
28
29    /// Status (building, ready, failed)
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub status: Option<String>,
32
33    /// Filter by action type
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub query_action: Option<String>,
36
37    /// Filter by interface type
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub query_interface: Option<String>,
40
41    /// Filter by user ID
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub query_user_id: Option<String>,
44
45    /// Filter by file ID
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub query_file_id: Option<String>,
48
49    /// Filter by parent folder ID
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub query_parent_id: Option<String>,
52
53    /// Filter by path pattern
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub query_path: Option<String>,
56
57    /// Filter by folder pattern
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub query_folder: Option<String>,
60
61    /// Filter by source pattern (for moves)
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub query_src: Option<String>,
64
65    /// Filter by destination pattern (for moves)
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub query_destination: Option<String>,
68
69    /// Filter by IP address
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub query_ip: Option<String>,
72
73    /// Filter by username
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub query_username: Option<String>,
76
77    /// Filter by failure type (for login failures)
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub query_failure_type: Option<String>,
80
81    /// Filter by target object ID
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub query_target_id: Option<String>,
84
85    /// Filter by target object name
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub query_target_name: Option<String>,
88
89    /// Filter by target permission level
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub query_target_permission: Option<String>,
92
93    /// Filter by target user ID
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub query_target_user_id: Option<String>,
96
97    /// Filter by target username
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub query_target_username: Option<String>,
100
101    /// Filter by target platform
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub query_target_platform: Option<String>,
104
105    /// Filter by target permission set
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub query_target_permission_set: Option<String>,
108
109    /// Results download URL (when ready)
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub results_url: Option<String>,
112}
113
114/// A History Export Result entity
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct HistoryExportResultEntity {
117    /// ID of the export this result belongs to
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub history_export_id: Option<i64>,
120
121    /// Result data (varies by export type)
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub result: Option<serde_json::Value>,
124}
125
126/// Handler for history operations
127pub struct HistoryHandler {
128    client: FilesClient,
129}
130
131impl HistoryHandler {
132    /// Create a new history handler
133    pub fn new(client: FilesClient) -> Self {
134        Self { client }
135    }
136
137    /// List history for a specific file path
138    ///
139    /// # Arguments
140    /// * `path` - File path
141    /// * `cursor` - Pagination cursor
142    /// * `per_page` - Results per page
143    ///
144    /// # Returns
145    /// Tuple of (history_items, pagination_info)
146    pub async fn list_for_file(
147        &self,
148        path: &str,
149        cursor: Option<&str>,
150        per_page: Option<i64>,
151    ) -> Result<(Vec<serde_json::Value>, PaginationInfo)> {
152        let mut params = vec![];
153        if let Some(c) = cursor {
154            params.push(("cursor", c.to_string()));
155        }
156        if let Some(pp) = per_page {
157            params.push(("per_page", pp.to_string()));
158        }
159
160        let query = if params.is_empty() {
161            String::new()
162        } else {
163            format!(
164                "?{}",
165                params
166                    .iter()
167                    .map(|(k, v)| format!("{}={}", k, v))
168                    .collect::<Vec<_>>()
169                    .join("&")
170            )
171        };
172
173        let response = self
174            .client
175            .get_raw(&format!("/history/files/{}{}", path, query))
176            .await?;
177        let history: Vec<serde_json::Value> = serde_json::from_value(response)?;
178
179        let pagination = PaginationInfo {
180            cursor_next: None,
181            cursor_prev: None,
182        };
183
184        Ok((history, pagination))
185    }
186
187    /// List history for a specific folder path
188    ///
189    /// # Arguments
190    /// * `path` - Folder path
191    /// * `cursor` - Pagination cursor
192    /// * `per_page` - Results per page
193    pub async fn list_for_folder(
194        &self,
195        path: &str,
196        cursor: Option<&str>,
197        per_page: Option<i64>,
198    ) -> Result<(Vec<serde_json::Value>, PaginationInfo)> {
199        let mut params = vec![];
200        if let Some(c) = cursor {
201            params.push(("cursor", c.to_string()));
202        }
203        if let Some(pp) = per_page {
204            params.push(("per_page", pp.to_string()));
205        }
206
207        let query = if params.is_empty() {
208            String::new()
209        } else {
210            format!(
211                "?{}",
212                params
213                    .iter()
214                    .map(|(k, v)| format!("{}={}", k, v))
215                    .collect::<Vec<_>>()
216                    .join("&")
217            )
218        };
219
220        let response = self
221            .client
222            .get_raw(&format!("/history/folders/{}{}", path, query))
223            .await?;
224        let history: Vec<serde_json::Value> = serde_json::from_value(response)?;
225
226        let pagination = PaginationInfo {
227            cursor_next: None,
228            cursor_prev: None,
229        };
230
231        Ok((history, pagination))
232    }
233
234    /// List history for a specific user
235    ///
236    /// # Arguments
237    /// * `user_id` - User ID
238    /// * `cursor` - Pagination cursor
239    /// * `per_page` - Results per page
240    pub async fn list_for_user(
241        &self,
242        user_id: i64,
243        cursor: Option<&str>,
244        per_page: Option<i64>,
245    ) -> Result<(Vec<serde_json::Value>, PaginationInfo)> {
246        let mut params = vec![];
247        if let Some(c) = cursor {
248            params.push(("cursor", c.to_string()));
249        }
250        if let Some(pp) = per_page {
251            params.push(("per_page", pp.to_string()));
252        }
253
254        let query = if params.is_empty() {
255            String::new()
256        } else {
257            format!(
258                "?{}",
259                params
260                    .iter()
261                    .map(|(k, v)| format!("{}={}", k, v))
262                    .collect::<Vec<_>>()
263                    .join("&")
264            )
265        };
266
267        let response = self
268            .client
269            .get_raw(&format!("/history/users/{}{}", user_id, query))
270            .await?;
271        let history: Vec<serde_json::Value> = serde_json::from_value(response)?;
272
273        let pagination = PaginationInfo {
274            cursor_next: None,
275            cursor_prev: None,
276        };
277
278        Ok((history, pagination))
279    }
280
281    /// List login history
282    ///
283    /// # Arguments
284    /// * `cursor` - Pagination cursor
285    /// * `per_page` - Results per page
286    pub async fn list_logins(
287        &self,
288        cursor: Option<&str>,
289        per_page: Option<i64>,
290    ) -> Result<(Vec<serde_json::Value>, PaginationInfo)> {
291        let mut params = vec![];
292        if let Some(c) = cursor {
293            params.push(("cursor", c.to_string()));
294        }
295        if let Some(pp) = per_page {
296            params.push(("per_page", pp.to_string()));
297        }
298
299        let query = if params.is_empty() {
300            String::new()
301        } else {
302            format!(
303                "?{}",
304                params
305                    .iter()
306                    .map(|(k, v)| format!("{}={}", k, v))
307                    .collect::<Vec<_>>()
308                    .join("&")
309            )
310        };
311
312        let response = self
313            .client
314            .get_raw(&format!("/history/login{}", query))
315            .await?;
316        let history: Vec<serde_json::Value> = serde_json::from_value(response)?;
317
318        let pagination = PaginationInfo {
319            cursor_next: None,
320            cursor_prev: None,
321        };
322
323        Ok((history, pagination))
324    }
325
326    /// Create a history export
327    ///
328    /// # Arguments
329    /// * `start_at` - Start date/time
330    /// * `end_at` - End date/time
331    /// * `query_action` - Filter by action
332    /// * `query_user_id` - Filter by user ID
333    /// * `query_folder` - Filter by folder
334    ///
335    /// # Returns
336    /// The created history export
337    #[allow(clippy::too_many_arguments)]
338    pub async fn create_export(
339        &self,
340        start_at: Option<&str>,
341        end_at: Option<&str>,
342        query_action: Option<&str>,
343        query_user_id: Option<&str>,
344        query_folder: Option<&str>,
345    ) -> Result<HistoryExportEntity> {
346        let mut request_body = json!({});
347
348        if let Some(start) = start_at {
349            request_body["start_at"] = json!(start);
350        }
351        if let Some(end) = end_at {
352            request_body["end_at"] = json!(end);
353        }
354        if let Some(action) = query_action {
355            request_body["query_action"] = json!(action);
356        }
357        if let Some(user) = query_user_id {
358            request_body["query_user_id"] = json!(user);
359        }
360        if let Some(folder) = query_folder {
361            request_body["query_folder"] = json!(folder);
362        }
363
364        let response = self
365            .client
366            .post_raw("/history_exports", request_body)
367            .await?;
368        Ok(serde_json::from_value(response)?)
369    }
370
371    /// Get a history export by ID
372    ///
373    /// # Arguments
374    /// * `id` - History export ID
375    pub async fn get_export(&self, id: i64) -> Result<HistoryExportEntity> {
376        let response = self
377            .client
378            .get_raw(&format!("/history_exports/{}", id))
379            .await?;
380        Ok(serde_json::from_value(response)?)
381    }
382
383    /// Get history export results
384    ///
385    /// # Arguments
386    /// * `cursor` - Pagination cursor
387    /// * `per_page` - Results per page
388    /// * `history_export_id` - Filter by export ID
389    pub async fn get_export_results(
390        &self,
391        cursor: Option<&str>,
392        per_page: Option<i64>,
393        history_export_id: Option<i64>,
394    ) -> Result<(Vec<HistoryExportResultEntity>, PaginationInfo)> {
395        let mut params = vec![];
396        if let Some(c) = cursor {
397            params.push(("cursor", c.to_string()));
398        }
399        if let Some(pp) = per_page {
400            params.push(("per_page", pp.to_string()));
401        }
402        if let Some(export_id) = history_export_id {
403            params.push(("history_export_id", export_id.to_string()));
404        }
405
406        let query = if params.is_empty() {
407            String::new()
408        } else {
409            format!(
410                "?{}",
411                params
412                    .iter()
413                    .map(|(k, v)| format!("{}={}", k, v))
414                    .collect::<Vec<_>>()
415                    .join("&")
416            )
417        };
418
419        let response = self
420            .client
421            .get_raw(&format!("/history_export_results{}", query))
422            .await?;
423        let results: Vec<HistoryExportResultEntity> = serde_json::from_value(response)?;
424
425        let pagination = PaginationInfo {
426            cursor_next: None,
427            cursor_prev: None,
428        };
429
430        Ok((results, pagination))
431    }
432}
433
434#[cfg(test)]
435mod tests {
436    use super::*;
437
438    #[test]
439    fn test_handler_creation() {
440        let client = FilesClient::builder().api_key("test-key").build().unwrap();
441        let _handler = HistoryHandler::new(client);
442    }
443}