use chrono::{DateTime, NaiveDate, Utc}; use sqlx::sqlite::SqliteRow; use sqlx::{FromRow, Row}; use std::str::FromStr; use super::Birthday; use crate::db::DbId; #[derive(Clone, Debug)] pub struct Contact { pub id: DbId, pub birthday: Option, pub manually_freshened_at: Option>, } #[derive(Clone, Debug)] pub struct HydratedContact { pub contact: Contact, pub last_mention_date: Option, pub names: Vec, } impl std::ops::Deref for HydratedContact { type Target = Contact; fn deref(&self) -> &Self::Target { &self.contact } } impl HydratedContact { pub fn display_name(&self) -> String { if let Some(name) = self.names.first() { name.clone() } else { "(unnamed)".to_string() } } } pub type ContactTrie = radix_trie::Trie; impl FromRow<'_, SqliteRow> for Contact { fn from_row(row: &SqliteRow) -> sqlx::Result { let id: DbId = row.try_get("id")?; let birthday = Birthday::from_row(row).ok(); let manually_freshened_at = row .try_get::("manually_freshened_at") .ok() .and_then(|str| { DateTime::parse_from_str(&str, "%+") .ok() .map(|d| d.to_utc()) }); Ok(Self { id, birthday, manually_freshened_at, }) } } impl FromRow<'_, SqliteRow> for HydratedContact { fn from_row(row: &SqliteRow) -> sqlx::Result { let contact = Contact::from_row(row)?; let names_str: String = row.try_get("names").unwrap_or("".to_string()); let names = if names_str.is_empty() { vec![] } else { names_str.split('\x1c').map(|s| s.to_string()).collect() }; let last_mention_date = row .try_get::("last_mention_date") .ok() .and_then(|str| NaiveDate::from_str(&str).ok()); Ok(Self { contact, names, last_mention_date, }) } }