//! Single Administrator account, password, API tokens, and session management view model. use chrono::NaiveDateTime; use serde::{Deserialize, Serialize}; /// Active session view row. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SessionRowView { pub id: String, pub ip_address: String, pub user_agent: String, pub created_at: NaiveDateTime, pub expires_at: NaiveDateTime, pub is_current: bool, } /// Active API token view row. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ApiTokenRowView { pub id: String, pub name: String, pub created_at: NaiveDateTime, pub expires_at: Option, pub last_used_at: Option, pub is_revoked: bool, } /// Administrator management view state. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AdministratorState { // Card 1: Account pub username: String, pub last_login_at: Option, pub last_login_ip: Option, pub totp_enabled: bool, pub active_session_count: usize, // Card 2: Password change pub current_password: String, pub new_password: String, pub confirm_password: String, // Card 3: API Tokens pub new_token_name: String, pub new_token_expires_days: Option, pub created_token_plaintext: Option, pub tokens: Vec, // Card 4: Sessions pub sessions: Vec, // Feedback pub success_message: Option, pub error_message: Option, } impl Default for AdministratorState { fn default() -> Self { Self { username: "admin".to_string(), last_login_at: None, last_login_ip: None, totp_enabled: false, active_session_count: 1, current_password: String::new(), new_password: String::new(), confirm_password: String::new(), new_token_name: String::new(), new_token_expires_days: Some(30), created_token_plaintext: None, tokens: Vec::new(), sessions: Vec::new(), success_message: None, error_message: None, } } } impl AdministratorState { pub fn validate_password_change(&self) -> Result<(), &'static str> { if self.current_password.is_empty() { return Err("Current password is required"); } if self.new_password.len() < 8 { return Err("New password must be at least 8 characters long"); } if self.new_password != self.confirm_password { return Err("New password and confirmation do not match"); } Ok(()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_admin_password_validation() { let mut state = AdministratorState::default(); assert!(state.validate_password_change().is_err()); state.current_password = "OldPassword123!".to_string(); state.new_password = "NewPassword123!".to_string(); state.confirm_password = "MismatchPassword!".to_string(); assert!(state.validate_password_change().is_err()); state.confirm_password = "NewPassword123!".to_string(); assert!(state.validate_password_change().is_ok()); } }