mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
feat: Add pagination to rooms list & include more information
This commit is contained in:
@@ -1,18 +1,36 @@
|
|||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use conduwuit::{Err, Result};
|
use conduwuit::{
|
||||||
|
Err, Event, Result,
|
||||||
|
utils::stream::{BroadbandExt, WidebandExt},
|
||||||
|
};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use ruma::OwnedRoomId;
|
use ruma::{
|
||||||
|
OwnedRoomId,
|
||||||
|
events::{
|
||||||
|
StateEventType,
|
||||||
|
room::{
|
||||||
|
create::RoomCreateEventContent,
|
||||||
|
encryption::PossiblyRedactedRoomEncryptionEventContent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
use ruminuwuity::admin::continuwuity::rooms;
|
use ruminuwuity::admin::continuwuity::rooms;
|
||||||
|
use tokio::join;
|
||||||
|
|
||||||
use crate::Ruma;
|
use crate::Ruma;
|
||||||
|
|
||||||
/// # `GET /_continuwuity/admin/v1/rooms/list`
|
/// # `GET /_continuwuity/admin/rooms`
|
||||||
///
|
///
|
||||||
/// Lists all rooms known to this server, excluding banned ones.
|
/// Lists all room IDs known to this server, excluding banned ones.
|
||||||
pub(crate) async fn list_rooms(
|
///
|
||||||
|
/// This is the legacy version of the endpoint, which does not support
|
||||||
|
/// pagination or including banned rooms. It is recommended to use the
|
||||||
|
/// `/v1/rooms` endpoint instead. This endpoint may be removed in a future
|
||||||
|
/// release.
|
||||||
|
pub(crate) async fn legacy_list_rooms_route(
|
||||||
State(services): State<crate::State>,
|
State(services): State<crate::State>,
|
||||||
body: Ruma<rooms::list::v1::Request>,
|
body: Ruma<rooms::list::unstable::Request>,
|
||||||
) -> Result<rooms::list::v1::Response> {
|
) -> Result<rooms::list::unstable::Response> {
|
||||||
let sender_user = body.identity.sender_user();
|
let sender_user = body.identity.sender_user();
|
||||||
if !services.users.is_admin(sender_user).await {
|
if !services.users.is_admin(sender_user).await {
|
||||||
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
|
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
|
||||||
@@ -32,5 +50,127 @@ pub(crate) async fn list_rooms(
|
|||||||
.collect()
|
.collect()
|
||||||
.await;
|
.await;
|
||||||
rooms.sort();
|
rooms.sort();
|
||||||
|
Ok(rooms::list::unstable::Response::new(rooms))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # `GET /_continuwuity/admin/v1/rooms`
|
||||||
|
///
|
||||||
|
/// Lists rooms known to this server.
|
||||||
|
pub(crate) async fn list_rooms_route(
|
||||||
|
State(services): State<crate::State>,
|
||||||
|
body: Ruma<rooms::list::v1::Request>,
|
||||||
|
) -> Result<rooms::list::v1::Response> {
|
||||||
|
let sender_user = body.sender_user();
|
||||||
|
if !services.users.is_admin(sender_user).await {
|
||||||
|
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
|
||||||
|
}
|
||||||
|
|
||||||
|
let include_banned_rooms = body.include_banned_rooms;
|
||||||
|
let rooms = services
|
||||||
|
.rooms
|
||||||
|
.metadata
|
||||||
|
.iter_ids()
|
||||||
|
.wide_filter_map(|room_id| async move {
|
||||||
|
if include_banned_rooms || !services.rooms.metadata.is_banned(&room_id).await {
|
||||||
|
Some(room_id.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.skip(body.offset.unwrap_or_default())
|
||||||
|
.take(body.limit.unwrap_or(100).min(100))
|
||||||
|
.broad_filter_map(|room_id| async move {
|
||||||
|
let (
|
||||||
|
banned,
|
||||||
|
disabled,
|
||||||
|
member_count,
|
||||||
|
local_member_count,
|
||||||
|
resident_server_count,
|
||||||
|
published,
|
||||||
|
create_event,
|
||||||
|
encryption_event,
|
||||||
|
name_event,
|
||||||
|
topic_event,
|
||||||
|
canonical_alias_event,
|
||||||
|
join_rules_event,
|
||||||
|
history_visibility_event,
|
||||||
|
) = join!(
|
||||||
|
services.rooms.metadata.is_banned(&room_id),
|
||||||
|
services.rooms.metadata.is_disabled(&room_id),
|
||||||
|
services.rooms.state_cache.room_joined_count(&room_id),
|
||||||
|
services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.active_local_users_in_room(&room_id)
|
||||||
|
.count(),
|
||||||
|
services.rooms.state_cache.room_servers(&room_id).count(),
|
||||||
|
services.rooms.directory.is_public_room(&room_id),
|
||||||
|
services.rooms.state_accessor.room_state_get(
|
||||||
|
&room_id,
|
||||||
|
&StateEventType::RoomCreate,
|
||||||
|
""
|
||||||
|
),
|
||||||
|
services
|
||||||
|
.rooms
|
||||||
|
.state_accessor
|
||||||
|
.room_state_get_content::<PossiblyRedactedRoomEncryptionEventContent>(
|
||||||
|
&room_id,
|
||||||
|
&StateEventType::RoomEncryption,
|
||||||
|
""
|
||||||
|
),
|
||||||
|
services.rooms.state_accessor.room_state_get_content(
|
||||||
|
&room_id,
|
||||||
|
&StateEventType::RoomName,
|
||||||
|
""
|
||||||
|
),
|
||||||
|
services.rooms.state_accessor.room_state_get_content(
|
||||||
|
&room_id,
|
||||||
|
&StateEventType::RoomTopic,
|
||||||
|
""
|
||||||
|
),
|
||||||
|
services.rooms.state_accessor.room_state_get_content(
|
||||||
|
&room_id,
|
||||||
|
&StateEventType::RoomCanonicalAlias,
|
||||||
|
""
|
||||||
|
),
|
||||||
|
services.rooms.state_accessor.room_state_get_content(
|
||||||
|
&room_id,
|
||||||
|
&StateEventType::RoomJoinRules,
|
||||||
|
""
|
||||||
|
),
|
||||||
|
services.rooms.state_accessor.room_state_get_content(
|
||||||
|
&room_id,
|
||||||
|
&StateEventType::RoomHistoryVisibility,
|
||||||
|
""
|
||||||
|
),
|
||||||
|
);
|
||||||
|
let Ok(create_event) = create_event else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let create_content = create_event
|
||||||
|
.get_content::<RoomCreateEventContent>()
|
||||||
|
.expect("m.room.create content must be valid");
|
||||||
|
Some(rooms::list::v1::MinimalRoomInfo {
|
||||||
|
room_id,
|
||||||
|
banned,
|
||||||
|
disabled,
|
||||||
|
member_count: usize::try_from(member_count.unwrap_or_default())
|
||||||
|
.expect("u64 should fit in usize"),
|
||||||
|
local_member_count,
|
||||||
|
resident_server_count,
|
||||||
|
creators: vec![create_event.sender],
|
||||||
|
encrypted: encryption_event.is_ok_and(|c| c.algorithm.is_some()),
|
||||||
|
federated: create_content.federate,
|
||||||
|
published,
|
||||||
|
version: create_content.room_version,
|
||||||
|
name: name_event.unwrap_or(None),
|
||||||
|
topic: topic_event.unwrap_or(None),
|
||||||
|
canonical_alias: canonical_alias_event.unwrap_or(None),
|
||||||
|
join_rules: join_rules_event.unwrap_or(None),
|
||||||
|
history_visibility: history_visibility_event.unwrap_or(None),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
.await;
|
||||||
Ok(rooms::list::v1::Response::new(rooms))
|
Ok(rooms::list::v1::Response::new(rooms))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ mod ban;
|
|||||||
mod list;
|
mod list;
|
||||||
|
|
||||||
pub(crate) use ban::ban_room;
|
pub(crate) use ban::ban_room;
|
||||||
pub(crate) use list::list_rooms;
|
pub(crate) use list::*;
|
||||||
|
|||||||
+4
-3
@@ -6,13 +6,13 @@ mod response;
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
|
Router,
|
||||||
response::{IntoResponse, Redirect},
|
response::{IntoResponse, Redirect},
|
||||||
routing::{any, get, post},
|
routing::{any, get, post},
|
||||||
Router,
|
|
||||||
};
|
};
|
||||||
use conduwuit::err;
|
use conduwuit::err;
|
||||||
pub(super) use conduwuit_service::state::State;
|
pub(super) use conduwuit_service::state::State;
|
||||||
use http::{uri, Uri};
|
use http::{Uri, uri};
|
||||||
|
|
||||||
use self::handler::RouterExt;
|
use self::handler::RouterExt;
|
||||||
pub(super) use self::{args::Args as Ruma, auth::ClientIdentity, response::RumaResponse};
|
pub(super) use self::{args::Args as Ruma, auth::ClientIdentity, response::RumaResponse};
|
||||||
@@ -284,7 +284,8 @@ pub fn build(router: Router<State>, state: State) -> Router<State> {
|
|||||||
router = router
|
router = router
|
||||||
.ruma_route(&admin_api::users::list_users_route)
|
.ruma_route(&admin_api::users::list_users_route)
|
||||||
.ruma_route(&admin_api::rooms::ban_room)
|
.ruma_route(&admin_api::rooms::ban_room)
|
||||||
.ruma_route(&admin_api::rooms::list_rooms);
|
.ruma_route(&admin_api::rooms::legacy_list_rooms_route)
|
||||||
|
.ruma_route(&admin_api::rooms::list_rooms_route);
|
||||||
};
|
};
|
||||||
|
|
||||||
router
|
router
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
pub mod v1 {
|
pub mod unstable {
|
||||||
use ruma::{
|
use ruma::{
|
||||||
OwnedRoomId,
|
OwnedRoomId,
|
||||||
api::{auth_scheme::AccessToken, request, response},
|
api::{auth_scheme::AccessToken, request, response},
|
||||||
@@ -10,8 +10,7 @@ pub mod v1 {
|
|||||||
rate_limited: false,
|
rate_limited: false,
|
||||||
authentication: AccessToken,
|
authentication: AccessToken,
|
||||||
history: {
|
history: {
|
||||||
unstable("org.continuwuity.admin") => "/_continuwuity/admin/rooms/list",
|
unstable => "/_continuwuity/admin/rooms/list",
|
||||||
1.0 => "/_continuwuity/admin/v1/rooms",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,3 +34,127 @@ pub mod v1 {
|
|||||||
pub fn new(rooms: Vec<OwnedRoomId>) -> Self { Self { rooms } }
|
pub fn new(rooms: Vec<OwnedRoomId>) -> Self { Self { rooms } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod v1 {
|
||||||
|
use ruma::{
|
||||||
|
OwnedRoomId, OwnedUserId, RoomVersionId,
|
||||||
|
api::{auth_scheme::AccessToken, request, response},
|
||||||
|
events::room::{
|
||||||
|
canonical_alias::PossiblyRedactedRoomCanonicalAliasEventContent,
|
||||||
|
history_visibility::PossiblyRedactedRoomHistoryVisibilityEventContent,
|
||||||
|
join_rules::PossiblyRedactedRoomJoinRulesEventContent,
|
||||||
|
name::PossiblyRedactedRoomNameEventContent,
|
||||||
|
topic::PossiblyRedactedRoomTopicEventContent,
|
||||||
|
},
|
||||||
|
metadata,
|
||||||
|
serde::{default_true, is_default},
|
||||||
|
};
|
||||||
|
|
||||||
|
metadata! {
|
||||||
|
method: GET,
|
||||||
|
rate_limited: false,
|
||||||
|
authentication: AccessToken,
|
||||||
|
history: {
|
||||||
|
1.0 => "/_continuwuity/admin/v1/rooms",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[request]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Request {
|
||||||
|
/// The maximum number of results to return in this page. Maximum (and
|
||||||
|
/// default) is 100.
|
||||||
|
#[ruma_api(query)]
|
||||||
|
#[serde(default, skip_serializing_if = "is_default")]
|
||||||
|
pub limit: Option<usize>,
|
||||||
|
|
||||||
|
/// The number of results to skip over before returning results. Default
|
||||||
|
/// is 0.
|
||||||
|
#[ruma_api(query)]
|
||||||
|
#[serde(default, skip_serializing_if = "is_default")]
|
||||||
|
pub offset: Option<usize>,
|
||||||
|
|
||||||
|
/// If true, includes banned rooms in the response.
|
||||||
|
#[ruma_api(query)]
|
||||||
|
#[serde(default, skip_serializing_if = "is_default")]
|
||||||
|
pub include_banned_rooms: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct MinimalRoomInfo {
|
||||||
|
/// The room's unique ID.
|
||||||
|
pub room_id: OwnedRoomId,
|
||||||
|
/// If true, this room is banned, and cannot be joined by non-admins.
|
||||||
|
#[serde(default, skip_serializing_if = "is_default")]
|
||||||
|
pub banned: bool,
|
||||||
|
/// If true, this room has federation disabled, but can still be locally
|
||||||
|
/// used.
|
||||||
|
#[serde(default, skip_serializing_if = "is_default")]
|
||||||
|
pub disabled: bool,
|
||||||
|
/// The total number of joined members in this room.
|
||||||
|
#[serde(default, skip_serializing_if = "is_default")]
|
||||||
|
pub member_count: usize,
|
||||||
|
/// The total number of joined members in this room that are local to
|
||||||
|
/// this server.
|
||||||
|
#[serde(default, skip_serializing_if = "is_default")]
|
||||||
|
pub local_member_count: usize,
|
||||||
|
/// The number of unique homeservers currently joined to this room.
|
||||||
|
#[serde(default, skip_serializing_if = "is_default")]
|
||||||
|
pub resident_server_count: usize,
|
||||||
|
/// The users who created this room.
|
||||||
|
///
|
||||||
|
/// The first entry is always the sender of the `m.room.create` event.
|
||||||
|
/// Any entries thereafter are additional creators in v12+ rooms. An
|
||||||
|
/// empty vec indicates the room is not known.
|
||||||
|
#[serde(default, skip_serializing_if = "is_default")]
|
||||||
|
pub creators: Vec<OwnedUserId>,
|
||||||
|
/// If true, this room has encryption enabled.
|
||||||
|
#[serde(default, skip_serializing_if = "is_default")]
|
||||||
|
pub encrypted: bool,
|
||||||
|
/// If true, this room is allowed to be federated (`m.federate` is not
|
||||||
|
/// `false` in `m.room.create`).
|
||||||
|
#[serde(default = "default_true", skip_serializing_if = "is_default")]
|
||||||
|
pub federated: bool,
|
||||||
|
/// If true, this room is published to this server's room directory.
|
||||||
|
#[serde(default, skip_serializing_if = "is_default")]
|
||||||
|
pub published: bool,
|
||||||
|
/// The version of the room.
|
||||||
|
pub version: RoomVersionId,
|
||||||
|
/// The event content for the `m.room.name` event, if any is present.
|
||||||
|
/// May be redacted.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub name: Option<PossiblyRedactedRoomNameEventContent>,
|
||||||
|
/// The event content for the `m.room.topic` event, if any is present.
|
||||||
|
/// May be redacted.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub topic: Option<PossiblyRedactedRoomTopicEventContent>,
|
||||||
|
/// The event content for the `m.room.canonical_alias` event, if any is
|
||||||
|
/// present. May be redacted.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub canonical_alias: Option<PossiblyRedactedRoomCanonicalAliasEventContent>,
|
||||||
|
/// The event content for the `m.room.join_rules` event, if any is
|
||||||
|
/// present. May be redacted.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub join_rules: Option<PossiblyRedactedRoomJoinRulesEventContent>,
|
||||||
|
/// The event content for the `m.room.history_visibility` event, if any
|
||||||
|
/// is present. May be redacted.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub history_visibility: Option<PossiblyRedactedRoomHistoryVisibilityEventContent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[response]
|
||||||
|
pub struct Response {
|
||||||
|
/// A list of rooms known to this server.
|
||||||
|
pub rooms: Vec<MinimalRoomInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Request {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> Self { Self::default() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Response {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(rooms: Vec<MinimalRoomInfo>) -> Self { Self { rooms } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user