From 8586d747d12cb32cd9caec5cae2058691070cb87 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Thu, 18 Dec 2025 23:19:13 +0000 Subject: [PATCH] feat: Run visibility checks on bundled relations --- src/api/client/threads.rs | 2 -- .../pdu_metadata/bundled_aggregations.rs | 35 +++++++++++++------ src/service/rooms/pdu_metadata/mod.rs | 3 ++ 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/api/client/threads.rs b/src/api/client/threads.rs index 32da2adb..86c3a399 100644 --- a/src/api/client/threads.rs +++ b/src/api/client/threads.rs @@ -31,8 +31,6 @@ pub(crate) async fn get_threads_route( .transpose()? .unwrap_or_else(PduCount::max); - // TODO: user_can_see_event and set_unsigned should be at the same level / - // function, so unsigned is only set for seen events. let threads: Vec<(PduCount, PduEvent)> = services .rooms .threads diff --git a/src/service/rooms/pdu_metadata/bundled_aggregations.rs b/src/service/rooms/pdu_metadata/bundled_aggregations.rs index 53c651ac..cf780df5 100644 --- a/src/service/rooms/pdu_metadata/bundled_aggregations.rs +++ b/src/service/rooms/pdu_metadata/bundled_aggregations.rs @@ -41,9 +41,6 @@ impl super::Service { // The relations database code still handles the basic unsigned data // We don't want to recursively fetch relations - // TODO: Event visibility check - // TODO: ignored users? - if relations.is_empty() { return Ok(None); } @@ -84,8 +81,17 @@ impl super::Service { // Handle m.replace relations - find the most recent valid one (lazy load // original event) if !replace_events.is_empty() { - if let Some(replacement) = - Self::find_most_recent_valid_replacement(pdu, &replace_events).await? + if let Some(replacement) = Self::find_most_recent_valid_replacement( + pdu, + &replace_events, + async |pdu: &PduEvent| { + self.services + .state_accessor + .user_can_see_event(user_id, &pdu.room_id_or_hash(), &pdu.event_id()) + .await + }, + ) + .await? { bundled.replace = Some(Self::serialize_replacement(replacement)?); } @@ -124,6 +130,7 @@ impl super::Service { async fn find_most_recent_valid_replacement<'a>( original_event: &PduEvent, replacement_events: &[&'a PdusIterItem], + visible: impl AsyncFn(&PduEvent) -> bool, ) -> Result> { // Filter valid replacements and find the maximum in a single pass let mut result: Option<&PduEvent> = None; @@ -136,17 +143,23 @@ impl super::Service { continue; } - result = Some(match result { - | None => pdu, + let next = match result { + | None => Some(pdu), | Some(current) => { // Compare by origin_server_ts first, then event_id lexicographically match pdu.origin_server_ts().cmp(¤t.origin_server_ts()) { - | std::cmp::Ordering::Greater => pdu, - | std::cmp::Ordering::Equal if pdu.event_id() > current.event_id() => pdu, - | _ => current, + | std::cmp::Ordering::Greater => Some(pdu), + | std::cmp::Ordering::Equal if pdu.event_id() > current.event_id() => + Some(pdu), + | _ => None, } }, - }); + }; + if let Some(pdu) = next + && visible(pdu).await + { + result = Some(pdu); + } } Ok(result) diff --git a/src/service/rooms/pdu_metadata/mod.rs b/src/service/rooms/pdu_metadata/mod.rs index e1f351a3..9bbc7c9c 100644 --- a/src/service/rooms/pdu_metadata/mod.rs +++ b/src/service/rooms/pdu_metadata/mod.rs @@ -20,6 +20,7 @@ pub struct Service { struct Services { short: Dep, timeline: Dep, + state_accessor: Dep, } impl crate::Service for Service { @@ -28,6 +29,8 @@ impl crate::Service for Service { services: Services { short: args.depend::("rooms::short"), timeline: args.depend::("rooms::timeline"), + state_accessor: args + .depend::("rooms::state_accessor"), }, db: Data::new(&args), }))