admin_dto.rs
269 lignes · 9065 octets
//! Admin DTOs //! Feature 009: Admin & Settings //! //! Data Transfer Objects for admin endpoints use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; /// Dashboard statistics DTO /// /// Contains statistics displayed on the admin dashboard. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DashboardStats { /// Total number of users in the system pub total_users: u64, /// Number of active sessions (non-revoked, non-expired refresh tokens) pub active_sessions: u64, /// Number of users with admin role pub total_admins: u64, /// Last activity timestamp (most recent last_login_at) pub last_activity: Option<DateTime<Utc>>, /// Audit logging statistics (if audit_logs table exists) pub audit_log_stats: Option<AuditLogStats>, } /// Audit logging statistics #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AuditLogStats { /// Total number of audit log entries pub total_logs: u64, /// Number of logs in the last 24 hours pub logs_last_24h: u64, /// Number of logs by level pub logs_by_level: LogsByLevel, } /// Logs count by level #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LogsByLevel { /// Number of DEBUG-level log entries. pub debug: u64, /// Number of INFO-level log entries. pub info: u64, /// Number of WARN-level log entries. pub warn: u64, /// Number of ERROR-level log entries. pub error: u64, } /// User information for admin list (admin context) #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UserInfo { /// User ID pub id: uuid::Uuid, /// Username pub username: String, /// Email pub email: String, /// User roles (as strings) pub roles: Vec<String>, /// Account creation date pub created_at: DateTime<Utc>, /// Last login timestamp pub last_login_at: Option<DateTime<Utc>>, /// Last login IP address pub last_login_ip: Option<String>, } /// User list response with pagination #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UserListResponse { /// List of users pub users: Vec<UserInfo>, /// Total number of users (for pagination) pub total: u64, /// Current page number (1-indexed) pub page: u64, /// Number of users per page pub per_page: u64, /// Total number of pages pub total_pages: u64, } impl UserListResponse { /// Create a new UserListResponse pub fn new(users: Vec<UserInfo>, total: u64, page: u64, per_page: u64) -> Self { let total_pages = (total as f64 / per_page as f64).ceil() as u64; Self { users, total, page, per_page, total_pages, } } } /// Request DTO for password reset by admin (Feature 009 - Phase 6) #[derive(Debug, Deserialize, Serialize)] pub struct ResetPasswordRequest { /// Optional new password /// If None, a secure password will be generated automatically pub new_password: Option<String>, } /// Response DTO for password reset (Feature 009 - Phase 6) #[derive(Debug, Deserialize, Serialize)] pub struct ResetPasswordResponse { /// The new password (only returned if auto-generated) pub password: Option<String>, /// Success message pub message: String, } /// Request DTO for creating a new user #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreateUserRequest { /// Username (must be unique) pub username: String, /// Email address (must be unique) pub email: String, /// Password (optional - will be generated if not provided) pub password: Option<String>, /// Roles to assign to the user /// Accepts either a single string or a vector of strings #[serde(deserialize_with = "deserialize_string_or_vec")] pub roles: Vec<String>, } /// Custom deserializer that accepts either a `String` or `Vec<String>` /// /// This is needed because HTML forms send: /// - `roles=user` when one checkbox is checked (`String`) /// - `roles=user&roles=admin` when multiple checkboxes are checked (`Vec<String>`) /// /// serde_urlencoded handles duplicate fields by creating a map where values are either /// a single string or a vector. We need to handle both cases. pub fn deserialize_string_or_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error> where D: serde::Deserializer<'de>, { use serde::de::{self, Visitor}; use std::fmt; struct StringOrVec; impl<'de> Visitor<'de> for StringOrVec { type Value = Vec<String>; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a string, a sequence of strings, or a map with string values") } fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> where E: de::Error, { Ok(vec![value.to_string()]) } fn visit_string<E>(self, value: String) -> Result<Self::Value, E> where E: de::Error, { Ok(vec![value]) } fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> where A: de::SeqAccess<'de>, { let mut vec = Vec::new(); while let Some(value) = seq.next_element()? { vec.push(value); } Ok(vec) } // Handle the case where serde_urlencoded creates a map with duplicate keys // This happens when multiple checkboxes with the same name are checked fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> where A: de::MapAccess<'de>, { let mut vec = Vec::new(); while let Some((_key, value)) = map.next_entry::<String, String>()? { vec.push(value); } Ok(vec) } } // Try deserialize_any first, but also handle the map case deserializer.deserialize_any(StringOrVec) } /// Request DTO for updating a user #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpdateUserRequest { /// Email address (optional - only if modifying) pub email: Option<String>, /// Roles to assign (optional - replaces all existing roles if provided) pub roles: Option<Vec<String>>, /// Whether user must change password on next login pub must_change_password: Option<bool>, } /// Response DTO for user creation (includes generated password if applicable) #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreateUserResponse { /// Created user information pub user: UserInfo, /// Generated password (only if password was auto-generated, None if user provided password) /// This should be displayed once and never stored pub generated_password: Option<String>, } /// Request DTO for updating app settings (Phase 11 - T166) #[derive(Debug, Deserialize, Serialize)] pub struct UpdateAppSettingsRequest { // General settings /// Application display name. pub app_name: Option<String>, /// Short description of the application. pub app_description: Option<String>, /// Public domain of the application. pub app_domain: Option<String>, /// Whether public self-registration is allowed. pub allow_registration: Option<bool>, /// Whether email validation is required on registration. pub validation_email_required: Option<bool>, /// Alias for `validation_email_required` (legacy French field name). pub validation_email_requise: Option<bool>, // Security - Password complexity /// Minimum password length. pub password_min_length: Option<u32>, /// Whether passwords must contain at least one uppercase letter. pub password_require_uppercase: Option<bool>, /// Whether passwords must contain at least one lowercase letter. pub password_require_lowercase: Option<bool>, /// Whether passwords must contain at least one digit. pub password_require_digits: Option<bool>, /// Whether passwords must contain at least one special character. pub password_require_special: Option<bool>, /// Whether periodic password expiration is enabled. pub password_expiration_enabled: Option<bool>, /// Number of days before a password expires. pub password_expiration_days: Option<u32>, /// Whether an alert email is sent before password expiry. pub password_expiration_alert_enabled: Option<bool>, /// Number of days before expiry at which the alert is sent. pub password_expiration_alert_days: Option<u32>, // Audit logging /// Minimum severity level for audit log entries. pub audit_log_level: Option<String>, /// Number of days to retain audit log entries. pub audit_log_retention_days: Option<u32>, } /// Response DTO for app settings (Phase 11) #[derive(Debug, Serialize)] pub struct AppSettingsResponse { /// All current settings as key-value pairs pub settings: std::collections::HashMap<String, String>, /// Success message if just updated pub message: Option<String>, }
GitRust