mascarpone/src/models/contact.rs

150 lines
4.5 KiB
Rust
Raw Normal View History

2026-04-03 11:54:36 -05:00
use jiff::{Timestamp, civil};
2026-01-26 22:14:58 -06:00
use sqlx::sqlite::SqlitePool;
2025-11-27 13:45:21 -06:00
use std::str::FromStr;
use super::Birthday;
2026-01-26 22:14:58 -06:00
use crate::AppError;
2025-11-27 13:45:21 -06:00
use crate::db::DbId;
2026-01-26 22:14:58 -06:00
use crate::switchboard::MentionHostType;
struct RawContact {
id: DbId,
birthday: Option<String>,
manually_freshened_at: Option<String>,
lives_with: String,
}
2025-11-27 13:45:21 -06:00
#[derive(Clone, Debug)]
pub struct Contact {
pub id: DbId,
pub birthday: Option<Birthday>,
2026-04-03 11:54:36 -05:00
pub manually_freshened_at: Option<Timestamp>,
2026-01-24 11:13:27 -06:00
pub lives_with: String,
2025-11-27 13:45:21 -06:00
}
2026-01-26 22:14:58 -06:00
impl Into<Contact> for RawContact {
fn into(self) -> Contact {
Contact {
id: self.id,
birthday: self
.birthday
.and_then(|s| Birthday::from_str(s.as_ref()).ok()),
manually_freshened_at: self
.manually_freshened_at
2026-04-03 11:54:36 -05:00
.and_then(|str| str.parse::<Timestamp>().ok()),
2026-01-26 22:14:58 -06:00
lives_with: self.lives_with,
}
}
}
struct RawHydratedContact {
id: DbId,
birthday: Option<String>,
manually_freshened_at: Option<String>,
lives_with: String,
last_mention_date: Option<String>,
names: Option<String>,
}
2025-11-27 13:45:21 -06:00
#[derive(Clone, Debug)]
pub struct HydratedContact {
pub contact: Contact,
2026-04-03 11:54:36 -05:00
pub last_mention_date: Option<civil::Date>,
2025-11-27 13:45:21 -06:00
pub names: Vec<String>,
}
2026-01-26 22:14:58 -06:00
impl Into<HydratedContact> for RawHydratedContact {
fn into(self) -> HydratedContact {
HydratedContact {
contact: Into::<Contact>::into(RawContact {
id: self.id,
birthday: self.birthday,
manually_freshened_at: self.manually_freshened_at,
lives_with: self.lives_with,
}),
names: self
.names
.unwrap_or(String::new())
.split('\x1c')
.map(|s| s.to_string())
.collect::<Vec<String>>(),
last_mention_date: self
.last_mention_date
2026-04-03 11:54:36 -05:00
.and_then(|str| str.parse::<civil::Date>().ok()),
2026-01-26 22:14:58 -06:00
}
}
}
2025-11-27 13:45:21 -06:00
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()
}
}
2026-01-26 22:14:58 -06:00
pub async fn load(id: DbId, pool: &SqlitePool) -> Result<Self, AppError> {
// copy-paste the query from 'all', then add "where c.id = $2" to the last line
let raw = sqlx::query_as!(
RawHydratedContact,
r#"select id, birthday, lives_with, manually_freshened_at as "manually_freshened_at: String", (
2026-01-31 21:01:01 -06:00
select string_agg(name,x'1c' order by sort)
2026-01-26 22:14:58 -06:00
from names where contact_id = c.id
) as names, (
select jes.date from journal_entries jes
join mentions ms on ms.entity_id = jes.id
where ms.entity_type = $1
and ms.url = '/contact/'||c.id
or ms.url in (
select '/group/'||slug from groups where
contact_id = c.id
)
order by jes.date desc limit 1
) as last_mention_date from contacts c
where c.id = $2"#,
MentionHostType::JournalEntry as DbId,
id
)
.fetch_one(pool)
.await?;
2025-11-27 13:45:21 -06:00
2026-01-26 22:14:58 -06:00
Ok(Into::<HydratedContact>::into(raw))
2025-11-27 13:45:21 -06:00
}
2026-01-26 22:14:58 -06:00
pub async fn all(pool: &SqlitePool) -> Result<Vec<Self>, AppError> {
let contacts = sqlx::query_as!(
RawHydratedContact,
r#"select id, birthday, lives_with, manually_freshened_at as "manually_freshened_at: String", (
2026-01-31 21:01:01 -06:00
select string_agg(name,x'1c' order by sort)
2026-01-26 22:14:58 -06:00
from names where contact_id = c.id
) as names, (
select jes.date from journal_entries jes
join mentions ms on ms.entity_id = jes.id
where ms.entity_type = $1
and ms.url = '/contact/'||c.id
or ms.url in (
select '/group/'||slug from groups where
contact_id = c.id
)
order by jes.date desc limit 1
) as last_mention_date from contacts c"#,
MentionHostType::JournalEntry as DbId
)
.fetch_all(pool)
.await?;
2025-11-27 13:45:21 -06:00
2026-01-26 22:14:58 -06:00
Ok(contacts
.into_iter()
.map(|raw| Into::<HydratedContact>::into(raw))
.collect())
2025-11-27 13:45:21 -06:00
}
}