feat: mentions in lives_with and text_body fields
Some checks failed
/ integration-test--firefox (push) Failing after 3m7s
Some checks failed
/ integration-test--firefox (push) Failing after 3m7s
This commit is contained in:
parent
fd5f1899c1
commit
d42adbe274
10 changed files with 369 additions and 200 deletions
|
|
@ -8,7 +8,7 @@ use axum::{
|
|||
use axum_extra::extract::Form;
|
||||
use cache_bust::asset;
|
||||
use chrono::DateTime;
|
||||
use maud::{Markup, PreEscaped, html};
|
||||
use maud::{Markup, html};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use slug::slugify;
|
||||
|
|
@ -19,6 +19,7 @@ use super::home::journal_section;
|
|||
use crate::db::DbId;
|
||||
use crate::models::user::AuthSession;
|
||||
use crate::models::{HydratedContact, JournalEntry};
|
||||
use crate::switchboard::{MentionHost, MentionHostType, insert_mentions};
|
||||
use crate::{AppError, AppState};
|
||||
|
||||
#[derive(serde::Serialize, Debug)]
|
||||
|
|
@ -69,10 +70,12 @@ mod get {
|
|||
pub async fn contact(
|
||||
auth_session: AuthSession,
|
||||
State(state): State<AppState>,
|
||||
Path(contact_id): Path<u32>,
|
||||
Path(contact_id): Path<DbId>,
|
||||
layout: Layout,
|
||||
) -> Result<Markup, AppError> {
|
||||
let pool = &state.db(&auth_session.user.unwrap()).pool;
|
||||
let user = auth_session.user.unwrap();
|
||||
let pool = &state.db(&user).pool;
|
||||
|
||||
let contact: HydratedContact = sqlx::query_as(
|
||||
"select *, (
|
||||
select string_agg(name,'\x1c' order by sort)
|
||||
|
|
@ -87,18 +90,30 @@ mod get {
|
|||
|
||||
let entries: Vec<JournalEntry> = sqlx::query_as(
|
||||
"select distinct j.id, j.value, j.date from journal_entries j
|
||||
join journal_mentions cm on j.id = cm.entry_id
|
||||
where cm.url = '/contact/'||$1 or cm.url in (
|
||||
join mentions m on j.id = m.entity_id
|
||||
where m.entity_type = $1 and (m.url = '/contact/'||$1 or m.url in (
|
||||
select '/group/'||slug from groups
|
||||
where contact_id = $1
|
||||
)
|
||||
where contact_id = $2
|
||||
))
|
||||
order by j.date desc
|
||||
",
|
||||
)
|
||||
.bind(MentionHostType::JournalEntry as DbId)
|
||||
.bind(contact_id)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
let lives_with = if contact.lives_with.len() > 1 {
|
||||
let mention_host = MentionHost {
|
||||
entity_id: contact_id,
|
||||
entity_type: MentionHostType::ContactLivesWith as DbId,
|
||||
input: &contact.lives_with,
|
||||
};
|
||||
Some(mention_host.format_pool(pool).await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let addresses: Vec<Address> = sqlx::query_as!(
|
||||
Address,
|
||||
"select * from addresses where contact_id = $1",
|
||||
|
|
@ -157,9 +172,9 @@ mod get {
|
|||
}
|
||||
}
|
||||
|
||||
@if contact.lives_with.len() > 0 {
|
||||
@if let Some(lives_with) = lives_with {
|
||||
label { "lives with" }
|
||||
div { (contact.lives_with) }
|
||||
div { (lives_with) }
|
||||
}
|
||||
|
||||
@if addresses.len() == 1 {
|
||||
|
|
@ -196,7 +211,11 @@ mod get {
|
|||
|
||||
@if let Some(text_body) = text_body {
|
||||
@if text_body.len() > 0 {
|
||||
#text_body { (PreEscaped(markdown::to_html(&text_body))) }
|
||||
#text_body { (MentionHost {
|
||||
entity_id: contact_id,
|
||||
entity_type: MentionHostType::ContactTextBody as DbId,
|
||||
input: &text_body
|
||||
}.format_pool(pool).await?) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -208,7 +227,7 @@ mod get {
|
|||
pub async fn contact_edit(
|
||||
auth_session: AuthSession,
|
||||
State(state): State<AppState>,
|
||||
Path(contact_id): Path<u32>,
|
||||
Path(contact_id): Path<DbId>,
|
||||
layout: Layout,
|
||||
) -> Result<Markup, AppError> {
|
||||
let pool = &state.db(&auth_session.user.unwrap()).pool;
|
||||
|
|
@ -218,17 +237,20 @@ mod get {
|
|||
from names where contact_id = c.id
|
||||
) as names, (
|
||||
select jes.date from journal_entries jes
|
||||
join journal_mentions cms on cms.entry_id = jes.id
|
||||
where cms.url = '/contact/'||c.id
|
||||
or cms.url in (
|
||||
select '/group/'||name
|
||||
from groups
|
||||
where contact_id = c.id
|
||||
)
|
||||
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 = $1",
|
||||
where c.id = $2",
|
||||
)
|
||||
.bind(MentionHostType::JournalEntry as DbId)
|
||||
.bind(contact_id)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
|
@ -391,6 +413,7 @@ mod put {
|
|||
) -> Result<impl IntoResponse, AppError> {
|
||||
let user = auth_session.user.unwrap();
|
||||
let pool = &state.db(&user).pool;
|
||||
let sw_lock = state.switchboard(&user);
|
||||
|
||||
let birthday = if payload.birthday.is_empty() {
|
||||
None
|
||||
|
|
@ -415,8 +438,15 @@ mod put {
|
|||
Some(payload.text_body)
|
||||
};
|
||||
|
||||
let old_contact = sqlx::query!("select * from contacts where id = $1", contact_id)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
sqlx::query!(
|
||||
"update contacts set (birthday, manually_freshened_at, lives_with, text_body) = ($1, $2, $3, $4) where id = $5",
|
||||
"update contacts set
|
||||
(birthday, manually_freshened_at, lives_with, text_body) =
|
||||
($1, $2, $3, $4)
|
||||
where id = $5",
|
||||
birthday,
|
||||
manually_freshened_at,
|
||||
payload.lives_with,
|
||||
|
|
@ -426,6 +456,52 @@ mod put {
|
|||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
if old_contact.lives_with != payload.lives_with {
|
||||
sqlx::query!(
|
||||
"delete from mentions where entity_id = $1 and entity_type = $2",
|
||||
contact_id,
|
||||
MentionHostType::ContactLivesWith as DbId
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
let mention_host = MentionHost {
|
||||
entity_id: contact_id,
|
||||
entity_type: MentionHostType::ContactLivesWith as DbId,
|
||||
input: &payload.lives_with,
|
||||
};
|
||||
|
||||
let mentions = {
|
||||
let switchboard = sw_lock.read().unwrap();
|
||||
switchboard.extract_mentions(mention_host)
|
||||
};
|
||||
insert_mentions(&mentions, pool).await?;
|
||||
}
|
||||
|
||||
if old_contact.text_body != text_body {
|
||||
sqlx::query!(
|
||||
"delete from mentions where entity_id = $1 and entity_type = $2",
|
||||
contact_id,
|
||||
MentionHostType::ContactTextBody as DbId
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
if text_body.is_some() {
|
||||
let mention_host = MentionHost {
|
||||
entity_id: contact_id,
|
||||
entity_type: MentionHostType::ContactTextBody as DbId,
|
||||
input: &text_body.unwrap(),
|
||||
};
|
||||
|
||||
let mentions = {
|
||||
let switchboard = sw_lock.read().unwrap();
|
||||
switchboard.extract_mentions(mention_host)
|
||||
};
|
||||
insert_mentions(&mentions, pool).await?;
|
||||
}
|
||||
}
|
||||
|
||||
// these blocks are not in functions because payload gets progressively
|
||||
// partially moved as we handle each field and i don't want to deal with it
|
||||
|
||||
|
|
@ -485,11 +561,11 @@ mod put {
|
|||
let old_names: Vec<String> = old_names.into_iter().map(|(s,)| s).collect();
|
||||
|
||||
if old_names != new_names {
|
||||
// delete and regen *all* journal mentions, not just the ones for the
|
||||
// current user, since changing *this* user's names can change, *globally*,
|
||||
// delete and regen *all* mentions, not just the ones for the current
|
||||
// contact, since changing *this* contact's names can change, *globally*,
|
||||
// which names have n=1 and thus are eligible for mentioning
|
||||
sqlx::query!(
|
||||
"delete from journal_mentions; delete from names where contact_id = $1",
|
||||
"delete from mentions; delete from names where contact_id = $1",
|
||||
contact_id
|
||||
)
|
||||
.execute(pool)
|
||||
|
|
@ -531,14 +607,13 @@ mod put {
|
|||
.await?;
|
||||
|
||||
{
|
||||
let trie_mutex = state.contact_search(&user);
|
||||
let mut trie = trie_mutex.write().unwrap();
|
||||
let mut switchboard = sw_lock.write().unwrap();
|
||||
for name in &old_names {
|
||||
trie.remove(name);
|
||||
switchboard.remove(name);
|
||||
}
|
||||
|
||||
for name in recalc_names {
|
||||
trie.insert(name.0, format!("/contact/{}", name.1));
|
||||
switchboard.add_mentionable(name.0, format!("/contact/{}", name.1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -558,7 +633,7 @@ mod put {
|
|||
|
||||
if new_groups != old_groups {
|
||||
sqlx::query!(
|
||||
"delete from journal_mentions; delete from groups where contact_id = $1",
|
||||
"delete from mentions; delete from groups where contact_id = $1",
|
||||
contact_id
|
||||
)
|
||||
.execute(pool)
|
||||
|
|
@ -576,17 +651,17 @@ mod put {
|
|||
.await?;
|
||||
|
||||
{
|
||||
let trie_mutex = state.contact_search(&user);
|
||||
let mut trie = trie_mutex.write().unwrap();
|
||||
let mut switchboard = sw_lock.write().unwrap();
|
||||
for name in &old_groups {
|
||||
// TODO i think we care about group name vs contact name counts,
|
||||
// otherwise this will cause a problem (or we want to disallow
|
||||
// setting group names that are contact names or vice versa?)
|
||||
trie.remove(name);
|
||||
switchboard.remove(name);
|
||||
}
|
||||
|
||||
for group in &new_groups {
|
||||
trie.insert(group.clone(), format!("/group/{}", slugify(group)));
|
||||
switchboard
|
||||
.add_mentionable(group.clone(), format!("/group/{}", slugify(group)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -598,9 +673,11 @@ mod put {
|
|||
.await?;
|
||||
|
||||
for entry in journal_entries {
|
||||
entry
|
||||
.insert_mentions(state.contact_search(&user), pool)
|
||||
.await?;
|
||||
let mentions = {
|
||||
let switchboard = sw_lock.read().unwrap();
|
||||
switchboard.extract_mentions(&entry)
|
||||
};
|
||||
insert_mentions(&mentions, pool).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -620,14 +697,9 @@ mod delete {
|
|||
let user = auth_session.user.unwrap();
|
||||
let pool = &state.db(&user).pool;
|
||||
|
||||
sqlx::query(
|
||||
"delete from journal_mentions where contact_id = $1;
|
||||
delete from names where contact_id = $1;
|
||||
delete from contacts where id = $1;",
|
||||
)
|
||||
.bind(contact_id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
sqlx::query!("delete from contacts where id = $1", contact_id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("HX-Redirect", "/".parse()?);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use super::Layout;
|
|||
use crate::db::DbId;
|
||||
use crate::models::user::AuthSession;
|
||||
use crate::models::{Birthday, HydratedContact, JournalEntry};
|
||||
use crate::switchboard::{MentionHost, MentionHostType};
|
||||
use crate::{AppError, AppState};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -109,7 +110,7 @@ pub async fn journal_section(
|
|||
|
||||
.entries {
|
||||
@for entry in entries {
|
||||
(entry.to_html(pool).await?)
|
||||
(Into::<MentionHost>::into(entry).format_pool(pool).await?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -124,18 +125,22 @@ pub mod get {
|
|||
State(state): State<AppState>,
|
||||
layout: Layout,
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
let pool = &state.db(&auth_session.user.unwrap()).pool;
|
||||
let user = auth_session.user.unwrap();
|
||||
let pool = &state.db(&user).pool;
|
||||
|
||||
let contacts: Vec<HydratedContact> = sqlx::query_as(
|
||||
"select id, birthday, manually_freshened_at, (
|
||||
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 journal_mentions cms on cms.entry_id = jes.id
|
||||
where cms.url = '/contact/'||c.id
|
||||
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?;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use serde::Deserialize;
|
|||
|
||||
use crate::models::JournalEntry;
|
||||
use crate::models::user::AuthSession;
|
||||
use crate::switchboard::{MentionHost, insert_mentions};
|
||||
use crate::{AppError, AppState};
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
|
|
@ -36,6 +37,8 @@ mod post {
|
|||
) -> Result<Markup, AppError> {
|
||||
let user = auth_session.user.unwrap();
|
||||
let pool = &state.db(&user).pool;
|
||||
let sw_lock = state.switchboard(&user);
|
||||
|
||||
let now = Local::now().date_naive();
|
||||
|
||||
let date = if payload.date.is_empty() {
|
||||
|
|
@ -73,9 +76,11 @@ mod post {
|
|||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
entry
|
||||
.insert_mentions(state.contact_search(&user), pool)
|
||||
.await?;
|
||||
let mentions = {
|
||||
let switchboard = sw_lock.read().unwrap();
|
||||
switchboard.extract_mentions(&entry)
|
||||
};
|
||||
insert_mentions(&mentions, pool).await?;
|
||||
|
||||
Ok(entry.to_html(pool).await?)
|
||||
}
|
||||
|
|
@ -84,6 +89,7 @@ mod post {
|
|||
mod patch {
|
||||
use super::*;
|
||||
|
||||
#[axum::debug_handler]
|
||||
pub async fn entry(
|
||||
auth_session: AuthSession,
|
||||
State(state): State<AppState>,
|
||||
|
|
@ -92,8 +98,10 @@ mod patch {
|
|||
) -> Result<impl IntoResponse, AppError> {
|
||||
let user = auth_session.user.unwrap();
|
||||
let pool = &state.db(&user).pool;
|
||||
let sw_lock = state.switchboard(&user);
|
||||
|
||||
// not a macro query, we want to use JournalEntry's custom FromRow
|
||||
let entry: JournalEntry = sqlx::query_as("select * from journal_entries where id = $1")
|
||||
let old_entry: JournalEntry = sqlx::query_as("select * from journal_entries where id = $1")
|
||||
.bind(entry_id)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
|
@ -107,17 +115,24 @@ mod patch {
|
|||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
if entry.value != new_entry.value {
|
||||
sqlx::query!("delete from journal_mentions where entry_id = $1", entry_id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
if old_entry.value != new_entry.value {
|
||||
sqlx::query!(
|
||||
"delete from mentions where entity_id = $1 and entity_type = 'journal_entry'",
|
||||
entry_id
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
new_entry
|
||||
.insert_mentions(state.contact_search(&user), pool)
|
||||
.await?;
|
||||
let mentions = {
|
||||
let switchboard = sw_lock.read().unwrap();
|
||||
switchboard.extract_mentions(&new_entry)
|
||||
};
|
||||
insert_mentions(&mentions, pool).await?;
|
||||
}
|
||||
|
||||
Ok(new_entry.to_html(pool).await?)
|
||||
Ok(Into::<MentionHost>::into(&new_entry)
|
||||
.format_pool(pool)
|
||||
.await?)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -132,14 +147,11 @@ mod delete {
|
|||
let user = auth_session.user.unwrap();
|
||||
let pool = &state.db(&user).pool;
|
||||
|
||||
sqlx::query(
|
||||
"delete from journal_mentions where entry_id = $1;
|
||||
delete from journal_entries where id = $2 returning id,date,value",
|
||||
)
|
||||
.bind(entry_id)
|
||||
.bind(entry_id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
sqlx::query("delete from journal_entries where id = $2 returning id,date,value")
|
||||
.bind(entry_id)
|
||||
.bind(entry_id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue