notes to self
This commit is contained in:
@@ -785,11 +785,15 @@ pub async fn message_by_id(db: &Db, user: &str, id: &str) -> Result<Option<Messa
|
||||
Ok(on!(db, p => sqlx::query_as::<_, Message>(sql).bind(user).bind(id).fetch_optional(p).await?))
|
||||
}
|
||||
|
||||
/// Messages exchanged with `jid` (bound three times). With our own JID, only
|
||||
/// the notes sent to ourself, not everything we sent or received.
|
||||
const WITH_JID: &str = "(jidfrom = ? OR jidto = ?) AND (jidfrom = jidto OR ? != user_id)";
|
||||
|
||||
/// Resolve a message referenced by another one (reply, reaction, retraction, receipt)
|
||||
/// from the conversation `jid`, by its message id, origin id or stanza id.
|
||||
pub async fn message_resolve_reference(db: &Db, user: &str, jid: &str, reference: &str) -> Result<Option<Message>> {
|
||||
let sql = db.sql(&format!(
|
||||
"SELECT {MESSAGE_COLUMNS} FROM messages WHERE user_id = ? AND (jidfrom = ? OR jidto = ?)
|
||||
"SELECT {MESSAGE_COLUMNS} FROM messages WHERE user_id = ? AND {WITH_JID}
|
||||
AND (messageid = ? OR originid = ? OR stanzaid = ? OR id = ?) ORDER BY mid DESC LIMIT 1"
|
||||
));
|
||||
Ok(on!(db, p => {
|
||||
@@ -797,6 +801,7 @@ pub async fn message_resolve_reference(db: &Db, user: &str, jid: &str, reference
|
||||
.bind(user)
|
||||
.bind(jid)
|
||||
.bind(jid)
|
||||
.bind(jid)
|
||||
.bind(reference)
|
||||
.bind(reference)
|
||||
.bind(reference)
|
||||
@@ -903,7 +908,7 @@ pub async fn messages_for(
|
||||
let types = if muc { MESSAGE_TYPE_MUC } else { MESSAGE_TYPE };
|
||||
let types_sql = types.iter().map(|t| format!("'{t}'")).collect::<Vec<_>>().join(",");
|
||||
let mut sql = format!(
|
||||
"SELECT {MESSAGE_COLUMNS} FROM messages WHERE user_id = ? AND (jidfrom = ? OR jidto = ?) AND type IN ({types_sql})"
|
||||
"SELECT {MESSAGE_COLUMNS} FROM messages WHERE user_id = ? AND {WITH_JID} AND type IN ({types_sql})"
|
||||
);
|
||||
if muc {
|
||||
sql.push_str(" AND subject IS NULL");
|
||||
@@ -919,7 +924,7 @@ pub async fn messages_for(
|
||||
|
||||
let sql = db.sql(&sql);
|
||||
Ok(on!(db, p => {
|
||||
let mut q = sqlx::query_as::<_, Message>(sql).bind(user).bind(jid).bind(jid);
|
||||
let mut q = sqlx::query_as::<_, Message>(sql).bind(user).bind(jid).bind(jid).bind(jid);
|
||||
if let Some(b) = before {
|
||||
q = q.bind(b);
|
||||
}
|
||||
@@ -931,8 +936,8 @@ pub async fn messages_for(
|
||||
}
|
||||
|
||||
pub async fn messages_count(db: &Db, user: &str, jid: &str) -> Result<i64> {
|
||||
let sql = db.sql("SELECT COUNT(*) FROM messages WHERE user_id = ? AND (jidfrom = ? OR jidto = ?)");
|
||||
Ok(on!(db, p => sqlx::query_scalar(sql).bind(user).bind(jid).bind(jid).fetch_one(p).await?))
|
||||
let sql = db.sql(&format!("SELECT COUNT(*) FROM messages WHERE user_id = ? AND {WITH_JID}"));
|
||||
Ok(on!(db, p => sqlx::query_scalar(sql).bind(user).bind(jid).bind(jid).bind(jid).fetch_one(p).await?))
|
||||
}
|
||||
|
||||
/// Clear the local history of a conversation. The archive is marked as fully
|
||||
@@ -942,11 +947,11 @@ pub async fn messages_clear(db: &Db, user: &str, jid: &str, archive_key: &str) -
|
||||
on!(db, p => {
|
||||
let mut tx = p.begin().await?;
|
||||
for sql in [
|
||||
"DELETE FROM reactions WHERE message_mid IN (SELECT mid FROM messages WHERE user_id = ? AND (jidfrom = ? OR jidto = ?))",
|
||||
"DELETE FROM message_files WHERE message_mid IN (SELECT mid FROM messages WHERE user_id = ? AND (jidfrom = ? OR jidto = ?))",
|
||||
"DELETE FROM messages WHERE user_id = ? AND (jidfrom = ? OR jidto = ?)",
|
||||
format!("DELETE FROM reactions WHERE message_mid IN (SELECT mid FROM messages WHERE user_id = ? AND {WITH_JID})"),
|
||||
format!("DELETE FROM message_files WHERE message_mid IN (SELECT mid FROM messages WHERE user_id = ? AND {WITH_JID})"),
|
||||
format!("DELETE FROM messages WHERE user_id = ? AND {WITH_JID}"),
|
||||
] {
|
||||
sqlx::query(db.sql(sql)).bind(user).bind(jid).bind(jid).execute(&mut *tx).await?;
|
||||
sqlx::query(db.sql(&sql)).bind(user).bind(jid).bind(jid).bind(jid).execute(&mut *tx).await?;
|
||||
}
|
||||
sqlx::query(db.sql(
|
||||
"INSERT INTO mam_earliests (user_id, jid, earliest) VALUES (?, ?, NULL)
|
||||
@@ -1194,8 +1199,8 @@ pub async fn mam_earliest_set(db: &Db, user: &str, jid: &str, earliest: Option<&
|
||||
}
|
||||
|
||||
pub async fn earliest_message_date(db: &Db, user: &str, jid: &str) -> Result<Option<String>> {
|
||||
let sql = db.sql("SELECT MIN(published) FROM messages WHERE user_id = ? AND (jidfrom = ? OR jidto = ?)");
|
||||
Ok(on!(db, p => sqlx::query_scalar(sql).bind(user).bind(jid).bind(jid).fetch_one(p).await?))
|
||||
let sql = db.sql(&format!("SELECT MIN(published) FROM messages WHERE user_id = ? AND {WITH_JID}"));
|
||||
Ok(on!(db, p => sqlx::query_scalar(sql).bind(user).bind(jid).bind(jid).bind(jid).fetch_one(p).await?))
|
||||
}
|
||||
|
||||
pub async fn latest_message_date(db: &Db, user: &str) -> Result<Option<String>> {
|
||||
@@ -1305,10 +1310,10 @@ pub async fn messages_search(
|
||||
let words: Vec<String> = crate::utils::search_normalize(keywords).split_whitespace().take(8).map(String::from).collect();
|
||||
|
||||
let sql = db.sql(&format!(
|
||||
"SELECT mid, body FROM messages WHERE user_id = ? AND (jidfrom = ? OR jidto = ?) AND type IN ({types_sql})
|
||||
"SELECT mid, body FROM messages WHERE user_id = ? AND {WITH_JID} AND type IN ({types_sql})
|
||||
AND encrypted = FALSE AND retracted = FALSE AND body IS NOT NULL AND {NOT_REPLACED} ORDER BY published DESC"
|
||||
));
|
||||
let rows: Vec<(i64, String)> = on!(db, p => sqlx::query_as(sql).bind(user).bind(jid).bind(jid).fetch_all(p).await?);
|
||||
let rows: Vec<(i64, String)> = on!(db, p => sqlx::query_as(sql).bind(user).bind(jid).bind(jid).bind(jid).fetch_all(p).await?);
|
||||
let mut mids: Vec<i64> = rows
|
||||
.into_iter()
|
||||
.filter(|(_, body)| {
|
||||
@@ -1325,11 +1330,11 @@ pub async fn messages_search(
|
||||
|
||||
let placeholders = vec!["?"; mids.len()].join(",");
|
||||
let sql = db.sql(&format!(
|
||||
"SELECT {MESSAGE_COLUMNS} FROM messages WHERE user_id = ? AND (jidfrom = ? OR jidto = ?) AND type IN ({types_sql})
|
||||
"SELECT {MESSAGE_COLUMNS} FROM messages WHERE user_id = ? AND {WITH_JID} AND type IN ({types_sql})
|
||||
AND retracted = FALSE AND mid IN ({placeholders}) ORDER BY published DESC LIMIT ?"
|
||||
));
|
||||
Ok(on!(db, p => {
|
||||
let mut q = sqlx::query_as::<_, Message>(sql).bind(user).bind(jid).bind(jid);
|
||||
let mut q = sqlx::query_as::<_, Message>(sql).bind(user).bind(jid).bind(jid).bind(jid);
|
||||
for mid in &mids {
|
||||
q = q.bind(mid);
|
||||
}
|
||||
@@ -1349,10 +1354,10 @@ const NOT_REPLACED: &str = "NOT EXISTS (SELECT 1 FROM messages r WHERE r.user_id
|
||||
pub async fn encrypted_index(db: &Db, user: &str, jid: &str, muc: bool) -> Result<Vec<(i64, String, Option<String>, String)>> {
|
||||
let types_sql = conversation_types(muc);
|
||||
let sql = db.sql(&format!(
|
||||
"SELECT mid, id, messageid, published FROM messages WHERE user_id = ? AND (jidfrom = ? OR jidto = ?)
|
||||
"SELECT mid, id, messageid, published FROM messages WHERE user_id = ? AND {WITH_JID}
|
||||
AND type IN ({types_sql}) AND encrypted = TRUE AND retracted = FALSE AND {NOT_REPLACED} ORDER BY published DESC LIMIT 5000"
|
||||
));
|
||||
Ok(on!(db, p => sqlx::query_as(sql).bind(user).bind(jid).bind(jid).fetch_all(p).await?))
|
||||
Ok(on!(db, p => sqlx::query_as(sql).bind(user).bind(jid).bind(jid).bind(jid).fetch_all(p).await?))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromRow, Serialize)]
|
||||
@@ -1372,10 +1377,10 @@ pub async fn conversation_files(db: &Db, user: &str, jid: &str, muc: bool, limit
|
||||
let types_sql = conversation_types(muc);
|
||||
let sql = db.sql(&format!(
|
||||
"SELECT m.mid, f.name, f.type, f.size, f.url, m.published FROM message_files f JOIN messages m ON m.mid = f.message_mid
|
||||
WHERE m.user_id = ? AND (m.jidfrom = ? OR m.jidto = ?) AND m.type IN ({types_sql}) AND m.retracted = FALSE
|
||||
WHERE m.user_id = ? AND (m.jidfrom = ? OR m.jidto = ?) AND (m.jidfrom = m.jidto OR ? != m.user_id) AND m.type IN ({types_sql}) AND m.retracted = FALSE
|
||||
ORDER BY m.published DESC LIMIT ?"
|
||||
));
|
||||
Ok(on!(db, p => sqlx::query_as(sql).bind(user).bind(jid).bind(jid).bind(limit).fetch_all(p).await?))
|
||||
Ok(on!(db, p => sqlx::query_as(sql).bind(user).bind(jid).bind(jid).bind(jid).bind(limit).fetch_all(p).await?))
|
||||
}
|
||||
|
||||
/// Forget everything stored for an account
|
||||
@@ -1488,6 +1493,50 @@ pub mod tests {
|
||||
db.close().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn notes_to_self_sqlite() {
|
||||
let (db, path) = sqlite_test_db("notes").await;
|
||||
notes_to_self(&db).await;
|
||||
db.close().await;
|
||||
remove_sqlite(&path);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn notes_to_self_postgres() {
|
||||
let Some(url) = postgres_test_url("notes").await else { return };
|
||||
let db = connect(&url).await.unwrap();
|
||||
notes_to_self(&db).await;
|
||||
db.close().await;
|
||||
}
|
||||
|
||||
/// The conversation with our own JID only holds the notes, not every message
|
||||
async fn notes_to_self(db: &Db) {
|
||||
let (me, bob) = ("me@example.org", "bob@example.org");
|
||||
let m = Message {
|
||||
user_id: me.into(),
|
||||
id: "n1".into(),
|
||||
jidfrom: bob.into(),
|
||||
jidto: me.into(),
|
||||
type_: "chat".into(),
|
||||
body: Some("hi".into()),
|
||||
published: "2026-01-01 10:00:00".into(),
|
||||
..Default::default()
|
||||
};
|
||||
message_save(db, &m).await.unwrap();
|
||||
message_save(db, &Message { id: "n2".into(), jidfrom: me.into(), jidto: bob.into(), ..m.clone() }).await.unwrap();
|
||||
message_save(db, &Message { id: "n3".into(), jidfrom: me.into(), jidto: me.into(), body: Some("note".into()), ..m.clone() })
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(messages_count(db, me, bob).await.unwrap(), 2);
|
||||
let notes = messages_for(db, me, me, false, None, None, 10).await.unwrap();
|
||||
assert_eq!(notes.iter().map(|n| n.id.as_str()).collect::<Vec<_>>(), ["n3"]);
|
||||
|
||||
messages_clear(db, me, me, me).await.unwrap();
|
||||
assert_eq!(messages_count(db, me, me).await.unwrap(), 0);
|
||||
assert_eq!(messages_count(db, me, bob).await.unwrap(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn postgres_placeholders() {
|
||||
assert_eq!(to_postgres("SELECT a FROM t WHERE b = ? AND c LIKE 'x?%' AND d IN (?, ?)"), "SELECT a FROM t WHERE b = $1 AND c LIKE 'x?%' AND d IN ($2, $3)");
|
||||
|
||||
+1
-2
@@ -230,8 +230,7 @@ async fn prepare_empty(ctx: &Ctx) -> String {
|
||||
|
||||
/// Open a 1:1 discussion (or the empty view)
|
||||
pub async fn get(ctx: &Ctx, jid: Option<&str>, light: bool) {
|
||||
let me = ctx.jid();
|
||||
match jid.filter(|j| !j.is_empty() && *j != me) {
|
||||
match jid.filter(|j| !j.is_empty()) {
|
||||
None => {
|
||||
rpc!(ctx, "MovimTpl.hidePanel");
|
||||
rpc!(ctx, "Notif.current", "chat");
|
||||
|
||||
@@ -73,7 +73,7 @@ pub async fn prepare_chats(ctx: &Ctx) -> String {
|
||||
let chats = db::open_chats(ctx.db(), &me).await.unwrap_or_default();
|
||||
let mut html = String::new();
|
||||
for jid in chats.iter().rev() {
|
||||
if utils::validate_jid(jid) && *jid != me {
|
||||
if utils::validate_jid(jid) {
|
||||
html.push_str(&prepare_chat(ctx, jid).await);
|
||||
}
|
||||
}
|
||||
@@ -168,7 +168,7 @@ pub async fn refresh_item(ctx: &Ctx, jid: &str) {
|
||||
|
||||
pub async fn open(ctx: &Ctx, jid: &str, and_show: bool, history: bool) {
|
||||
let me = ctx.jid();
|
||||
if !utils::validate_jid(jid) || jid == me {
|
||||
if !utils::validate_jid(jid) {
|
||||
return;
|
||||
}
|
||||
if history {
|
||||
|
||||
@@ -23,8 +23,7 @@ pub async fn call(ctx: &Ctx, widget: &str, func: &str, p: &P<'_>) -> bool {
|
||||
("Search", "ajaxSearch") => {
|
||||
let key = p.str(0).replace("xmpp:", "").trim().to_string();
|
||||
let key = key.split('?').next().unwrap_or("").to_string();
|
||||
let me = ctx.jid();
|
||||
let html = if utils::validate_bare_jid(&key) && key.contains('@') && key != me {
|
||||
let html = if utils::validate_bare_jid(&key) && key.contains('@') {
|
||||
let picture = ctx.placeholder_url(&key);
|
||||
ctx.render("Search/_search_results.html", json!({ "jid": key, "picture": picture }))
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user