tuwunel_api/client/admin/rooms/
forward_extremities.rs1use axum::extract::State;
2use futures::{
3 StreamExt,
4 future::{join, join_all},
5};
6use ruma::{EventId, OwnedEventId, UInt};
7use synapse_admin_api::rooms::forward_extremities::{
8 delete::{Request as DeleteRequest, Response as DeleteResponse},
9 get::{ForwardExtremity, Request as GetRequest, Response as GetResponse},
10};
11use tuwunel_core::{Result, smallvec::SmallVec};
12
13use crate::{Ruma, client::admin::require_admin};
14
15type Extremities = SmallVec<[OwnedEventId; 1]>;
16
17pub(crate) async fn admin_get_forward_extremities_route(
23 State(services): State<crate::State>,
24 body: Ruma<GetRequest>,
25) -> Result<GetResponse> {
26 require_admin(&services, body.sender_user()).await?;
27
28 let (room_id, _) = services
29 .alias
30 .maybe_resolve_with_servers(&body.room_id_or_alias, None)
31 .await?;
32
33 let extremities = services
34 .state
35 .get_forward_extremities(&room_id)
36 .map(ToOwned::to_owned)
37 .collect::<Extremities>()
38 .await;
39
40 let results = join_all(
41 extremities
42 .iter()
43 .map(|event_id| forward_extremity(&services, event_id)),
44 )
45 .await;
46
47 Ok(GetResponse {
48 count: super::usize_to_uint(results.len()),
49 results,
50 })
51}
52
53pub(crate) async fn admin_delete_forward_extremities_route(
58 State(services): State<crate::State>,
59 body: Ruma<DeleteRequest>,
60) -> Result<DeleteResponse> {
61 require_admin(&services, body.sender_user()).await?;
62
63 let (room_id, _) = services
64 .alias
65 .maybe_resolve_with_servers(&body.room_id_or_alias, None)
66 .await?;
67
68 let state_lock = services.state.mutex.lock(&room_id).await;
69
70 let deleted = services
71 .state
72 .collapse_forward_extremities(&room_id, &state_lock)
73 .await;
74
75 Ok(DeleteResponse { deleted: super::usize_to_uint(deleted) })
76}
77
78async fn forward_extremity(services: &crate::State, event_id: &EventId) -> ForwardExtremity {
79 let (pdu, shortstatehash) =
80 join(services.timeline.get_pdu(event_id), services.state.pdu_shortstatehash(event_id))
81 .await;
82
83 let (depth, received_ts) = pdu
84 .map(|pdu| (pdu.depth, pdu.origin_server_ts))
85 .unwrap_or_default();
86
87 forward_extremity_row(event_id, depth, received_ts, shortstatehash.ok())
88}
89
90fn forward_extremity_row(
93 event_id: &EventId,
94 depth: UInt,
95 received_ts: UInt,
96 shortstatehash: Option<u64>,
97) -> ForwardExtremity {
98 ForwardExtremity {
99 event_id: event_id.to_owned(),
100 state_group: shortstatehash.and_then(|hash| UInt::try_from(hash).ok()),
101 depth,
102 received_ts,
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use ruma::{event_id, uint};
109 use serde_json::json;
110
111 use super::forward_extremity_row;
112
113 #[test]
114 fn state_group_is_the_state_hash_when_it_fits() {
115 let row =
116 forward_extremity_row(event_id!("$abc:example.org"), uint!(7), uint!(1000), Some(42));
117
118 let value = serde_json::to_value(row).unwrap();
119
120 assert_eq!(value["state_group"], json!(42));
121 assert_eq!(value["depth"], json!(7));
122 assert_eq!(value["received_ts"], json!(1000));
123 }
124
125 #[test]
126 fn state_group_is_null_when_the_hash_overflows_uint() {
127 let row = forward_extremity_row(
128 event_id!("$abc:example.org"),
129 uint!(0),
130 uint!(0),
131 Some(u64::MAX),
132 );
133
134 let value = serde_json::to_value(row).unwrap();
135
136 assert_eq!(value["state_group"], json!(null));
137 }
138}