From 9147534cf372cd92021e66d512b0ca443275b7d6 Mon Sep 17 00:00:00 2001 From: mikoto <avdb@keemail.me> Date: Tue, 30 Jan 2024 14:29:54 +0000 Subject: [PATCH] chore: fix room endpoints for Admin API --- crates/matrix/src/admin/resources/mod.rs | 1 + crates/matrix/src/admin/resources/room.rs | 357 ++++++++++++++++++++++ crates/matrix/src/http.rs | 14 + crates/matrix/src/lib.rs | 18 +- 4 files changed, 384 insertions(+), 6 deletions(-) create mode 100644 crates/matrix/src/admin/resources/room.rs diff --git a/crates/matrix/src/admin/resources/mod.rs b/crates/matrix/src/admin/resources/mod.rs index d478e99..cb201a4 100644 --- a/crates/matrix/src/admin/resources/mod.rs +++ b/crates/matrix/src/admin/resources/mod.rs @@ -1,3 +1,4 @@ +pub mod room; pub mod token; pub mod user; pub mod user_id; diff --git a/crates/matrix/src/admin/resources/room.rs b/crates/matrix/src/admin/resources/room.rs new file mode 100644 index 0000000..a7b5a5e --- /dev/null +++ b/crates/matrix/src/admin/resources/room.rs @@ -0,0 +1,357 @@ +//! [Room Admin API](https://matrix-org.github.io/synapse/latest/admin_api/rooms.html) +//! +//! To use it, you will need to authenticate by providing an `access_token` +//! for a server admin: see Admin API. + +use anyhow::Result; +use ruma::{events::{AnyTimelineEvent, AnyStateEvent}, OwnedRoomId, OwnedEventId, OwnedUserId}; +use serde::{Deserialize, Serialize}; +use tracing::instrument; + +use crate::http::Client; + +#[derive(Default)] +pub struct RoomService; + +#[derive(Debug, Default, Serialize)] +pub struct ListParams { + pub from: Option<u64>, + pub limit: Option<u64>, + pub order_by: Option<OrderBy>, + pub direction: Option<Direction>, + pub search_term: Option<String>, +} + +#[derive(Debug, Deserialize)] +pub struct Room { + /// Room ID postfixed with Matrix instance Host + /// E.g. `!room:example.com` + pub room_id: OwnedRoomId, + pub name: Option<String>, + pub canonical_alias: Option<String>, + pub joined_members: u64, + pub joined_local_members: u64, + pub version: Option<String>, + pub creator: Option<String>, + pub encryption: Option<String>, + pub federatable: bool, + pub public: bool, + pub join_rules: Option<String>, + pub guest_access: Option<String>, + pub history_visibility: Option<String>, + pub state_events: u64, + pub room_type: Option<String>, + #[serde(flatten)] + pub details: Option<RoomExt>, +} + +#[derive(Debug, Deserialize)] +pub struct RoomExt { + pub avatar: Option<String>, + pub topic: Option<String>, + pub joined_local_devices: u64, + pub forgotten: bool, +} + +#[derive(Debug, Deserialize)] +pub struct ListResponse { + pub rooms: Vec<Room>, + pub offset: Option<u64>, + pub total_rooms: Option<u64>, + pub prev_batch: Option<String>, + pub next_batch: Option<String>, +} + +#[derive(Debug, Deserialize)] +pub struct MembersResponse { + pub members: Vec<String>, + pub total: u64, +} + +#[derive(Debug, Deserialize)] +pub struct State { + #[serde(rename = "type")] + pub kind: String, + pub state_key: String, + pub etc: Option<bool>, +} + +#[derive(Debug, Deserialize)] +pub struct StateResponse { + pub state: Vec<State>, +} + +#[derive(Default, Debug, Serialize)] +pub struct RoomEventFilter { + pub not_types: Vec<String>, + pub not_rooms: Vec<OwnedRoomId>, + pub limit: Option<u64>, + pub rooms: Option<Vec<OwnedRoomId>>, + pub not_senders: Vec<OwnedUserId>, + pub senders: Option<Vec<OwnedUserId>>, + pub types: Option<Vec<String>>, + pub url_filter: Option<bool>, + pub lazy_load_options: Option<bool>, + pub unread_thread_notifications: bool, +} + +#[derive(Debug, Serialize)] +pub struct MessagesParams { + pub from: String, + pub to: Option<String>, + pub limit: Option<u64>, + pub filter: Option<RoomEventFilter>, + pub direction: Option<String>, +} + +#[derive(Default, Debug, Deserialize)] +pub struct MessagesResponse { + pub chunk: Vec<AnyTimelineEvent>, + pub start: String, + pub end: String, + pub state: Option<Vec<State>>, +} + +#[derive(Debug, Default, Serialize)] +pub struct TimestampToEventParams { + pub ts: Option<u64>, + pub direction: Option<Direction>, +} + +#[derive(Debug, Deserialize)] +pub struct TimestampToEventResponse { + pub event_id: String, + pub origin_server_ts: u64, +} + +#[derive(Debug, Deserialize)] +pub struct ForwardExtremities { + pub event_id: String, + pub state_group: u64, + pub depth: u64, + pub received_ts: u64, +} + +#[derive(Debug, Deserialize)] +pub struct CheckForwardExtremitiesResponse { + pub count: u64, + pub result: Vec<ForwardExtremities>, +} + +#[derive(Debug, Deserialize)] +pub struct DeleteForwardExtremitiesResponse { + pub deleted: u64, +} + +#[derive(Default, Debug, Serialize)] +pub struct EventContextParams { + pub limit: Option<u64>, + pub filter: Option<RoomEventFilter>, +} + +#[derive(Debug, Deserialize)] +pub struct EventContextResponse { + pub start: String, + pub end: String, + pub events_before: Vec<AnyTimelineEvent>, + pub event: AnyTimelineEvent, + pub events_after: Vec<AnyTimelineEvent>, + pub state: Vec<AnyStateEvent>, +} + +impl RoomService { + /// Returns information about a specific room + /// + /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#room-details-api + #[instrument(skip(client))] + pub async fn get_one(client: &Client, room_id: OwnedRoomId) -> Result<Room> { + let resp = client + .get(format!( + "/_synapse/admin/v1/rooms/{room_id}", + room_id = room_id + )) + .await?; + let data: Room = resp.json().await?; + + Ok(data) + } + + /// Returns all rooms. By default, the response is ordered alphabetically by room name + /// + /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#list-room-api + #[instrument(skip(client))] + pub async fn get_all(client: &Client, params: ListParams) -> Result<ListResponse> { + let resp = client + .get_query("/_synapse/admin/v1/rooms", ¶ms) + .await?; + let data: ListResponse = resp.json().await?; + + Ok(data) + } + + /// Allows a server admin to get a list of all members of a room + /// + /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#room-members-api + #[instrument(skip(client))] + pub async fn get_members(client: &Client, room_id: OwnedRoomId) -> Result<MembersResponse> { + let resp = client + .get(format!( + "/_synapse/admin/v1/rooms/{room_id}/members", + room_id = room_id + )) + .await?; + let data: MembersResponse = resp.json().await?; + + Ok(data) + } + + /// Allows a server admin to get a list of all state events in a room + /// + /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#room-state-api + #[instrument(skip(client))] + pub async fn get_state(client: &Client, room_id: OwnedRoomId) -> Result<StateResponse> { + let resp = client + .get(format!( + "/_synapse/admin/v1/rooms/{room_id}/state", + room_id = room_id + )) + .await?; + let data: StateResponse = resp.json().await?; + + Ok(data) + } + + /// Allows a server admin to get all messages sent to a room in a given timeframe + /// + /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#room-messages-api + #[instrument(skip(client))] + pub async fn get_messages( + client: &Client, + room_id: OwnedRoomId, + params: MessagesParams, + ) -> Result<MessagesResponse> { + let resp = client + .get_query( + format!( + "/_synapse/admin/v1/rooms/{room_id}/messages", + room_id = room_id + ), + ¶ms, + ) + .await?; + let data: MessagesResponse = resp.json().await?; + + Ok(data) + } + + /// Allows a server admin to get the `event_id` of the closest event to the given timestamp + /// + /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#room-timestamp-to-event-api + #[instrument(skip(client))] + pub async fn get_timestamp_to_event( + client: &Client, + room_id: OwnedRoomId, + params: TimestampToEventParams, + ) -> Result<TimestampToEventResponse> { + let resp = client + .get_query( + format!( + "/_synapse/admin/v1/rooms/{room_id}/timestamp_to_event", + room_id = room_id + ), + ¶ms, + ) + .await?; + let data: TimestampToEventResponse = resp.json().await?; + + Ok(data) + } + + /// Allows a server admin to check the status of forward extremities for a room + /// + /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#check-for-forward-extremities + #[instrument(skip(client))] + pub async fn check_forward_extremities( + client: &Client, + room_id: OwnedRoomId, + ) -> Result<CheckForwardExtremitiesResponse> { + let resp = client + .get(format!( + "/_synapse/admin/v1/rooms/{room_id}/forward_extremities", + room_id = room_id + )) + .await?; + let data: CheckForwardExtremitiesResponse = resp.json().await?; + + Ok(data) + } + + /// Allows a server admin to delete forward extremities for a room + /// WARNING: Please ensure you know what you're doing and read the related issue [#1760](https://github.com/matrix-org/synapse/issues/1760) + /// + /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#delete-for-forward-extremities + #[instrument(skip(client))] + pub async fn delete_forward_extremities( + client: &Client, + room_id: OwnedRoomId, + ) -> Result<DeleteForwardExtremitiesResponse> { + let resp = client + .delete(format!( + "/_synapse/admin/v1/rooms/{room_id}/forward_extremities", + room_id = room_id + )) + .await?; + let data: DeleteForwardExtremitiesResponse = resp.json().await?; + + Ok(data) + } + + /// Allows a server admin to delete forward extremities for a room + /// WARNING: Please ensure you know what you're doing and have read the related issue [#1760](https://github.com/matrix-org/synapse/issues/1760) + /// + /// Refer: https://matrix-org.github.io/synapse/latest/admin_api/rooms.html#delete-for-forward-extremities + #[instrument(skip(client))] + pub async fn get_event_context( + client: &Client, + room_id: OwnedRoomId, + event_id: OwnedEventId, + params: EventContextParams, + ) -> Result<EventContextResponse> { + let resp = client + .get_query(format!( + "/_synapse/admin/v1/rooms/{room_id}/context/{event_id}", + room_id = room_id, + event_id = event_id, + ),¶ms) + .await?; + let data: EventContextResponse = resp.json().await?; + + Ok(data) + } +} + +#[derive(Debug, Serialize)] +pub enum Direction { + #[serde(rename = "f")] + Forward, + #[serde(rename = "b")] + Backward, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum OrderBy { + Name, + CanonicalAlias, + JoinedMembers, + JoinedLocalMembers, + Version, + Creator, + Encryption, + Federatable, + Public, + JoinRules, + GuestAccess, + HistoryVisibility, + StateEvents, +} diff --git a/crates/matrix/src/http.rs b/crates/matrix/src/http.rs index f789bff..1955392 100644 --- a/crates/matrix/src/http.rs +++ b/crates/matrix/src/http.rs @@ -99,6 +99,20 @@ impl Client { Ok(resp) } + pub async fn delete(&self, path: impl AsRef<str>) -> Result<Response> + { + let url = self.build_url(path)?; + let headers = self.build_headers()?; + let resp = self + .client + .delete(url) + .headers(headers) + .send() + .await?; + + Ok(resp) + } + fn build_headers(&self) -> Result<HeaderMap> { let mut headers = HeaderMap::new(); diff --git a/crates/matrix/src/lib.rs b/crates/matrix/src/lib.rs index 2b6fcb2..613b44d 100644 --- a/crates/matrix/src/lib.rs +++ b/crates/matrix/src/lib.rs @@ -17,11 +17,17 @@ pub mod admin; /// this is equivalent to making cURL requests to the Matrix server. pub mod client; -/// Implementation of our custom space events -pub mod space; +/// Implementation of our custom space events. +mod space; -/// Implementation of database entities -pub mod entities; +/// Here we intentionally shadow `ruma::events::space` in favor of our own. +/// `ruma::events` extended with the implementation of our custom space events. +pub mod events { + pub use ruma::events::*; -/// Wrapper to open our database connection -pub mod postgres; + pub mod space { + pub use ruma::events::space::*; + + pub use crate::space::*; + } +}