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
147
src/switchboard.rs
Normal file
147
src/switchboard.rs
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
use maud::{Markup, PreEscaped};
|
||||
use regex::Regex;
|
||||
use sqlx::QueryBuilder;
|
||||
use sqlx::sqlite::SqlitePool;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::AppError;
|
||||
use crate::db::DbId;
|
||||
|
||||
pub struct Switchboard {
|
||||
trie: radix_trie::Trie<String, String>,
|
||||
}
|
||||
|
||||
struct Mentionable {
|
||||
text: String,
|
||||
uri: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Mention {
|
||||
pub entity_id: DbId,
|
||||
pub entity_type: DbId,
|
||||
pub url: String,
|
||||
pub input_text: String,
|
||||
pub byte_range_start: DbId,
|
||||
pub byte_range_end: DbId,
|
||||
}
|
||||
|
||||
// must match the constants in trigger definitions in
|
||||
// migrations/each_user/0010_more-mentions.sql (or future migrations)
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum MentionHostType {
|
||||
JournalEntry,
|
||||
ContactTextBody,
|
||||
ContactLivesWith,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct MentionHost<'a> {
|
||||
pub entity_id: DbId,
|
||||
pub entity_type: DbId,
|
||||
pub input: &'a String,
|
||||
}
|
||||
|
||||
impl MentionHost<'_> {
|
||||
pub fn format<'a>(
|
||||
self: &Self,
|
||||
mentions: impl IntoIterator<Item = &'a Mention>,
|
||||
) -> Result<Markup, AppError> {
|
||||
let mut out = self.input.clone();
|
||||
for mention in mentions.into_iter() {
|
||||
out.replace_range(
|
||||
(mention.byte_range_start as usize)..(mention.byte_range_end as usize),
|
||||
&format!("[{}]({})", mention.input_text, mention.url),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(PreEscaped(markdown::to_html(&out)))
|
||||
}
|
||||
pub async fn format_pool(self: &Self, pool: &SqlitePool) -> Result<Markup, AppError> {
|
||||
let mentions = sqlx::query_as!(
|
||||
Mention,
|
||||
"select * from mentions
|
||||
where entity_id = $1 and entity_type = $2
|
||||
order by byte_range_start desc",
|
||||
self.entity_id,
|
||||
self.entity_type
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
self.format(&mentions)
|
||||
}
|
||||
}
|
||||
|
||||
impl Switchboard {
|
||||
pub async fn new(pool: &SqlitePool) -> Result<Self, AppError> {
|
||||
let mut trie = radix_trie::Trie::new();
|
||||
|
||||
let mentionables = sqlx::query_as!(
|
||||
Mentionable,
|
||||
"select name as text, '/contact/'||contact_id as uri from (
|
||||
select contact_id, name, count(name) as ct from names group by name
|
||||
) where ct = 1
|
||||
union
|
||||
select distinct name as text, '/group/'||slug as uri from groups",
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
for mentionable in mentionables {
|
||||
trie.insert(mentionable.text, mentionable.uri);
|
||||
}
|
||||
|
||||
Ok(Switchboard { trie })
|
||||
}
|
||||
|
||||
pub fn remove(self: &mut Self, text: &String) {
|
||||
self.trie.remove(text);
|
||||
}
|
||||
|
||||
pub fn add_mentionable(self: &mut Self, text: String, uri: String) {
|
||||
self.trie.insert(text, uri);
|
||||
}
|
||||
|
||||
pub fn extract_mentions<'a>(&self, host: impl Into<MentionHost<'a>>) -> HashSet<Mention> {
|
||||
let host: MentionHost = host.into();
|
||||
let name_re = Regex::new(r"\[\[(.+?)\]\]").unwrap();
|
||||
name_re
|
||||
.captures_iter(host.input)
|
||||
.map(|caps| {
|
||||
let range = caps.get_match().range();
|
||||
self.trie.get(&caps[1]).map(|url| Mention {
|
||||
entity_id: host.entity_id,
|
||||
entity_type: host.entity_type,
|
||||
url: url.to_string(),
|
||||
input_text: caps[1].to_string(),
|
||||
byte_range_start: DbId::try_from(range.start).unwrap(),
|
||||
byte_range_end: DbId::try_from(range.end).unwrap(),
|
||||
})
|
||||
})
|
||||
.filter(|o| o.is_some())
|
||||
.map(|o| o.unwrap())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn insert_mentions<'a>(
|
||||
mentions: impl IntoIterator<Item = &'a Mention>,
|
||||
pool: &SqlitePool,
|
||||
) -> Result<(), AppError> {
|
||||
let mut qb = QueryBuilder::<sqlx::Sqlite>::new(
|
||||
"insert into mentions (
|
||||
entity_id, entity_type, url, input_text,
|
||||
byte_range_start, byte_range_end) ",
|
||||
);
|
||||
qb.push_values(mentions, |mut b, mention| {
|
||||
b.push_bind(mention.entity_id)
|
||||
.push_bind(mention.entity_type)
|
||||
.push_bind(&mention.url)
|
||||
.push_bind(&mention.input_text)
|
||||
.push_bind(mention.byte_range_start)
|
||||
.push_bind(mention.byte_range_end);
|
||||
});
|
||||
qb.build().execute(pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue