//! Administrator repository operations. use crate::error::{DbError, Result}; use crate::models::{format_datetime, parse_datetime}; use chrono::Utc; use nx9_wg_core::types::auth::Admin; use sqlx::{Row, SqlitePool}; /// Retrieve the single administrator record, if initialized. pub async fn get_admin(pool: &SqlitePool) -> Result> { let row = sqlx::query( r#" SELECT id, username, password_hash, totp_secret, totp_enabled, last_login_at, last_login_ip, created_at, updated_at FROM admin WHERE id = 1 "#, ) .fetch_optional(pool) .await .map_err(DbError::Sqlx)?; match row { Some(r) => { let id: i64 = r.try_get("id")?; let username: String = r.try_get("username")?; let password_hash: String = r.try_get("password_hash")?; let totp_secret: Option = r.try_get("totp_secret")?; let totp_enabled_int: i64 = r.try_get("totp_enabled")?; let last_login_at_str: Option = r.try_get("last_login_at")?; let last_login_ip: Option = r.try_get("last_login_ip")?; let created_at_str: String = r.try_get("created_at")?; let updated_at_str: String = r.try_get("updated_at")?; let last_login_at = match last_login_at_str { Some(s) => Some(parse_datetime(&s)?), None => None, }; Ok(Some(Admin { id, username, password_hash, totp_secret, totp_enabled: totp_enabled_int != 0, last_login_at, last_login_ip, created_at: parse_datetime(&created_at_str)?, updated_at: parse_datetime(&updated_at_str)?, })) } None => Ok(None), } } /// Retrieve the administrator record by username. pub async fn get_admin_by_username(pool: &SqlitePool, username: &str) -> Result> { let admin = get_admin(pool).await?; match admin { Some(a) if a.username == username => Ok(Some(a)), _ => Ok(None), } } /// Check whether the single administrator has already been initialized. pub async fn admin_exists(pool: &SqlitePool) -> Result { let row = sqlx::query("SELECT COUNT(*) as count FROM admin WHERE id = 1") .fetch_one(pool) .await .map_err(DbError::Sqlx)?; let count: i64 = row.try_get("count")?; Ok(count > 0) } /// Create the single administrator record. /// /// Fails if an administrator already exists. pub async fn create_admin(pool: &SqlitePool, username: &str, password_hash: &str) -> Result { if admin_exists(pool).await? { return Err(DbError::Conflict( "Administrator has already been initialized".to_string(), )); } let now = Utc::now().naive_utc(); let now_str = format_datetime(&now); sqlx::query( r#" INSERT INTO admin (id, username, password_hash, totp_secret, totp_enabled, created_at, updated_at) VALUES (1, ?, ?, NULL, 0, ?, ?) "#, ) .bind(username) .bind(password_hash) .bind(&now_str) .bind(&now_str) .execute(pool) .await .map_err(|e| match &e { sqlx::Error::Database(dbe) if dbe.is_unique_violation() => { DbError::Conflict("Administrator already exists or username conflict".to_string()) } _ => DbError::Sqlx(e), })?; Ok(Admin { id: 1, username: username.to_string(), password_hash: password_hash.to_string(), totp_secret: None, totp_enabled: false, last_login_at: None, last_login_ip: None, created_at: now, updated_at: now, }) } /// Update the administrator's password hash. pub async fn update_admin_password(pool: &SqlitePool, new_password_hash: &str) -> Result<()> { let now = Utc::now().naive_utc(); let now_str = format_datetime(&now); let result = sqlx::query( r#" UPDATE admin SET password_hash = ?, updated_at = ? WHERE id = 1 "#, ) .bind(new_password_hash) .bind(&now_str) .execute(pool) .await .map_err(DbError::Sqlx)?; if result.rows_affected() == 0 { return Err(DbError::NotFound( "Administrator record does not exist".to_string(), )); } Ok(()) } /// Update administrator TOTP configuration. pub async fn update_admin_totp( pool: &SqlitePool, totp_secret: Option<&str>, totp_enabled: bool, ) -> Result<()> { let now = Utc::now().naive_utc(); let now_str = format_datetime(&now); let result = sqlx::query( r#" UPDATE admin SET totp_secret = ?, totp_enabled = ?, updated_at = ? WHERE id = 1 "#, ) .bind(totp_secret) .bind(if totp_enabled { 1 } else { 0 }) .bind(&now_str) .execute(pool) .await .map_err(DbError::Sqlx)?; if result.rows_affected() == 0 { return Err(DbError::NotFound( "Administrator record does not exist".to_string(), )); } Ok(()) } /// Record a successful administrator login timestamp and IP address. pub async fn record_admin_login(pool: &SqlitePool, ip_address: Option<&str>) -> Result<()> { let now = Utc::now().naive_utc(); let now_str = format_datetime(&now); let result = sqlx::query( r#" UPDATE admin SET last_login_at = ?, last_login_ip = ?, updated_at = ? WHERE id = 1 "#, ) .bind(&now_str) .bind(ip_address) .bind(&now_str) .execute(pool) .await .map_err(DbError::Sqlx)?; if result.rows_affected() == 0 { return Err(DbError::NotFound( "Administrator record does not exist".to_string(), )); } Ok(()) } /// Delete administrator record (if explicitly supported). pub async fn delete_admin(pool: &SqlitePool) -> Result<()> { sqlx::query("DELETE FROM admin WHERE id = 1") .execute(pool) .await .map_err(DbError::Sqlx)?; Ok(()) }