files_sdk/users/
users.rs

1//! User management operations
2//!
3//! This module provides user management functionality including:
4//! - List users with filtering and pagination
5//! - Create new users
6//! - Update user settings
7//! - Delete users
8//! - User utility operations (unlock, reset 2FA, etc.)
9
10use crate::{FilesClient, PaginationInfo, Result};
11use serde::{Deserialize, Serialize};
12use serde_json::json;
13
14/// User entity from Files.com API
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct UserEntity {
17    /// User ID
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub id: Option<i64>,
20
21    /// Username
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub username: Option<String>,
24
25    /// User's email address
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub email: Option<String>,
28
29    /// User's full name
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub name: Option<String>,
32
33    /// Company name
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub company: Option<String>,
36
37    /// Notes about the user
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub notes: Option<String>,
40
41    /// User home directory
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub user_home: Option<String>,
44
45    /// User root directory
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub user_root: Option<String>,
48
49    /// Is site admin
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub site_admin: Option<bool>,
52
53    /// Is read-only site admin
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub readonly_site_admin: Option<bool>,
56
57    /// User is disabled
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub disabled: Option<bool>,
60
61    /// User is disabled, expired, or inactive
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub disabled_expired_or_inactive: Option<bool>,
64
65    /// SSL/TLS is required for this user
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub ssl_required: Option<String>,
68
69    /// Time zone
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub time_zone: Option<String>,
72
73    /// Language preference
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub language: Option<String>,
76
77    /// Allowed IP addresses
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub allowed_ips: Option<String>,
80
81    /// Bypass site allowed IPs
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub bypass_site_allowed_ips: Option<bool>,
84
85    /// Group IDs this user belongs to (can be string or array)
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub group_ids: Option<String>,
88
89    /// Admin group IDs
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub admin_group_ids: Option<Vec<i64>>,
92
93    /// FTP permission
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub ftp_permission: Option<bool>,
96
97    /// SFTP permission
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub sftp_permission: Option<bool>,
100
101    /// WebDAV permission
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub dav_permission: Option<bool>,
104
105    /// REST API permission
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub restapi_permission: Option<bool>,
108
109    /// Require 2FA
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub require_2fa: Option<String>,
112
113    /// Active 2FA method
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub active_2fa: Option<bool>,
116
117    /// Created at timestamp
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub created_at: Option<String>,
120
121    /// Last login at timestamp
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub last_login_at: Option<String>,
124
125    /// Password set at timestamp
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub password_set_at: Option<String>,
128
129    /// Password validity in days
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub password_validity_days: Option<i64>,
132
133    /// API keys count
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub api_keys_count: Option<i64>,
136
137    /// Public keys count
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub public_keys_count: Option<i64>,
140}
141
142/// Handler for user operations
143#[derive(Debug, Clone)]
144pub struct UserHandler {
145    client: FilesClient,
146}
147
148impl UserHandler {
149    /// Creates a new UserHandler
150    pub fn new(client: FilesClient) -> Self {
151        Self { client }
152    }
153
154    /// List users
155    ///
156    /// # Arguments
157    ///
158    /// * `cursor` - Pagination cursor (optional)
159    /// * `per_page` - Results per page (optional, default 100)
160    ///
161    /// # Examples
162    ///
163    /// ```rust,no_run
164    /// # use files_sdk::{FilesClient, UserHandler};
165    /// # #[tokio::main]
166    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
167    /// # let client = FilesClient::builder().api_key("key").build()?;
168    /// let handler = UserHandler::new(client);
169    /// let (users, pagination) = handler.list(None, None).await?;
170    ///
171    /// for user in users {
172    ///     println!("User: {:?}", user.username);
173    /// }
174    /// # Ok(())
175    /// # }
176    /// ```
177    pub async fn list(
178        &self,
179        cursor: Option<String>,
180        per_page: Option<i32>,
181    ) -> Result<(Vec<UserEntity>, PaginationInfo)> {
182        let mut path = "/users?".to_string();
183
184        if let Some(c) = cursor {
185            path.push_str(&format!("cursor={}&", c));
186        }
187        if let Some(pp) = per_page {
188            path.push_str(&format!("per_page={}&", pp));
189        }
190
191        let response = self.client.get_raw(&path).await?;
192        let users: Vec<UserEntity> = serde_json::from_value(response)?;
193
194        // TODO: Extract pagination info from response headers
195        let pagination = PaginationInfo {
196            cursor_next: None,
197            cursor_prev: None,
198        };
199
200        Ok((users, pagination))
201    }
202
203    /// Get a specific user by ID
204    ///
205    /// # Arguments
206    ///
207    /// * `id` - User ID
208    pub async fn get(&self, id: i64) -> Result<UserEntity> {
209        let path = format!("/users/{}", id);
210        let response = self.client.get_raw(&path).await?;
211        Ok(serde_json::from_value(response)?)
212    }
213
214    /// Create a new user
215    ///
216    /// # Arguments
217    ///
218    /// * `username` - Username (required)
219    /// * `email` - Email address (optional)
220    /// * `password` - Password (optional)
221    /// * `name` - Full name (optional)
222    ///
223    /// # Examples
224    ///
225    /// ```rust,no_run
226    /// # use files_sdk::{FilesClient, UserHandler};
227    /// # #[tokio::main]
228    /// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
229    /// # let client = FilesClient::builder().api_key("key").build()?;
230    /// let handler = UserHandler::new(client);
231    /// let user = handler.create(
232    ///     "newuser",
233    ///     Some("user@example.com"),
234    ///     Some("password123"),
235    ///     Some("New User")
236    /// ).await?;
237    /// # Ok(())
238    /// # }
239    /// ```
240    pub async fn create(
241        &self,
242        username: &str,
243        email: Option<&str>,
244        password: Option<&str>,
245        name: Option<&str>,
246    ) -> Result<UserEntity> {
247        let mut body = json!({
248            "username": username,
249        });
250
251        if let Some(e) = email {
252            body["email"] = json!(e);
253        }
254        if let Some(p) = password {
255            body["password"] = json!(p);
256        }
257        if let Some(n) = name {
258            body["name"] = json!(n);
259        }
260
261        let response = self.client.post_raw("/users", body).await?;
262        Ok(serde_json::from_value(response)?)
263    }
264
265    /// Update a user
266    ///
267    /// # Arguments
268    ///
269    /// * `id` - User ID
270    /// * `email` - New email (optional)
271    /// * `name` - New name (optional)
272    /// * `company` - New company (optional)
273    /// * `notes` - New notes (optional)
274    pub async fn update(
275        &self,
276        id: i64,
277        email: Option<&str>,
278        name: Option<&str>,
279        company: Option<&str>,
280        notes: Option<&str>,
281    ) -> Result<UserEntity> {
282        let mut body = json!({});
283
284        if let Some(e) = email {
285            body["email"] = json!(e);
286        }
287        if let Some(n) = name {
288            body["name"] = json!(n);
289        }
290        if let Some(c) = company {
291            body["company"] = json!(c);
292        }
293        if let Some(nt) = notes {
294            body["notes"] = json!(nt);
295        }
296
297        let path = format!("/users/{}", id);
298        let response = self.client.patch_raw(&path, body).await?;
299        Ok(serde_json::from_value(response)?)
300    }
301
302    /// Delete a user
303    ///
304    /// # Arguments
305    ///
306    /// * `id` - User ID
307    pub async fn delete(&self, id: i64) -> Result<()> {
308        let path = format!("/users/{}", id);
309        self.client.delete_raw(&path).await?;
310        Ok(())
311    }
312
313    /// Unlock a locked user
314    ///
315    /// # Arguments
316    ///
317    /// * `id` - User ID
318    pub async fn unlock(&self, id: i64) -> Result<()> {
319        let path = format!("/users/{}/unlock", id);
320        self.client.post_raw(&path, json!({})).await?;
321        Ok(())
322    }
323
324    /// Reset 2FA for a user
325    ///
326    /// # Arguments
327    ///
328    /// * `id` - User ID
329    pub async fn reset_2fa(&self, id: i64) -> Result<()> {
330        let path = format!("/users/{}/2fa/reset", id);
331        self.client.post_raw(&path, json!({})).await?;
332        Ok(())
333    }
334
335    /// Resend welcome email to a user
336    ///
337    /// # Arguments
338    ///
339    /// * `id` - User ID
340    pub async fn resend_welcome_email(&self, id: i64) -> Result<()> {
341        let path = format!("/users/{}/resend_welcome_email", id);
342        self.client.post_raw(&path, json!({})).await?;
343        Ok(())
344    }
345}
346
347#[cfg(test)]
348mod tests {
349    use super::*;
350
351    #[test]
352    fn test_handler_creation() {
353        let client = FilesClient::builder().api_key("test-key").build().unwrap();
354        let _handler = UserHandler::new(client);
355    }
356}