use crate::models::users::{user_sessions, user_ssh_keys}; use crate::protos::auth::common::v1::{SessionInfo, SshKeyInfo}; use chrono::{DateTime, NaiveDate, TimeZone, Utc}; use prost_types::Timestamp; use crate::api::store; pub const DEFAULT_PAGE_SIZE: i32 = 20; pub const MAX_PAGE_SIZE: i32 = 100; pub fn timestamp(seconds: i64, nanos: i32) -> Timestamp { Timestamp { seconds, nanos } } pub fn timestamp_from_datetime(value: DateTime) -> Timestamp { Timestamp { seconds: value.timestamp(), nanos: value.timestamp_subsec_nanos() as i32, } } pub fn now_timestamp() -> Timestamp { timestamp_from_datetime(Utc::now()) } pub fn datetime(value: &Timestamp) -> Option> { let nanos = value.nanos.clamp(0, 999_999_999) as u32; Utc.timestamp_opt(value.seconds, nanos).single() } pub fn date_timestamp(value: NaiveDate) -> Option { value .and_hms_opt(0, 0, 0) .map(|naive| timestamp_from_datetime(Utc.from_utc_datetime(&naive))) } pub fn naive_date(value: &Timestamp) -> Option { datetime(value).map(|value| value.date_naive()) } pub fn datetime_opt(value: Option>) -> Option { value.map(timestamp_from_datetime) } pub fn session_info(row: &user_sessions::Model) -> SessionInfo { SessionInfo { session_id: row.id.to_string(), user_id: row.user_id, security_version: row.security_version, client_ip: row.ip_address.clone().unwrap_or_default(), user_agent: row.user_agent.clone().unwrap_or_default(), created_at: datetime_opt(Some(row.created_at)), last_seen_at: datetime_opt(Some(row.last_seen_at)), expires_at: datetime_opt(Some(row.expires_at)), refresh_expires_at: datetime_opt(row.refresh_expires_at), mfa_verified_at: datetime_opt(row.mfa_verified_at), revoked_at: datetime_opt(row.revoked_at), revoke_reason: String::new(), } } pub fn ssh_key_info(row: &user_ssh_keys::Model) -> SshKeyInfo { SshKeyInfo { key_id: row.id.to_string(), user_id: row.user_id, name: row.name.clone(), description: row.description.clone().unwrap_or_default(), algorithm: store::ssh_algorithm_code(row.algorithm.clone()), public_key: row.public_key.clone(), fingerprint: row.fingerprint.clone(), created_at: datetime_opt(Some(row.created_at)), expires_at: datetime_opt(row.expires_at), last_used_at: datetime_opt(row.last_used_at), revoked_at: datetime_opt(row.revoked_at), } } pub fn page_size(requested: i32) -> i32 { if requested <= 0 { DEFAULT_PAGE_SIZE } else { requested.min(MAX_PAGE_SIZE) } } pub fn offset(page: i32, page_size: i32) -> u64 { let page = page.max(1) as u64; let page_size = page_size.max(1) as u64; page.saturating_sub(1).saturating_mul(page_size) } pub fn has_more(offset: u64, page_size: i32, row_count: usize) -> bool { row_count as u64 == page_size.max(0) as u64 && offset + row_count as u64 > offset }