refactor: fewer non-macro queries
Some checks failed
/ integration-test--firefox (push) Failing after 3m5s
Some checks failed
/ integration-test--firefox (push) Failing after 3m5s
This commit is contained in:
parent
69e23fd9bb
commit
84c41dda4d
5 changed files with 119 additions and 111 deletions
|
|
@ -1,10 +1,18 @@
|
||||||
use chrono::{DateTime, NaiveDate, Utc};
|
use chrono::{DateTime, NaiveDate, Utc};
|
||||||
use sqlx::sqlite::SqliteRow;
|
use sqlx::sqlite::SqlitePool;
|
||||||
use sqlx::{FromRow, Row};
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use super::Birthday;
|
use super::Birthday;
|
||||||
|
use crate::AppError;
|
||||||
use crate::db::DbId;
|
use crate::db::DbId;
|
||||||
|
use crate::switchboard::MentionHostType;
|
||||||
|
|
||||||
|
struct RawContact {
|
||||||
|
id: DbId,
|
||||||
|
birthday: Option<String>,
|
||||||
|
manually_freshened_at: Option<String>,
|
||||||
|
lives_with: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Contact {
|
pub struct Contact {
|
||||||
|
|
@ -14,6 +22,31 @@ pub struct Contact {
|
||||||
pub lives_with: String,
|
pub lives_with: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
.and_then(|str| DateTime::parse_from_str(str.as_ref(), "%+").ok())
|
||||||
|
.map(|d| d.to_utc()),
|
||||||
|
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>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct HydratedContact {
|
pub struct HydratedContact {
|
||||||
pub contact: Contact,
|
pub contact: Contact,
|
||||||
|
|
@ -21,6 +54,28 @@ pub struct HydratedContact {
|
||||||
pub names: Vec<String>,
|
pub names: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
.and_then(|str| NaiveDate::from_str(str.as_ref()).ok()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::ops::Deref for HydratedContact {
|
impl std::ops::Deref for HydratedContact {
|
||||||
type Target = Contact;
|
type Target = Contact;
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
|
|
@ -36,54 +91,60 @@ impl HydratedContact {
|
||||||
"(unnamed)".to_string()
|
"(unnamed)".to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl FromRow<'_, SqliteRow> for Contact {
|
pub async fn load(id: DbId, pool: &SqlitePool) -> Result<Self, AppError> {
|
||||||
fn from_row(row: &SqliteRow) -> sqlx::Result<Self> {
|
// copy-paste the query from 'all', then add "where c.id = $2" to the last line
|
||||||
let id: DbId = row.try_get("id")?;
|
let raw = sqlx::query_as!(
|
||||||
|
RawHydratedContact,
|
||||||
|
r#"select id, birthday, lives_with, manually_freshened_at as "manually_freshened_at: String", (
|
||||||
|
select string_agg(name,'\x1c' order by sort)
|
||||||
|
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?;
|
||||||
|
|
||||||
let birthday = Birthday::from_row(row).ok();
|
Ok(Into::<HydratedContact>::into(raw))
|
||||||
|
}
|
||||||
|
|
||||||
let manually_freshened_at = row
|
pub async fn all(pool: &SqlitePool) -> Result<Vec<Self>, AppError> {
|
||||||
.try_get::<String, &str>("manually_freshened_at")
|
let contacts = sqlx::query_as!(
|
||||||
.ok()
|
RawHydratedContact,
|
||||||
.and_then(|str| {
|
r#"select id, birthday, lives_with, manually_freshened_at as "manually_freshened_at: String", (
|
||||||
DateTime::parse_from_str(&str, "%+")
|
select string_agg(name,'\x1c' order by sort)
|
||||||
.ok()
|
from names where contact_id = c.id
|
||||||
.map(|d| d.to_utc())
|
) 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?;
|
||||||
|
|
||||||
let lives_with: String = row.try_get("lives_with")?;
|
Ok(contacts
|
||||||
|
.into_iter()
|
||||||
Ok(Self {
|
.map(|raw| Into::<HydratedContact>::into(raw))
|
||||||
id,
|
.collect())
|
||||||
birthday,
|
|
||||||
manually_freshened_at,
|
|
||||||
lives_with,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromRow<'_, SqliteRow> for HydratedContact {
|
|
||||||
fn from_row(row: &SqliteRow) -> sqlx::Result<Self> {
|
|
||||||
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::<String, &str>("last_mention_date")
|
|
||||||
.ok()
|
|
||||||
.and_then(|str| NaiveDate::from_str(&str).ok());
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
contact,
|
|
||||||
names,
|
|
||||||
last_mention_date,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,17 +76,7 @@ mod get {
|
||||||
let user = auth_session.user.unwrap();
|
let user = auth_session.user.unwrap();
|
||||||
let pool = &state.db(&user).pool;
|
let pool = &state.db(&user).pool;
|
||||||
|
|
||||||
let contact: HydratedContact = sqlx::query_as(
|
let contact = HydratedContact::load(contact_id, pool).await?;
|
||||||
"select *, (
|
|
||||||
select string_agg(name,'\x1c' order by sort)
|
|
||||||
from names where contact_id = c.id
|
|
||||||
) as names
|
|
||||||
from contacts c
|
|
||||||
where c.id = $1",
|
|
||||||
)
|
|
||||||
.bind(contact_id)
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let entries: Vec<JournalEntry> = sqlx::query_as(
|
let entries: Vec<JournalEntry> = sqlx::query_as(
|
||||||
"select distinct j.id, j.value, j.date from journal_entries j
|
"select distinct j.id, j.value, j.date from journal_entries j
|
||||||
|
|
@ -231,29 +221,7 @@ mod get {
|
||||||
layout: Layout,
|
layout: Layout,
|
||||||
) -> Result<Markup, AppError> {
|
) -> Result<Markup, AppError> {
|
||||||
let pool = &state.db(&auth_session.user.unwrap()).pool;
|
let pool = &state.db(&auth_session.user.unwrap()).pool;
|
||||||
let contact: HydratedContact = sqlx::query_as(
|
let contact = HydratedContact::load(contact_id, pool).await?;
|
||||||
"select *, (
|
|
||||||
select string_agg(name,'\x1c' order by sort)
|
|
||||||
from names where contact_id = c.id
|
|
||||||
) as names, (
|
|
||||||
select jes.date from journal_entries jes
|
|
||||||
join mentions m on m.entity_id = jes.id
|
|
||||||
where
|
|
||||||
m.entity_type = $1 and (
|
|
||||||
m.url = '/contact/'||c.id
|
|
||||||
or m.url in (
|
|
||||||
select '/group/'||name
|
|
||||||
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",
|
|
||||||
)
|
|
||||||
.bind(MentionHostType::JournalEntry as DbId)
|
|
||||||
.bind(contact_id)
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let addresses: Vec<Address> = sqlx::query_as!(
|
let addresses: Vec<Address> = sqlx::query_as!(
|
||||||
Address,
|
Address,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ use super::Layout;
|
||||||
use crate::db::DbId;
|
use crate::db::DbId;
|
||||||
use crate::models::user::AuthSession;
|
use crate::models::user::AuthSession;
|
||||||
use crate::models::{Birthday, HydratedContact, JournalEntry};
|
use crate::models::{Birthday, HydratedContact, JournalEntry};
|
||||||
use crate::switchboard::{MentionHost, MentionHostType};
|
|
||||||
use crate::{AppError, AppState};
|
use crate::{AppError, AppState};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -129,22 +128,7 @@ pub mod get {
|
||||||
let user = auth_session.user.unwrap();
|
let user = auth_session.user.unwrap();
|
||||||
let pool = &state.db(&user).pool;
|
let pool = &state.db(&user).pool;
|
||||||
|
|
||||||
let contacts: Vec<HydratedContact> = sqlx::query_as(
|
let contacts = HydratedContact::all(&pool).await?;
|
||||||
"select *, (
|
|
||||||
select string_agg(name,'\x1c' order by sort)
|
|
||||||
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
|
|
||||||
order by jes.date desc limit 1
|
|
||||||
) as last_mention_date from contacts c",
|
|
||||||
)
|
|
||||||
.bind(MentionHostType::JournalEntry as DbId)
|
|
||||||
.fetch_all(pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut freshens: Vec<ContactFreshness> = contacts
|
let mut freshens: Vec<ContactFreshness> = contacts
|
||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
|
||||||
|
|
@ -55,15 +55,9 @@ mod get {
|
||||||
let mut calendar = Calendar::new();
|
let mut calendar = Calendar::new();
|
||||||
calendar.name(&calname);
|
calendar.name(&calname);
|
||||||
calendar.append_property(("PRODID", "Mascarpone CRM"));
|
calendar.append_property(("PRODID", "Mascarpone CRM"));
|
||||||
let contacts: Vec<HydratedContact> = sqlx::query_as(
|
|
||||||
"select id, birthday, (
|
// TODO; this does some db work to pull in last_modified_date that we don't use
|
||||||
select string_agg(name,'\x1c' order by sort)
|
let contacts = HydratedContact::all(&pool).await?;
|
||||||
from names where contact_id = c.id
|
|
||||||
) as names
|
|
||||||
from contacts c",
|
|
||||||
)
|
|
||||||
.fetch_all(&pool)
|
|
||||||
.await?;
|
|
||||||
for contact in &contacts {
|
for contact in &contacts {
|
||||||
if let Some(Birthday::Date(yo_date)) = &contact.birthday {
|
if let Some(Birthday::Date(yo_date)) = &contact.birthday {
|
||||||
if let Some(date) = NaiveDate::from_ymd_opt(
|
if let Some(date) = NaiveDate::from_ymd_opt(
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ use axum::extract::FromRequestParts;
|
||||||
use cache_bust::asset;
|
use cache_bust::asset;
|
||||||
use http::request::Parts;
|
use http::request::Parts;
|
||||||
use maud::{DOCTYPE, Markup, html};
|
use maud::{DOCTYPE, Markup, html};
|
||||||
use sqlx::FromRow;
|
|
||||||
|
|
||||||
use super::models::user::{AuthSession, User};
|
use super::models::user::{AuthSession, User};
|
||||||
use super::{AppError, AppState};
|
use super::{AppError, AppState};
|
||||||
|
use crate::db::DbId;
|
||||||
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod contact;
|
pub mod contact;
|
||||||
|
|
@ -16,10 +16,10 @@ pub mod ics;
|
||||||
pub mod journal;
|
pub mod journal;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
|
||||||
#[derive(Debug, FromRow)]
|
#[derive(Debug)]
|
||||||
struct ContactLink {
|
struct ContactLink {
|
||||||
name: String,
|
name: String,
|
||||||
contact_id: u32,
|
contact_id: DbId,
|
||||||
}
|
}
|
||||||
pub struct Layout {
|
pub struct Layout {
|
||||||
contact_links: Vec<ContactLink>,
|
contact_links: Vec<ContactLink>,
|
||||||
|
|
@ -39,7 +39,8 @@ impl FromRequestParts<AppState> for Layout {
|
||||||
.map_err(|_| anyhow::Error::msg("could not get session"))?;
|
.map_err(|_| anyhow::Error::msg("could not get session"))?;
|
||||||
let user = auth_session.user.unwrap();
|
let user = auth_session.user.unwrap();
|
||||||
|
|
||||||
let contact_links: Vec<ContactLink> = sqlx::query_as(
|
let contact_links = sqlx::query_as!(
|
||||||
|
ContactLink,
|
||||||
"select c.id as contact_id,
|
"select c.id as contact_id,
|
||||||
coalesce(n.name, '(unnamed)') as name
|
coalesce(n.name, '(unnamed)') as name
|
||||||
from contacts c
|
from contacts c
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue