1use std::iter::once;
2
3use axum::extract::State;
4use futures::{
5 FutureExt, StreamExt, TryFutureExt,
6 future::try_join3,
7 stream::{select_all, unfold},
8};
9use ruma::{
10 EventId, RoomId, UInt, UserId,
11 api::{
12 Direction,
13 client::relations::{
14 get_relating_events, get_relating_events_with_rel_type,
15 get_relating_events_with_rel_type_and_event_type,
16 },
17 },
18 events::{TimelineEventType, relation::RelationType},
19};
20use tuwunel_core::{
21 Err, Error, Result, at, err,
22 matrix::{
23 event::{Event, RelationTypeEqual},
24 pdu::{PduCount, PduId},
25 },
26 utils::{
27 BoolExt,
28 math::usize_from_ruma_bounded,
29 result::FlatOk,
30 stream::{ReadyExt, WidebandExt},
31 },
32};
33use tuwunel_service::Services;
34
35use crate::{Ruma, client::is_ignored_pdu};
36
37pub(crate) async fn get_relating_events_with_rel_type_and_event_type_route(
39 State(services): State<crate::State>,
40 body: Ruma<get_relating_events_with_rel_type_and_event_type::v1::Request>,
41) -> Result<get_relating_events_with_rel_type_and_event_type::v1::Response> {
42 paginate_relations_with_filter(
43 &services,
44 body.sender_user(),
45 &body.room_id,
46 &body.event_id,
47 body.event_type.clone().into(),
48 body.rel_type.clone().into(),
49 body.from.as_deref(),
50 body.to.as_deref(),
51 body.limit,
52 body.recurse,
53 body.dir,
54 )
55 .await
56 .map(|res| get_relating_events_with_rel_type_and_event_type::v1::Response {
57 chunk: res.chunk,
58 next_batch: res.next_batch,
59 prev_batch: res.prev_batch,
60 recursion_depth: res.recursion_depth,
61 })
62}
63
64pub(crate) async fn get_relating_events_with_rel_type_route(
66 State(services): State<crate::State>,
67 body: Ruma<get_relating_events_with_rel_type::v1::Request>,
68) -> Result<get_relating_events_with_rel_type::v1::Response> {
69 paginate_relations_with_filter(
70 &services,
71 body.sender_user(),
72 &body.room_id,
73 &body.event_id,
74 None,
75 body.rel_type.clone().into(),
76 body.from.as_deref(),
77 body.to.as_deref(),
78 body.limit,
79 body.recurse,
80 body.dir,
81 )
82 .await
83 .map(|res| get_relating_events_with_rel_type::v1::Response {
84 chunk: res.chunk,
85 next_batch: res.next_batch,
86 prev_batch: res.prev_batch,
87 recursion_depth: res.recursion_depth,
88 })
89}
90
91pub(crate) async fn get_relating_events_route(
93 State(services): State<crate::State>,
94 body: Ruma<get_relating_events::v1::Request>,
95) -> Result<get_relating_events::v1::Response> {
96 paginate_relations_with_filter(
97 &services,
98 body.sender_user(),
99 &body.room_id,
100 &body.event_id,
101 None,
102 None,
103 body.from.as_deref(),
104 body.to.as_deref(),
105 body.limit,
106 body.recurse,
107 body.dir,
108 )
109 .await
110}
111
112#[expect(clippy::too_many_arguments)]
113#[tracing::instrument(
114 name = "relations",
115 level = "debug",
116 skip_all,
117 fields(room_id, target, from, to, dir, limit, recurse)
118)]
119async fn paginate_relations_with_filter(
120 services: &Services,
121 sender_user: &UserId,
122 room_id: &RoomId,
123 target: &EventId,
124 filter_event_type: Option<TimelineEventType>,
125 filter_rel_type: Option<RelationType>,
126 from: Option<&str>,
127 to: Option<&str>,
128 limit: Option<UInt>,
129 recurse: bool,
130 dir: Direction,
131) -> Result<get_relating_events::v1::Response> {
132 let from: Option<PduCount> = from.map(str::parse).transpose()?;
133
134 let to: Option<PduCount> = to.map(str::parse).flat_ok();
135
136 let max_depth: usize = if recurse { 3 } else { 0 };
138
139 let limit = limit.map_or(30, |limit| usize_from_ruma_bounded(limit, 30, 100));
140
141 let target_event_id: &EventId = target;
142
143 let target = services
144 .timeline
145 .get_pdu_id(target)
146 .map_ok(PduId::from)
147 .map_ok(Ok::<_, Error>);
148
149 let visible = services
150 .state_accessor
151 .user_can_see_state_events(sender_user, room_id)
152 .map(|visible| {
153 visible
154 .into_option()
155 .ok_or_else(|| err!(Request(Forbidden("You cannot view this room."))))
156 });
157
158 let shortroomid = services.short.get_shortroomid(room_id);
159
160 let (shortroomid, target, ()) = try_join3(shortroomid, target, visible).await?;
161
162 let Ok(target) = target else {
163 return Ok(get_relating_events::v1::Response::new(Vec::new()));
164 };
165
166 if shortroomid != target.shortroomid {
167 return Err!(Request(NotFound("Event not found in room.")));
168 }
169
170 if let PduCount::Backfilled(_) = target.count {
171 return Ok(get_relating_events::v1::Response::new(Vec::new()));
172 }
173
174 if let Ok(target_pdu) = services.timeline.get_pdu(target_event_id).await
175 && is_ignored_pdu(services, &target_pdu, sender_user).await
176 {
177 return Err!(HttpJson(NOT_FOUND, {
178 "errcode": "M_SENDER_IGNORED",
179 "error": "You have ignored the user that sent this event",
180 "sender": target_pdu.sender().as_str(),
181 }));
182 }
183
184 let fetch = |depth: usize, count: PduCount| {
185 services
186 .pdu_metadata
187 .get_relations(shortroomid, count, from, dir, Some(sender_user))
188 .map(move |(count, pdu)| (depth, count, pdu))
189 .ready_filter(|(_, count, _)| matches!(count, PduCount::Normal(_)))
190 .boxed()
191 };
192
193 let events = unfold(select_all(once(fetch(0, target.count))), async |mut relations| {
194 let (depth, count, pdu) = relations.next().await?;
195
196 if depth < max_depth {
197 relations.push(fetch(depth.saturating_add(1), count));
198 }
199
200 Some(((depth, count, pdu), relations))
201 })
202 .ready_take_while(|&(_, count, _)| Some(count) != to)
203 .ready_filter(|(_, _, pdu)| {
204 filter_event_type
205 .as_ref()
206 .is_none_or(|kind| kind == pdu.kind())
207 })
208 .ready_filter(|(_, _, pdu)| {
209 filter_rel_type
210 .as_ref()
211 .is_none_or(|rel_type| rel_type.relation_type_equal(pdu))
212 })
213 .wide_filter_map(async |(depth, count, pdu)| {
214 services
215 .state_accessor
216 .user_can_see_event(sender_user, &pdu)
217 .await
218 .then_some((depth, count, pdu))
219 })
220 .take(limit)
221 .wide_then(async |(depth, count, pdu)| {
222 let pdu = services
223 .pdu_metadata
224 .bundle_aggregations(sender_user, pdu)
225 .await;
226
227 (depth, count, pdu)
228 })
229 .collect::<Vec<_>>()
230 .await;
231
232 Ok(get_relating_events::v1::Response {
233 recursion_depth: max_depth
234 .gt(&0)
235 .then(|| events.iter().map(at!(0)))
236 .into_iter()
237 .flatten()
238 .max()
239 .map(TryInto::try_into)
240 .transpose()?,
241
242 next_batch: events
243 .last()
244 .map(at!(1))
245 .as_ref()
246 .map(ToString::to_string),
247
248 prev_batch: events
249 .first()
250 .map(at!(1))
251 .or(from)
252 .as_ref()
253 .map(ToString::to_string),
254
255 chunk: events
256 .into_iter()
257 .map(at!(2))
258 .map(Event::into_format)
259 .collect(),
260 })
261}