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 conduwuit::{Err, Result};
|
||||
use conduwuit::{
|
||||
Err, Event, Result,
|
||||
utils::stream::{BroadbandExt, WidebandExt},
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use ruma::OwnedRoomId;
|
||||
use ruma::{
|
||||
OwnedRoomId,
|
||||
events::{
|
||||
StateEventType,
|
||||
room::{
|
||||
create::RoomCreateEventContent,
|
||||
encryption::PossiblyRedactedRoomEncryptionEventContent,
|
||||
},
|
||||
},
|
||||
};
|
||||
use ruminuwuity::admin::continuwuity::rooms;
|
||||
use tokio::join;
|
||||
|
||||
use crate::Ruma;
|
||||
|
||||
/// # `GET /_continuwuity/admin/v1/rooms/list`
|
||||
/// # `GET /_continuwuity/admin/rooms`
|
||||
///
|
||||
/// Lists all rooms known to this server, excluding banned ones.
|
||||
pub(crate) async fn list_rooms(
|
||||
/// Lists all room IDs known to this server, excluding banned ones.
|
||||
///
|
||||
/// 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>,
|
||||
body: Ruma<rooms::list::v1::Request>,
|
||||
) -> Result<rooms::list::v1::Response> {
|
||||
body: Ruma<rooms::list::unstable::Request>,
|
||||
) -> Result<rooms::list::unstable::Response> {
|
||||
let sender_user = body.identity.sender_user();
|
||||
if !services.users.is_admin(sender_user).await {
|
||||
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
|
||||
@@ -32,5 +50,127 @@ pub(crate) async fn list_rooms(
|
||||
.collect()
|
||||
.await;
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@ mod ban;
|
||||
mod list;
|
||||
|
||||
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 axum::{
|
||||
Router,
|
||||
response::{IntoResponse, Redirect},
|
||||
routing::{any, get, post},
|
||||
Router,
|
||||
};
|
||||
use conduwuit::err;
|
||||
pub(super) use conduwuit_service::state::State;
|
||||
use http::{uri, Uri};
|
||||
use http::{Uri, uri};
|
||||
|
||||
use self::handler::RouterExt;
|
||||
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
|
||||
.ruma_route(&admin_api::users::list_users_route)
|
||||
.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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
pub mod v1 {
|
||||
pub mod unstable {
|
||||
use ruma::{
|
||||
OwnedRoomId,
|
||||
api::{auth_scheme::AccessToken, request, response},
|
||||
@@ -10,8 +10,7 @@ pub mod v1 {
|
||||
rate_limited: false,
|
||||
authentication: AccessToken,
|
||||
history: {
|
||||
unstable("org.continuwuity.admin") => "/_continuwuity/admin/rooms/list",
|
||||
1.0 => "/_continuwuity/admin/v1/rooms",
|
||||
unstable => "/_continuwuity/admin/rooms/list",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,3 +34,127 @@ pub mod v1 {
|
||||
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