files_sdk/users/users.rs
1//! User management operations
2//!
3//! Provides comprehensive user account management for Files.com sites, including
4//! creation, modification, and administrative operations.
5//!
6//! # Features
7//!
8//! - Create and manage user accounts
9//! - Configure permissions and access controls
10//! - Set user quotas and restrictions
11//! - Administrative operations (unlock, reset 2FA)
12//! - Group membership management
13//! - Protocol-specific permissions (FTP, SFTP, WebDAV, REST API)
14//!
15//! # Example
16//!
17//! ```no_run
18//! use files_sdk::{FilesClient, UserHandler};
19//!
20//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
21//! let client = FilesClient::builder()
22//! .api_key("your-api-key")
23//! .build()?;
24//!
25//! let handler = UserHandler::new(client);
26//!
27//! // Create a new user with SFTP access
28//! let user = handler.create(
29//! "newuser",
30//! Some("user@company.com"),
31//! Some("secure-password"),
32//! Some("New User")
33//! ).await?;
34//!
35//! println!("Created user ID: {}", user.id.unwrap());
36//!
37//! // List all users
38//! let (users, _) = handler.list(None, Some(100)).await?;
39//! for user in users {
40//! println!("User: {} ({})",
41//! user.username.unwrap_or_default(),
42//! user.email.unwrap_or_default());
43//! }
44//! # Ok(())
45//! # }
46//! ```
47
48use crate::{FilesClient, PaginationInfo, Result};
49use serde::{Deserialize, Serialize};
50use serde_json::json;
51
52/// User entity from Files.com API
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct UserEntity {
55 /// User ID
56 #[serde(skip_serializing_if = "Option::is_none")]
57 pub id: Option<i64>,
58
59 /// Username
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub username: Option<String>,
62
63 /// User's email address
64 #[serde(skip_serializing_if = "Option::is_none")]
65 pub email: Option<String>,
66
67 /// User's full name
68 #[serde(skip_serializing_if = "Option::is_none")]
69 pub name: Option<String>,
70
71 /// Company name
72 #[serde(skip_serializing_if = "Option::is_none")]
73 pub company: Option<String>,
74
75 /// Notes about the user
76 #[serde(skip_serializing_if = "Option::is_none")]
77 pub notes: Option<String>,
78
79 /// User home directory
80 #[serde(skip_serializing_if = "Option::is_none")]
81 pub user_home: Option<String>,
82
83 /// User root directory
84 #[serde(skip_serializing_if = "Option::is_none")]
85 pub user_root: Option<String>,
86
87 /// Is site admin
88 #[serde(skip_serializing_if = "Option::is_none")]
89 pub site_admin: Option<bool>,
90
91 /// Is read-only site admin
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub readonly_site_admin: Option<bool>,
94
95 /// User is disabled
96 #[serde(skip_serializing_if = "Option::is_none")]
97 pub disabled: Option<bool>,
98
99 /// User is disabled, expired, or inactive
100 #[serde(skip_serializing_if = "Option::is_none")]
101 pub disabled_expired_or_inactive: Option<bool>,
102
103 /// SSL/TLS is required for this user
104 #[serde(skip_serializing_if = "Option::is_none")]
105 pub ssl_required: Option<String>,
106
107 /// Time zone
108 #[serde(skip_serializing_if = "Option::is_none")]
109 pub time_zone: Option<String>,
110
111 /// Language preference
112 #[serde(skip_serializing_if = "Option::is_none")]
113 pub language: Option<String>,
114
115 /// Allowed IP addresses
116 #[serde(skip_serializing_if = "Option::is_none")]
117 pub allowed_ips: Option<String>,
118
119 /// Bypass site allowed IPs
120 #[serde(skip_serializing_if = "Option::is_none")]
121 pub bypass_site_allowed_ips: Option<bool>,
122
123 /// Group IDs this user belongs to (can be string or array)
124 #[serde(skip_serializing_if = "Option::is_none")]
125 pub group_ids: Option<String>,
126
127 /// Admin group IDs
128 #[serde(skip_serializing_if = "Option::is_none")]
129 pub admin_group_ids: Option<Vec<i64>>,
130
131 /// FTP permission
132 #[serde(skip_serializing_if = "Option::is_none")]
133 pub ftp_permission: Option<bool>,
134
135 /// SFTP permission
136 #[serde(skip_serializing_if = "Option::is_none")]
137 pub sftp_permission: Option<bool>,
138
139 /// WebDAV permission
140 #[serde(skip_serializing_if = "Option::is_none")]
141 pub dav_permission: Option<bool>,
142
143 /// REST API permission
144 #[serde(skip_serializing_if = "Option::is_none")]
145 pub restapi_permission: Option<bool>,
146
147 /// Require 2FA
148 #[serde(skip_serializing_if = "Option::is_none")]
149 pub require_2fa: Option<String>,
150
151 /// Active 2FA method
152 #[serde(skip_serializing_if = "Option::is_none")]
153 pub active_2fa: Option<bool>,
154
155 /// Created at timestamp
156 #[serde(skip_serializing_if = "Option::is_none")]
157 pub created_at: Option<String>,
158
159 /// Last login at timestamp
160 #[serde(skip_serializing_if = "Option::is_none")]
161 pub last_login_at: Option<String>,
162
163 /// Password set at timestamp
164 #[serde(skip_serializing_if = "Option::is_none")]
165 pub password_set_at: Option<String>,
166
167 /// Password validity in days
168 #[serde(skip_serializing_if = "Option::is_none")]
169 pub password_validity_days: Option<i64>,
170
171 /// API keys count
172 #[serde(skip_serializing_if = "Option::is_none")]
173 pub api_keys_count: Option<i64>,
174
175 /// Public keys count
176 #[serde(skip_serializing_if = "Option::is_none")]
177 pub public_keys_count: Option<i64>,
178}
179
180/// Handler for user operations
181#[derive(Debug, Clone)]
182pub struct UserHandler {
183 client: FilesClient,
184}
185
186impl UserHandler {
187 /// Creates a new UserHandler
188 pub fn new(client: FilesClient) -> Self {
189 Self { client }
190 }
191
192 /// List all users on the site
193 ///
194 /// Returns a paginated list of user accounts with their details and permissions.
195 ///
196 /// # Arguments
197 ///
198 /// * `cursor` - Pagination cursor from previous response
199 /// * `per_page` - Number of results per page (default 100, max 10,000)
200 ///
201 /// # Returns
202 ///
203 /// A tuple containing:
204 /// - Vector of `UserEntity` objects
205 /// - `PaginationInfo` with cursors for next/previous pages
206 ///
207 /// # Example
208 ///
209 /// ```no_run
210 /// use files_sdk::{FilesClient, UserHandler};
211 ///
212 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
213 /// let client = FilesClient::builder().api_key("key").build()?;
214 /// let handler = UserHandler::new(client);
215 ///
216 /// // List first page of users
217 /// let (users, pagination) = handler.list(None, Some(50)).await?;
218 ///
219 /// for user in users {
220 /// println!("{}: {} - Admin: {}",
221 /// user.username.unwrap_or_default(),
222 /// user.email.unwrap_or_default(),
223 /// user.site_admin.unwrap_or(false));
224 /// }
225 ///
226 /// // Get next page if available
227 /// if let Some(next) = pagination.cursor_next {
228 /// let (more_users, _) = handler.list(Some(next), Some(50)).await?;
229 /// }
230 /// # Ok(())
231 /// # }
232 /// ```
233 pub async fn list(
234 &self,
235 cursor: Option<String>,
236 per_page: Option<i32>,
237 ) -> Result<(Vec<UserEntity>, PaginationInfo)> {
238 let mut path = "/users?".to_string();
239
240 if let Some(c) = cursor {
241 path.push_str(&format!("cursor={}&", c));
242 }
243 if let Some(pp) = per_page {
244 path.push_str(&format!("per_page={}&", pp));
245 }
246
247 let response = self.client.get_raw(&path).await?;
248 let users: Vec<UserEntity> = serde_json::from_value(response)?;
249
250 // TODO: Extract pagination info from response headers
251 let pagination = PaginationInfo {
252 cursor_next: None,
253 cursor_prev: None,
254 };
255
256 Ok((users, pagination))
257 }
258
259 /// Get details of a specific user by ID
260 ///
261 /// # Arguments
262 ///
263 /// * `id` - The unique user ID
264 ///
265 /// # Returns
266 ///
267 /// A `UserEntity` with complete user information
268 ///
269 /// # Example
270 ///
271 /// ```no_run
272 /// use files_sdk::{FilesClient, UserHandler};
273 ///
274 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
275 /// let client = FilesClient::builder().api_key("key").build()?;
276 /// let handler = UserHandler::new(client);
277 ///
278 /// let user = handler.get(12345).await?;
279 /// println!("User: {}", user.username.unwrap_or_default());
280 /// println!("Last login: {}", user.last_login_at.unwrap_or_default());
281 /// # Ok(())
282 /// # }
283 /// ```
284 pub async fn get(&self, id: i64) -> Result<UserEntity> {
285 let path = format!("/users/{}", id);
286 let response = self.client.get_raw(&path).await?;
287 Ok(serde_json::from_value(response)?)
288 }
289
290 /// Create a new user account
291 ///
292 /// Creates a new user with the specified credentials. The user will receive
293 /// a welcome email if email is provided.
294 ///
295 /// # Arguments
296 ///
297 /// * `username` - Unique username for login (required)
298 /// * `email` - User's email address
299 /// * `password` - Initial password (if not provided, user must set on first login)
300 /// * `name` - User's full name
301 ///
302 /// # Returns
303 ///
304 /// The newly created `UserEntity`
305 ///
306 /// # Example
307 ///
308 /// ```no_run
309 /// use files_sdk::{FilesClient, UserHandler};
310 ///
311 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
312 /// let client = FilesClient::builder().api_key("key").build()?;
313 /// let handler = UserHandler::new(client);
314 ///
315 /// // Create user with all details
316 /// let user = handler.create(
317 /// "jdoe",
318 /// Some("jdoe@company.com"),
319 /// Some("SecureP@ssw0rd"),
320 /// Some("John Doe")
321 /// ).await?;
322 ///
323 /// println!("Created user: {} (ID: {})",
324 /// user.username.unwrap(),
325 /// user.id.unwrap());
326 /// # Ok(())
327 /// # }
328 /// ```
329 pub async fn create(
330 &self,
331 username: &str,
332 email: Option<&str>,
333 password: Option<&str>,
334 name: Option<&str>,
335 ) -> Result<UserEntity> {
336 let mut body = json!({
337 "username": username,
338 });
339
340 if let Some(e) = email {
341 body["email"] = json!(e);
342 }
343 if let Some(p) = password {
344 body["password"] = json!(p);
345 }
346 if let Some(n) = name {
347 body["name"] = json!(n);
348 }
349
350 let response = self.client.post_raw("/users", body).await?;
351 Ok(serde_json::from_value(response)?)
352 }
353
354 /// Update user information
355 ///
356 /// Updates user profile information. Only provided fields are updated;
357 /// omitted fields remain unchanged.
358 ///
359 /// # Arguments
360 ///
361 /// * `id` - User ID to update
362 /// * `email` - New email address
363 /// * `name` - New full name
364 /// * `company` - New company name
365 /// * `notes` - Administrative notes about the user
366 ///
367 /// # Returns
368 ///
369 /// The updated `UserEntity`
370 ///
371 /// # Example
372 ///
373 /// ```no_run
374 /// use files_sdk::{FilesClient, UserHandler};
375 ///
376 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
377 /// let client = FilesClient::builder().api_key("key").build()?;
378 /// let handler = UserHandler::new(client);
379 ///
380 /// // Update user's company and notes
381 /// let user = handler.update(
382 /// 12345,
383 /// None,
384 /// None,
385 /// Some("New Company Inc."),
386 /// Some("Transferred from old company")
387 /// ).await?;
388 /// # Ok(())
389 /// # }
390 /// ```
391 pub async fn update(
392 &self,
393 id: i64,
394 email: Option<&str>,
395 name: Option<&str>,
396 company: Option<&str>,
397 notes: Option<&str>,
398 ) -> Result<UserEntity> {
399 let mut body = json!({});
400
401 if let Some(e) = email {
402 body["email"] = json!(e);
403 }
404 if let Some(n) = name {
405 body["name"] = json!(n);
406 }
407 if let Some(c) = company {
408 body["company"] = json!(c);
409 }
410 if let Some(nt) = notes {
411 body["notes"] = json!(nt);
412 }
413
414 let path = format!("/users/{}", id);
415 let response = self.client.patch_raw(&path, body).await?;
416 Ok(serde_json::from_value(response)?)
417 }
418
419 /// Delete a user account permanently
420 ///
421 /// Removes the user account and all associated data. This operation cannot
422 /// be undone.
423 ///
424 /// # Arguments
425 ///
426 /// * `id` - User ID to delete
427 ///
428 /// # Example
429 ///
430 /// ```no_run
431 /// use files_sdk::{FilesClient, UserHandler};
432 ///
433 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
434 /// let client = FilesClient::builder().api_key("key").build()?;
435 /// let handler = UserHandler::new(client);
436 ///
437 /// handler.delete(12345).await?;
438 /// println!("User deleted successfully");
439 /// # Ok(())
440 /// # }
441 /// ```
442 pub async fn delete(&self, id: i64) -> Result<()> {
443 let path = format!("/users/{}", id);
444 self.client.delete_raw(&path).await?;
445 Ok(())
446 }
447
448 /// Unlock a locked user account
449 ///
450 /// Removes the lock from a user account that has been locked due to too many
451 /// failed login attempts or administrative action.
452 ///
453 /// # Arguments
454 ///
455 /// * `id` - User ID to unlock
456 ///
457 /// # Example
458 ///
459 /// ```no_run
460 /// use files_sdk::{FilesClient, UserHandler};
461 ///
462 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
463 /// let client = FilesClient::builder().api_key("key").build()?;
464 /// let handler = UserHandler::new(client);
465 ///
466 /// // Unlock user after failed login attempts
467 /// handler.unlock(12345).await?;
468 /// println!("User unlocked successfully");
469 /// # Ok(())
470 /// # }
471 /// ```
472 pub async fn unlock(&self, id: i64) -> Result<()> {
473 let path = format!("/users/{}/unlock", id);
474 self.client.post_raw(&path, json!({})).await?;
475 Ok(())
476 }
477
478 /// Reset two-factor authentication for a user
479 ///
480 /// Disables 2FA for the user, requiring them to set it up again on next login.
481 /// Use this when a user has lost access to their 2FA device.
482 ///
483 /// # Arguments
484 ///
485 /// * `id` - User ID
486 ///
487 /// # Example
488 ///
489 /// ```no_run
490 /// use files_sdk::{FilesClient, UserHandler};
491 ///
492 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
493 /// let client = FilesClient::builder().api_key("key").build()?;
494 /// let handler = UserHandler::new(client);
495 ///
496 /// // Reset 2FA for user who lost their device
497 /// handler.reset_2fa(12345).await?;
498 /// println!("2FA reset - user must configure on next login");
499 /// # Ok(())
500 /// # }
501 /// ```
502 pub async fn reset_2fa(&self, id: i64) -> Result<()> {
503 let path = format!("/users/{}/2fa/reset", id);
504 self.client.post_raw(&path, json!({})).await?;
505 Ok(())
506 }
507
508 /// Resend welcome email to a user
509 ///
510 /// Sends a new welcome email to the user with login instructions and
511 /// password setup link if needed.
512 ///
513 /// # Arguments
514 ///
515 /// * `id` - User ID
516 ///
517 /// # Example
518 ///
519 /// ```no_run
520 /// use files_sdk::{FilesClient, UserHandler};
521 ///
522 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
523 /// let client = FilesClient::builder().api_key("key").build()?;
524 /// let handler = UserHandler::new(client);
525 ///
526 /// // Resend welcome email to new user
527 /// handler.resend_welcome_email(12345).await?;
528 /// println!("Welcome email sent");
529 /// # Ok(())
530 /// # }
531 /// ```
532 pub async fn resend_welcome_email(&self, id: i64) -> Result<()> {
533 let path = format!("/users/{}/resend_welcome_email", id);
534 self.client.post_raw(&path, json!({})).await?;
535 Ok(())
536 }
537}
538
539#[cfg(test)]
540mod tests {
541 use super::*;
542
543 #[test]
544 fn test_handler_creation() {
545 let client = FilesClient::builder().api_key("test-key").build().unwrap();
546 let _handler = UserHandler::new(client);
547 }
548}