de-global services from admin

Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
Jason Volk 2024-07-27 00:11:41 +00:00
parent 7a3cc3941e
commit 7e50db4193
37 changed files with 1131 additions and 1127 deletions

View file

@ -3,14 +3,14 @@ use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent; use ruma::events::room::message::RoomMessageEventContent;
use crate::{ use crate::{
appservice, appservice::AppserviceCommand, check, check::CheckCommand, debug, debug::DebugCommand, federation, appservice, appservice::AppserviceCommand, check, check::CheckCommand, command::Command, debug,
federation::FederationCommand, media, media::MediaCommand, query, query::QueryCommand, room, room::RoomCommand, debug::DebugCommand, federation, federation::FederationCommand, media, media::MediaCommand, query,
server, server::ServerCommand, user, user::UserCommand, query::QueryCommand, room, room::RoomCommand, server, server::ServerCommand, user, user::UserCommand,
}; };
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command(name = "admin", version = env!("CARGO_PKG_VERSION"))] #[command(name = "admin", version = env!("CARGO_PKG_VERSION"))]
pub(crate) enum AdminCommand { pub(super) enum AdminCommand {
#[command(subcommand)] #[command(subcommand)]
/// - Commands for managing appservices /// - Commands for managing appservices
Appservices(AppserviceCommand), Appservices(AppserviceCommand),
@ -49,18 +49,18 @@ pub(crate) enum AdminCommand {
} }
#[tracing::instrument(skip_all, name = "command")] #[tracing::instrument(skip_all, name = "command")]
pub(crate) async fn process(command: AdminCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> { pub(super) async fn process(command: AdminCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
let reply_message_content = match command { use AdminCommand::*;
AdminCommand::Appservices(command) => appservice::process(command, body).await?,
AdminCommand::Media(command) => media::process(command, body).await?,
AdminCommand::Users(command) => user::process(command, body).await?,
AdminCommand::Rooms(command) => room::process(command, body).await?,
AdminCommand::Federation(command) => federation::process(command, body).await?,
AdminCommand::Server(command) => server::process(command, body).await?,
AdminCommand::Debug(command) => debug::process(command, body).await?,
AdminCommand::Query(command) => query::process(command, body).await?,
AdminCommand::Check(command) => check::process(command, body).await?,
};
Ok(reply_message_content) Ok(match command {
Appservices(command) => appservice::process(command, context).await?,
Media(command) => media::process(command, context).await?,
Users(command) => user::process(command, context).await?,
Rooms(command) => room::process(command, context).await?,
Federation(command) => federation::process(command, context).await?,
Server(command) => server::process(command, context).await?,
Debug(command) => debug::process(command, context).await?,
Query(command) => query::process(command, context).await?,
Check(command) => check::process(command, context).await?,
})
} }

View file

@ -1,18 +1,20 @@
use ruma::{api::appservice::Registration, events::room::message::RoomMessageEventContent}; use ruma::{api::appservice::Registration, events::room::message::RoomMessageEventContent};
use crate::{services, Result}; use crate::{admin_command, Result};
pub(super) async fn register(body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { pub(super) async fn register(&self) -> Result<RoomMessageEventContent> {
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
{
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.", "Expected code block in command body. Add --help for details.",
)); ));
} }
let appservice_config = body[1..body.len().checked_sub(1).unwrap()].join("\n"); let appservice_config = self.body[1..self.body.len().checked_sub(1).unwrap()].join("\n");
let parsed_config = serde_yaml::from_str::<Registration>(&appservice_config); let parsed_config = serde_yaml::from_str::<Registration>(&appservice_config);
match parsed_config { match parsed_config {
Ok(yaml) => match services().appservice.register_appservice(yaml).await { Ok(yaml) => match self.services.appservice.register_appservice(yaml).await {
Ok(id) => Ok(RoomMessageEventContent::text_plain(format!( Ok(id) => Ok(RoomMessageEventContent::text_plain(format!(
"Appservice registered with ID: {id}." "Appservice registered with ID: {id}."
))), ))),
@ -26,8 +28,10 @@ pub(super) async fn register(body: Vec<&str>) -> Result<RoomMessageEventContent>
} }
} }
pub(super) async fn unregister(_body: Vec<&str>, appservice_identifier: String) -> Result<RoomMessageEventContent> { #[admin_command]
match services() pub(super) async fn unregister(&self, appservice_identifier: String) -> Result<RoomMessageEventContent> {
match self
.services
.appservice .appservice
.unregister_appservice(&appservice_identifier) .unregister_appservice(&appservice_identifier)
.await .await
@ -39,8 +43,10 @@ pub(super) async fn unregister(_body: Vec<&str>, appservice_identifier: String)
} }
} }
pub(super) async fn show(_body: Vec<&str>, appservice_identifier: String) -> Result<RoomMessageEventContent> { #[admin_command]
match services() pub(super) async fn show_appservice_config(&self, appservice_identifier: String) -> Result<RoomMessageEventContent> {
match self
.services
.appservice .appservice
.get_registration(&appservice_identifier) .get_registration(&appservice_identifier)
.await .await
@ -54,8 +60,9 @@ pub(super) async fn show(_body: Vec<&str>, appservice_identifier: String) -> Res
} }
} }
pub(super) async fn list(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
let appservices = services().appservice.iter_ids().await; pub(super) async fn list_registered(&self) -> Result<RoomMessageEventContent> {
let appservices = self.services.appservice.iter_ids().await;
let output = format!("Appservices ({}): {}", appservices.len(), appservices.join(", ")); let output = format!("Appservices ({}): {}", appservices.len(), appservices.join(", "));
Ok(RoomMessageEventContent::text_plain(output)) Ok(RoomMessageEventContent::text_plain(output))
} }

View file

@ -2,11 +2,11 @@ mod commands;
use clap::Subcommand; use clap::Subcommand;
use conduit::Result; use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent;
use self::commands::*; use crate::admin_command_dispatch;
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
#[admin_command_dispatch]
pub(super) enum AppserviceCommand { pub(super) enum AppserviceCommand {
/// - Register an appservice using its registration YAML /// - Register an appservice using its registration YAML
/// ///
@ -28,24 +28,13 @@ pub(super) enum AppserviceCommand {
/// - Show an appservice's config using its ID /// - Show an appservice's config using its ID
/// ///
/// You can find the ID using the `list-appservices` command. /// You can find the ID using the `list-appservices` command.
Show { #[clap(alias("show"))]
ShowAppserviceConfig {
/// The appservice to show /// The appservice to show
appservice_identifier: String, appservice_identifier: String,
}, },
/// - List all the currently registered appservices /// - List all the currently registered appservices
List, #[clap(alias("list"))]
} ListRegistered,
pub(super) async fn process(command: AppserviceCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
AppserviceCommand::Register => register(body).await?,
AppserviceCommand::Unregister {
appservice_identifier,
} => unregister(body, appservice_identifier).await?,
AppserviceCommand::Show {
appservice_identifier,
} => show(body, appservice_identifier).await?,
AppserviceCommand::List => list(body).await?,
})
} }

View file

@ -1,12 +1,14 @@
use conduit::Result; use conduit::Result;
use conduit_macros::implement;
use ruma::events::room::message::RoomMessageEventContent; use ruma::events::room::message::RoomMessageEventContent;
use crate::services; use crate::{services, Command};
/// Uses the iterator in `src/database/key_value/users.rs` to iterator over /// Uses the iterator in `src/database/key_value/users.rs` to iterator over
/// every user in our database (remote and local). Reports total count, any /// every user in our database (remote and local). Reports total count, any
/// errors if there were any, etc /// errors if there were any, etc
pub(super) async fn check_all_users(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[implement(Command, params = "<'_>")]
pub(super) async fn check_all_users(&self) -> Result<RoomMessageEventContent> {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().users.db.iter(); let results = services().users.db.iter();
let query_time = timer.elapsed(); let query_time = timer.elapsed();

View file

@ -4,15 +4,15 @@ use clap::Subcommand;
use conduit::Result; use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent; use ruma::events::room::message::RoomMessageEventContent;
use self::commands::*; use crate::Command;
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub(super) enum CheckCommand { pub(super) enum CheckCommand {
AllUsers, AllUsers,
} }
pub(super) async fn process(command: CheckCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> { pub(super) async fn process(command: CheckCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
Ok(match command { Ok(match command {
CheckCommand::AllUsers => check_all_users(body).await?, CheckCommand::AllUsers => context.check_all_users().await?,
}) })
} }

6
src/admin/command.rs Normal file
View file

@ -0,0 +1,6 @@
use service::Services;
pub(crate) struct Command<'a> {
pub(crate) services: &'a Services,
pub(crate) body: &'a [&'a str],
}

View file

@ -16,19 +16,22 @@ use ruma::{
events::room::message::RoomMessageEventContent, events::room::message::RoomMessageEventContent,
CanonicalJsonObject, EventId, OwnedRoomOrAliasId, RoomId, RoomVersionId, ServerName, CanonicalJsonObject, EventId, OwnedRoomOrAliasId, RoomId, RoomVersionId, ServerName,
}; };
use service::services;
use tokio::sync::RwLock; use tokio::sync::RwLock;
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
pub(super) async fn echo(_body: &[&str], message: Vec<String>) -> Result<RoomMessageEventContent> { use crate::admin_command;
#[admin_command]
pub(super) async fn echo(&self, message: Vec<String>) -> Result<RoomMessageEventContent> {
let message = message.join(" "); let message = message.join(" ");
Ok(RoomMessageEventContent::notice_plain(message)) Ok(RoomMessageEventContent::notice_plain(message))
} }
pub(super) async fn get_auth_chain(_body: &[&str], event_id: Box<EventId>) -> Result<RoomMessageEventContent> { #[admin_command]
pub(super) async fn get_auth_chain(&self, event_id: Box<EventId>) -> Result<RoomMessageEventContent> {
let event_id = Arc::<EventId>::from(event_id); let event_id = Arc::<EventId>::from(event_id);
if let Some(event) = services().rooms.timeline.get_pdu_json(&event_id)? { if let Some(event) = self.services.rooms.timeline.get_pdu_json(&event_id)? {
let room_id_str = event let room_id_str = event
.get("room_id") .get("room_id")
.and_then(|val| val.as_str()) .and_then(|val| val.as_str())
@ -36,13 +39,16 @@ pub(super) async fn get_auth_chain(_body: &[&str], event_id: Box<EventId>) -> Re
let room_id = <&RoomId>::try_from(room_id_str) let room_id = <&RoomId>::try_from(room_id_str)
.map_err(|_| Error::bad_database("Invalid room id field in event in database"))?; .map_err(|_| Error::bad_database("Invalid room id field in event in database"))?;
let start = Instant::now(); let start = Instant::now();
let count = services() let count = self
.services
.rooms .rooms
.auth_chain .auth_chain
.event_ids_iter(room_id, vec![event_id]) .event_ids_iter(room_id, vec![event_id])
.await? .await?
.count(); .count();
let elapsed = start.elapsed(); let elapsed = start.elapsed();
Ok(RoomMessageEventContent::text_plain(format!( Ok(RoomMessageEventContent::text_plain(format!(
"Loaded auth chain with length {count} in {elapsed:?}" "Loaded auth chain with length {count} in {elapsed:?}"
@ -52,14 +58,16 @@ pub(super) async fn get_auth_chain(_body: &[&str], event_id: Box<EventId>) -> Re
} }
} }
pub(super) async fn parse_pdu(body: &[&str]) -> Result<RoomMessageEventContent> { #[admin_command]
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { pub(super) async fn parse_pdu(&self) -> Result<RoomMessageEventContent> {
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
{
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.", "Expected code block in command body. Add --help for details.",
)); ));
} }
let string = body[1..body.len().saturating_sub(1)].join("\n"); let string = self.body[1..self.body.len().saturating_sub(1)].join("\n");
match serde_json::from_str(&string) { match serde_json::from_str(&string) {
Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) { Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
Ok(hash) => { Ok(hash) => {
@ -80,15 +88,17 @@ pub(super) async fn parse_pdu(body: &[&str]) -> Result<RoomMessageEventContent>
} }
} }
pub(super) async fn get_pdu(_body: &[&str], event_id: Box<EventId>) -> Result<RoomMessageEventContent> { #[admin_command]
pub(super) async fn get_pdu(&self, event_id: Box<EventId>) -> Result<RoomMessageEventContent> {
let mut outlier = false; let mut outlier = false;
let mut pdu_json = services() let mut pdu_json = self
.services
.rooms .rooms
.timeline .timeline
.get_non_outlier_pdu_json(&event_id)?; .get_non_outlier_pdu_json(&event_id)?;
if pdu_json.is_none() { if pdu_json.is_none() {
outlier = true; outlier = true;
pdu_json = services().rooms.timeline.get_pdu_json(&event_id)?; pdu_json = self.services.rooms.timeline.get_pdu_json(&event_id)?;
} }
match pdu_json { match pdu_json {
Some(json) => { Some(json) => {
@ -107,39 +117,42 @@ pub(super) async fn get_pdu(_body: &[&str], event_id: Box<EventId>) -> Result<Ro
} }
} }
#[admin_command]
pub(super) async fn get_remote_pdu_list( pub(super) async fn get_remote_pdu_list(
body: &[&str], server: Box<ServerName>, force: bool, &self, server: Box<ServerName>, force: bool,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
if !services().globals.config.allow_federation { if !self.services.globals.config.allow_federation {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Federation is disabled on this homeserver.", "Federation is disabled on this homeserver.",
)); ));
} }
if server == services().globals.server_name() { if server == self.services.globals.server_name() {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs from \ "Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs from \
the database.", the database.",
)); ));
} }
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
{
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.", "Expected code block in command body. Add --help for details.",
)); ));
} }
let list = body let list = self
.body
.iter() .iter()
.collect::<Vec<_>>() .collect::<Vec<_>>()
.drain(1..body.len().saturating_sub(1)) .drain(1..self.body.len().saturating_sub(1))
.filter_map(|pdu| EventId::parse(pdu).ok()) .filter_map(|pdu| EventId::parse(pdu).ok())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
for pdu in list { for pdu in list {
if force { if force {
if let Err(e) = get_remote_pdu(&[], Box::from(pdu), server.clone()).await { if let Err(e) = self.get_remote_pdu(Box::from(pdu), server.clone()).await {
services() self.services
.admin .admin
.send_message(RoomMessageEventContent::text_plain(format!( .send_message(RoomMessageEventContent::text_plain(format!(
"Failed to get remote PDU, ignoring error: {e}" "Failed to get remote PDU, ignoring error: {e}"
@ -148,29 +161,31 @@ pub(super) async fn get_remote_pdu_list(
warn!(%e, "Failed to get remote PDU, ignoring error"); warn!(%e, "Failed to get remote PDU, ignoring error");
} }
} else { } else {
get_remote_pdu(&[], Box::from(pdu), server.clone()).await?; self.get_remote_pdu(Box::from(pdu), server.clone()).await?;
} }
} }
Ok(RoomMessageEventContent::text_plain("Fetched list of remote PDUs.")) Ok(RoomMessageEventContent::text_plain("Fetched list of remote PDUs."))
} }
#[admin_command]
pub(super) async fn get_remote_pdu( pub(super) async fn get_remote_pdu(
_body: &[&str], event_id: Box<EventId>, server: Box<ServerName>, &self, event_id: Box<EventId>, server: Box<ServerName>,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
if !services().globals.config.allow_federation { if !self.services.globals.config.allow_federation {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Federation is disabled on this homeserver.", "Federation is disabled on this homeserver.",
)); ));
} }
if server == services().globals.server_name() { if server == self.services.globals.server_name() {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs.", "Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs.",
)); ));
} }
match services() match self
.services
.sending .sending
.send_federation_request( .send_federation_request(
&server, &server,
@ -191,7 +206,8 @@ pub(super) async fn get_remote_pdu(
debug!("Attempting to parse PDU: {:?}", &response.pdu); debug!("Attempting to parse PDU: {:?}", &response.pdu);
let parsed_pdu = { let parsed_pdu = {
let parsed_result = services() let parsed_result = self
.services
.rooms .rooms
.event_handler .event_handler
.parse_incoming_pdu(&response.pdu); .parse_incoming_pdu(&response.pdu);
@ -212,7 +228,7 @@ pub(super) async fn get_remote_pdu(
let pub_key_map = RwLock::new(BTreeMap::new()); let pub_key_map = RwLock::new(BTreeMap::new());
debug!("Attempting to fetch homeserver signing keys for {server}"); debug!("Attempting to fetch homeserver signing keys for {server}");
services() self.services
.rooms .rooms
.event_handler .event_handler
.fetch_required_signing_keys(parsed_pdu.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map) .fetch_required_signing_keys(parsed_pdu.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map)
@ -222,7 +238,7 @@ pub(super) async fn get_remote_pdu(
}); });
info!("Attempting to handle event ID {event_id} as backfilled PDU"); info!("Attempting to handle event ID {event_id} as backfilled PDU");
services() self.services
.rooms .rooms
.timeline .timeline
.backfill_pdu(&server, response.pdu, &pub_key_map) .backfill_pdu(&server, response.pdu, &pub_key_map)
@ -241,9 +257,11 @@ pub(super) async fn get_remote_pdu(
} }
} }
pub(super) async fn get_room_state(_body: &[&str], room: OwnedRoomOrAliasId) -> Result<RoomMessageEventContent> { #[admin_command]
let room_id = services().rooms.alias.resolve(&room).await?; pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result<RoomMessageEventContent> {
let room_state = services() let room_id = self.services.rooms.alias.resolve(&room).await?;
let room_state = self
.services
.rooms .rooms
.state_accessor .state_accessor
.room_state_full(&room_id) .room_state_full(&room_id)
@ -268,8 +286,9 @@ pub(super) async fn get_room_state(_body: &[&str], room: OwnedRoomOrAliasId) ->
Ok(RoomMessageEventContent::notice_markdown(format!("```json\n{json}\n```"))) Ok(RoomMessageEventContent::notice_markdown(format!("```json\n{json}\n```")))
} }
pub(super) async fn ping(_body: &[&str], server: Box<ServerName>) -> Result<RoomMessageEventContent> { #[admin_command]
if server == services().globals.server_name() { pub(super) async fn ping(&self, server: Box<ServerName>) -> Result<RoomMessageEventContent> {
if server == self.services.globals.server_name() {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Not allowed to send federation requests to ourselves.", "Not allowed to send federation requests to ourselves.",
)); ));
@ -277,7 +296,8 @@ pub(super) async fn ping(_body: &[&str], server: Box<ServerName>) -> Result<Room
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
match services() match self
.services
.sending .sending
.send_federation_request(&server, ruma::api::federation::discovery::get_server_version::v1::Request {}) .send_federation_request(&server, ruma::api::federation::discovery::get_server_version::v1::Request {})
.await .await
@ -306,23 +326,23 @@ pub(super) async fn ping(_body: &[&str], server: Box<ServerName>) -> Result<Room
} }
} }
pub(super) async fn force_device_list_updates(_body: &[&str]) -> Result<RoomMessageEventContent> { #[admin_command]
pub(super) async fn force_device_list_updates(&self) -> Result<RoomMessageEventContent> {
// Force E2EE device list updates for all users // Force E2EE device list updates for all users
for user_id in services().users.iter().filter_map(Result::ok) { for user_id in self.services.users.iter().filter_map(Result::ok) {
services().users.mark_device_key_update(&user_id)?; self.services.users.mark_device_key_update(&user_id)?;
} }
Ok(RoomMessageEventContent::text_plain( Ok(RoomMessageEventContent::text_plain(
"Marked all devices for all users as having new keys to update", "Marked all devices for all users as having new keys to update",
)) ))
} }
pub(super) async fn change_log_level( #[admin_command]
_body: &[&str], filter: Option<String>, reset: bool, pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool) -> Result<RoomMessageEventContent> {
) -> Result<RoomMessageEventContent> {
let handles = &["console"]; let handles = &["console"];
if reset { if reset {
let old_filter_layer = match EnvFilter::try_new(&services().globals.config.log) { let old_filter_layer = match EnvFilter::try_new(&self.services.globals.config.log) {
Ok(s) => s, Ok(s) => s,
Err(e) => { Err(e) => {
return Ok(RoomMessageEventContent::text_plain(format!( return Ok(RoomMessageEventContent::text_plain(format!(
@ -331,7 +351,8 @@ pub(super) async fn change_log_level(
}, },
}; };
match services() match self
.services
.server .server
.log .log
.reload .reload
@ -340,7 +361,7 @@ pub(super) async fn change_log_level(
Ok(()) => { Ok(()) => {
return Ok(RoomMessageEventContent::text_plain(format!( return Ok(RoomMessageEventContent::text_plain(format!(
"Successfully changed log level back to config value {}", "Successfully changed log level back to config value {}",
services().globals.config.log self.services.globals.config.log
))); )));
}, },
Err(e) => { Err(e) => {
@ -361,7 +382,8 @@ pub(super) async fn change_log_level(
}, },
}; };
match services() match self
.services
.server .server
.log .log
.reload .reload
@ -381,19 +403,21 @@ pub(super) async fn change_log_level(
Ok(RoomMessageEventContent::text_plain("No log level was specified.")) Ok(RoomMessageEventContent::text_plain("No log level was specified."))
} }
pub(super) async fn sign_json(body: &[&str]) -> Result<RoomMessageEventContent> { #[admin_command]
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { pub(super) async fn sign_json(&self) -> Result<RoomMessageEventContent> {
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
{
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.", "Expected code block in command body. Add --help for details.",
)); ));
} }
let string = body[1..body.len().checked_sub(1).unwrap()].join("\n"); let string = self.body[1..self.body.len().checked_sub(1).unwrap()].join("\n");
match serde_json::from_str(&string) { match serde_json::from_str(&string) {
Ok(mut value) => { Ok(mut value) => {
ruma::signatures::sign_json( ruma::signatures::sign_json(
services().globals.server_name().as_str(), self.services.globals.server_name().as_str(),
services().globals.keypair(), self.services.globals.keypair(),
&mut value, &mut value,
) )
.expect("our request json is what ruma expects"); .expect("our request json is what ruma expects");
@ -404,19 +428,21 @@ pub(super) async fn sign_json(body: &[&str]) -> Result<RoomMessageEventContent>
} }
} }
pub(super) async fn verify_json(body: &[&str]) -> Result<RoomMessageEventContent> { #[admin_command]
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { pub(super) async fn verify_json(&self) -> Result<RoomMessageEventContent> {
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
{
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.", "Expected code block in command body. Add --help for details.",
)); ));
} }
let string = body[1..body.len().checked_sub(1).unwrap()].join("\n"); let string = self.body[1..self.body.len().checked_sub(1).unwrap()].join("\n");
match serde_json::from_str(&string) { match serde_json::from_str(&string) {
Ok(value) => { Ok(value) => {
let pub_key_map = RwLock::new(BTreeMap::new()); let pub_key_map = RwLock::new(BTreeMap::new());
services() self.services
.rooms .rooms
.event_handler .event_handler
.fetch_required_signing_keys([&value], &pub_key_map) .fetch_required_signing_keys([&value], &pub_key_map)
@ -434,19 +460,22 @@ pub(super) async fn verify_json(body: &[&str]) -> Result<RoomMessageEventContent
} }
} }
#[tracing::instrument(skip(_body))] #[admin_command]
pub(super) async fn first_pdu_in_room(_body: &[&str], room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { #[tracing::instrument(skip(self))]
if !services() pub(super) async fn first_pdu_in_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
if !self
.services
.rooms .rooms
.state_cache .state_cache
.server_in_room(&services().globals.config.server_name, &room_id)? .server_in_room(&self.services.globals.config.server_name, &room_id)?
{ {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"We are not participating in the room / we don't know about the room ID.", "We are not participating in the room / we don't know about the room ID.",
)); ));
} }
let first_pdu = services() let first_pdu = self
.services
.rooms .rooms
.timeline .timeline
.first_pdu_in_room(&room_id)? .first_pdu_in_room(&room_id)?
@ -455,19 +484,22 @@ pub(super) async fn first_pdu_in_room(_body: &[&str], room_id: Box<RoomId>) -> R
Ok(RoomMessageEventContent::text_plain(format!("{first_pdu:?}"))) Ok(RoomMessageEventContent::text_plain(format!("{first_pdu:?}")))
} }
#[tracing::instrument(skip(_body))] #[admin_command]
pub(super) async fn latest_pdu_in_room(_body: &[&str], room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { #[tracing::instrument(skip(self))]
if !services() pub(super) async fn latest_pdu_in_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
if !self
.services
.rooms .rooms
.state_cache .state_cache
.server_in_room(&services().globals.config.server_name, &room_id)? .server_in_room(&self.services.globals.config.server_name, &room_id)?
{ {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"We are not participating in the room / we don't know about the room ID.", "We are not participating in the room / we don't know about the room ID.",
)); ));
} }
let latest_pdu = services() let latest_pdu = self
.services
.rooms .rooms
.timeline .timeline
.latest_pdu_in_room(&room_id)? .latest_pdu_in_room(&room_id)?
@ -476,32 +508,36 @@ pub(super) async fn latest_pdu_in_room(_body: &[&str], room_id: Box<RoomId>) ->
Ok(RoomMessageEventContent::text_plain(format!("{latest_pdu:?}"))) Ok(RoomMessageEventContent::text_plain(format!("{latest_pdu:?}")))
} }
#[tracing::instrument(skip(_body))] #[admin_command]
#[tracing::instrument(skip(self))]
pub(super) async fn force_set_room_state_from_server( pub(super) async fn force_set_room_state_from_server(
_body: &[&str], room_id: Box<RoomId>, server_name: Box<ServerName>, &self, room_id: Box<RoomId>, server_name: Box<ServerName>,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
if !services() if !self
.services
.rooms .rooms
.state_cache .state_cache
.server_in_room(&services().globals.config.server_name, &room_id)? .server_in_room(&self.services.globals.config.server_name, &room_id)?
{ {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"We are not participating in the room / we don't know about the room ID.", "We are not participating in the room / we don't know about the room ID.",
)); ));
} }
let first_pdu = services() let first_pdu = self
.services
.rooms .rooms
.timeline .timeline
.latest_pdu_in_room(&room_id)? .latest_pdu_in_room(&room_id)?
.ok_or_else(|| Error::bad_database("Failed to find the latest PDU in database"))?; .ok_or_else(|| Error::bad_database("Failed to find the latest PDU in database"))?;
let room_version = services().rooms.state.get_room_version(&room_id)?; let room_version = self.services.rooms.state.get_room_version(&room_id)?;
let mut state: HashMap<u64, Arc<EventId>> = HashMap::new(); let mut state: HashMap<u64, Arc<EventId>> = HashMap::new();
let pub_key_map = RwLock::new(BTreeMap::new()); let pub_key_map = RwLock::new(BTreeMap::new());
let remote_state_response = services() let remote_state_response = self
.services
.sending .sending
.send_federation_request( .send_federation_request(
&server_name, &server_name,
@ -515,7 +551,7 @@ pub(super) async fn force_set_room_state_from_server(
let mut events = Vec::with_capacity(remote_state_response.pdus.len()); let mut events = Vec::with_capacity(remote_state_response.pdus.len());
for pdu in remote_state_response.pdus.clone() { for pdu in remote_state_response.pdus.clone() {
events.push(match services().rooms.event_handler.parse_incoming_pdu(&pdu) { events.push(match self.services.rooms.event_handler.parse_incoming_pdu(&pdu) {
Ok(t) => t, Ok(t) => t,
Err(e) => { Err(e) => {
warn!("Could not parse PDU, ignoring: {e}"); warn!("Could not parse PDU, ignoring: {e}");
@ -525,7 +561,7 @@ pub(super) async fn force_set_room_state_from_server(
} }
info!("Fetching required signing keys for all the state events we got"); info!("Fetching required signing keys for all the state events we got");
services() self.services
.rooms .rooms
.event_handler .event_handler
.fetch_required_signing_keys(events.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map) .fetch_required_signing_keys(events.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map)
@ -535,7 +571,7 @@ pub(super) async fn force_set_room_state_from_server(
for result in remote_state_response for result in remote_state_response
.pdus .pdus
.iter() .iter()
.map(|pdu| validate_and_add_event_id(services(), pdu, &room_version, &pub_key_map)) .map(|pdu| validate_and_add_event_id(self.services, pdu, &room_version, &pub_key_map))
{ {
let Ok((event_id, value)) = result.await else { let Ok((event_id, value)) = result.await else {
continue; continue;
@ -546,12 +582,13 @@ pub(super) async fn force_set_room_state_from_server(
Error::BadServerResponse("Invalid PDU in send_join response.") Error::BadServerResponse("Invalid PDU in send_join response.")
})?; })?;
services() self.services
.rooms .rooms
.outlier .outlier
.add_pdu_outlier(&event_id, &value)?; .add_pdu_outlier(&event_id, &value)?;
if let Some(state_key) = &pdu.state_key { if let Some(state_key) = &pdu.state_key {
let shortstatekey = services() let shortstatekey = self
.services
.rooms .rooms
.short .short
.get_or_create_shortstatekey(&pdu.kind.to_string().into(), state_key)?; .get_or_create_shortstatekey(&pdu.kind.to_string().into(), state_key)?;
@ -563,32 +600,34 @@ pub(super) async fn force_set_room_state_from_server(
for result in remote_state_response for result in remote_state_response
.auth_chain .auth_chain
.iter() .iter()
.map(|pdu| validate_and_add_event_id(services(), pdu, &room_version, &pub_key_map)) .map(|pdu| validate_and_add_event_id(self.services, pdu, &room_version, &pub_key_map))
{ {
let Ok((event_id, value)) = result.await else { let Ok((event_id, value)) = result.await else {
continue; continue;
}; };
services() self.services
.rooms .rooms
.outlier .outlier
.add_pdu_outlier(&event_id, &value)?; .add_pdu_outlier(&event_id, &value)?;
} }
let new_room_state = services() let new_room_state = self
.services
.rooms .rooms
.event_handler .event_handler
.resolve_state(room_id.clone().as_ref(), &room_version, state) .resolve_state(room_id.clone().as_ref(), &room_version, state)
.await?; .await?;
info!("Forcing new room state"); info!("Forcing new room state");
let (short_state_hash, new, removed) = services() let (short_state_hash, new, removed) = self
.services
.rooms .rooms
.state_compressor .state_compressor
.save_state(room_id.clone().as_ref(), new_room_state)?; .save_state(room_id.clone().as_ref(), new_room_state)?;
let state_lock = services().rooms.state.mutex.lock(&room_id).await; let state_lock = self.services.rooms.state.mutex.lock(&room_id).await;
services() self.services
.rooms .rooms
.state .state
.force_state(room_id.clone().as_ref(), short_state_hash, new, removed, &state_lock) .force_state(room_id.clone().as_ref(), short_state_hash, new, removed, &state_lock)
@ -598,7 +637,10 @@ pub(super) async fn force_set_room_state_from_server(
"Updating joined counts for room just in case (e.g. we may have found a difference in the room's \ "Updating joined counts for room just in case (e.g. we may have found a difference in the room's \
m.room.member state" m.room.member state"
); );
services().rooms.state_cache.update_joined_count(&room_id)?; self.services
.rooms
.state_cache
.update_joined_count(&room_id)?;
drop(state_lock); drop(state_lock);
@ -607,28 +649,30 @@ pub(super) async fn force_set_room_state_from_server(
)) ))
} }
#[admin_command]
pub(super) async fn get_signing_keys( pub(super) async fn get_signing_keys(
_body: &[&str], server_name: Option<Box<ServerName>>, _cached: bool, &self, server_name: Option<Box<ServerName>>, _cached: bool,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
let server_name = server_name.unwrap_or_else(|| services().server.config.server_name.clone().into()); let server_name = server_name.unwrap_or_else(|| self.services.server.config.server_name.clone().into());
let signing_keys = services().globals.signing_keys_for(&server_name)?; let signing_keys = self.services.globals.signing_keys_for(&server_name)?;
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"```rs\n{signing_keys:#?}\n```" "```rs\n{signing_keys:#?}\n```"
))) )))
} }
#[admin_command]
#[allow(dead_code)] #[allow(dead_code)]
pub(super) async fn get_verify_keys( pub(super) async fn get_verify_keys(
_body: &[&str], server_name: Option<Box<ServerName>>, cached: bool, &self, server_name: Option<Box<ServerName>>, cached: bool,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
let server_name = server_name.unwrap_or_else(|| services().server.config.server_name.clone().into()); let server_name = server_name.unwrap_or_else(|| self.services.server.config.server_name.clone().into());
let mut out = String::new(); let mut out = String::new();
if cached { if cached {
writeln!(out, "| Key ID | VerifyKey |")?; writeln!(out, "| Key ID | VerifyKey |")?;
writeln!(out, "| --- | --- |")?; writeln!(out, "| --- | --- |")?;
for (key_id, verify_key) in services().globals.verify_keys_for(&server_name)? { for (key_id, verify_key) in self.services.globals.verify_keys_for(&server_name)? {
writeln!(out, "| {key_id} | {verify_key:?} |")?; writeln!(out, "| {key_id} | {verify_key:?} |")?;
} }
@ -636,7 +680,8 @@ pub(super) async fn get_verify_keys(
} }
let signature_ids: Vec<String> = Vec::new(); let signature_ids: Vec<String> = Vec::new();
let keys = services() let keys = self
.services
.rooms .rooms
.event_handler .event_handler
.fetch_signing_keys_for_server(&server_name, signature_ids) .fetch_signing_keys_for_server(&server_name, signature_ids)
@ -651,16 +696,17 @@ pub(super) async fn get_verify_keys(
Ok(RoomMessageEventContent::notice_markdown(out)) Ok(RoomMessageEventContent::notice_markdown(out))
} }
#[admin_command]
pub(super) async fn resolve_true_destination( pub(super) async fn resolve_true_destination(
_body: &[&str], server_name: Box<ServerName>, no_cache: bool, &self, server_name: Box<ServerName>, no_cache: bool,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
if !services().globals.config.allow_federation { if !self.services.globals.config.allow_federation {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Federation is disabled on this homeserver.", "Federation is disabled on this homeserver.",
)); ));
} }
if server_name == services().globals.config.server_name { if server_name == self.services.globals.config.server_name {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs.", "Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs.",
)); ));
@ -672,12 +718,13 @@ pub(super) async fn resolve_true_destination(
&& matches!(data.span_name(), "actual" | "well-known" | "srv") && matches!(data.span_name(), "actual" | "well-known" | "srv")
}; };
let state = &services().server.log.capture; let state = &self.services.server.log.capture;
let logs = Arc::new(Mutex::new(String::new())); let logs = Arc::new(Mutex::new(String::new()));
let capture = Capture::new(state, Some(filter), capture::fmt_markdown(logs.clone())); let capture = Capture::new(state, Some(filter), capture::fmt_markdown(logs.clone()));
let capture_scope = capture.start(); let capture_scope = capture.start();
let actual = services() let actual = self
.services
.resolver .resolver
.resolve_actual_dest(&server_name, !no_cache) .resolve_actual_dest(&server_name, !no_cache)
.await?; .await?;
@ -692,7 +739,8 @@ pub(super) async fn resolve_true_destination(
Ok(RoomMessageEventContent::text_markdown(msg)) Ok(RoomMessageEventContent::text_markdown(msg))
} }
pub(super) async fn memory_stats(_body: &[&str]) -> Result<RoomMessageEventContent> { #[admin_command]
pub(super) async fn memory_stats(&self) -> Result<RoomMessageEventContent> {
let html_body = conduit::alloc::memory_stats(); let html_body = conduit::alloc::memory_stats();
if html_body.is_none() { if html_body.is_none() {
@ -708,8 +756,9 @@ pub(super) async fn memory_stats(_body: &[&str]) -> Result<RoomMessageEventConte
} }
#[cfg(tokio_unstable)] #[cfg(tokio_unstable)]
pub(super) async fn runtime_metrics(_body: &[&str]) -> Result<RoomMessageEventContent> { #[admin_command]
let out = services().server.metrics.runtime_metrics().map_or_else( pub(super) async fn runtime_metrics(&self) -> Result<RoomMessageEventContent> {
let out = self.services.server.metrics.runtime_metrics().map_or_else(
|| "Runtime metrics are not available.".to_owned(), || "Runtime metrics are not available.".to_owned(),
|metrics| format!("```rs\n{metrics:#?}\n```"), |metrics| format!("```rs\n{metrics:#?}\n```"),
); );
@ -718,15 +767,17 @@ pub(super) async fn runtime_metrics(_body: &[&str]) -> Result<RoomMessageEventCo
} }
#[cfg(not(tokio_unstable))] #[cfg(not(tokio_unstable))]
pub(super) async fn runtime_metrics(_body: &[&str]) -> Result<RoomMessageEventContent> { #[admin_command]
pub(super) async fn runtime_metrics(&self) -> Result<RoomMessageEventContent> {
Ok(RoomMessageEventContent::text_markdown( Ok(RoomMessageEventContent::text_markdown(
"Runtime metrics require building with `tokio_unstable`.", "Runtime metrics require building with `tokio_unstable`.",
)) ))
} }
#[cfg(tokio_unstable)] #[cfg(tokio_unstable)]
pub(super) async fn runtime_interval(_body: &[&str]) -> Result<RoomMessageEventContent> { #[admin_command]
let out = services().server.metrics.runtime_interval().map_or_else( pub(super) async fn runtime_interval(&self) -> Result<RoomMessageEventContent> {
let out = self.services.server.metrics.runtime_interval().map_or_else(
|| "Runtime metrics are not available.".to_owned(), || "Runtime metrics are not available.".to_owned(),
|metrics| format!("```rs\n{metrics:#?}\n```"), |metrics| format!("```rs\n{metrics:#?}\n```"),
); );
@ -735,18 +786,21 @@ pub(super) async fn runtime_interval(_body: &[&str]) -> Result<RoomMessageEventC
} }
#[cfg(not(tokio_unstable))] #[cfg(not(tokio_unstable))]
pub(super) async fn runtime_interval(_body: &[&str]) -> Result<RoomMessageEventContent> { #[admin_command]
pub(super) async fn runtime_interval(&self) -> Result<RoomMessageEventContent> {
Ok(RoomMessageEventContent::text_markdown( Ok(RoomMessageEventContent::text_markdown(
"Runtime metrics require building with `tokio_unstable`.", "Runtime metrics require building with `tokio_unstable`.",
)) ))
} }
pub(super) async fn time(_body: &[&str]) -> Result<RoomMessageEventContent> { #[admin_command]
pub(super) async fn time(&self) -> Result<RoomMessageEventContent> {
let now = SystemTime::now(); let now = SystemTime::now();
Ok(RoomMessageEventContent::text_markdown(utils::time::format(now, "%+"))) Ok(RoomMessageEventContent::text_markdown(utils::time::format(now, "%+")))
} }
pub(super) async fn list_dependencies(_body: &[&str], names: bool) -> Result<RoomMessageEventContent> { #[admin_command]
pub(super) async fn list_dependencies(&self, names: bool) -> Result<RoomMessageEventContent> {
if names { if names {
let out = info::cargo::dependencies_names().join(" "); let out = info::cargo::dependencies_names().join(" ");
return Ok(RoomMessageEventContent::notice_markdown(out)); return Ok(RoomMessageEventContent::notice_markdown(out));

View file

@ -3,10 +3,10 @@ pub(crate) mod tester;
use clap::Subcommand; use clap::Subcommand;
use conduit::Result; use conduit::Result;
use conduit_macros::admin_command_dispatch; use ruma::{EventId, OwnedRoomOrAliasId, RoomId, ServerName};
use ruma::{events::room::message::RoomMessageEventContent, EventId, OwnedRoomOrAliasId, RoomId, ServerName};
use self::{commands::*, tester::TesterCommand}; use self::tester::TesterCommand;
use crate::admin_command_dispatch;
#[admin_command_dispatch] #[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]

View file

@ -1,33 +1,29 @@
use ruma::events::room::message::RoomMessageEventContent; use ruma::events::room::message::RoomMessageEventContent;
use crate::Result; use crate::{admin_command, admin_command_dispatch, Result};
#[admin_command_dispatch]
#[derive(Debug, clap::Subcommand)] #[derive(Debug, clap::Subcommand)]
pub(crate) enum TesterCommand { pub(crate) enum TesterCommand {
Tester, Tester,
Timer, Timer,
} }
pub(super) async fn process(command: TesterCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
match command {
TesterCommand::Tester => tester(body).await,
TesterCommand::Timer => timer(body).await,
}
}
#[inline(never)] #[inline(never)]
#[rustfmt::skip] #[rustfmt::skip]
#[allow(unused_variables)] #[allow(unused_variables)]
async fn tester(body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
async fn tester(&self) -> Result<RoomMessageEventContent> {
Ok(RoomMessageEventContent::notice_plain("completed")) Ok(RoomMessageEventContent::notice_plain("completed"))
} }
#[inline(never)] #[inline(never)]
#[rustfmt::skip] #[rustfmt::skip]
async fn timer(body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
async fn timer(&self) -> Result<RoomMessageEventContent> {
let started = std::time::Instant::now(); let started = std::time::Instant::now();
timed(&body); timed(self.body);
let elapsed = started.elapsed(); let elapsed = started.elapsed();
Ok(RoomMessageEventContent::notice_plain(format!("completed in {elapsed:#?}"))) Ok(RoomMessageEventContent::notice_plain(format!("completed in {elapsed:#?}")))

View file

@ -1,21 +1,26 @@
use std::fmt::Write; use std::fmt::Write;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomId, ServerName, UserId}; use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomId, ServerName, UserId};
use crate::{escape_html, get_room_info, services, Result}; use crate::{admin_command, escape_html, get_room_info};
pub(super) async fn disable_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { #[admin_command]
services().rooms.metadata.disable_room(&room_id, true)?; pub(super) async fn disable_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
self.services.rooms.metadata.disable_room(&room_id, true)?;
Ok(RoomMessageEventContent::text_plain("Room disabled.")) Ok(RoomMessageEventContent::text_plain("Room disabled."))
} }
pub(super) async fn enable_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { #[admin_command]
services().rooms.metadata.disable_room(&room_id, false)?; pub(super) async fn enable_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
self.services.rooms.metadata.disable_room(&room_id, false)?;
Ok(RoomMessageEventContent::text_plain("Room enabled.")) Ok(RoomMessageEventContent::text_plain("Room enabled."))
} }
pub(super) async fn incoming_federation(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
let map = services() pub(super) async fn incoming_federation(&self) -> Result<RoomMessageEventContent> {
let map = self
.services
.rooms .rooms
.event_handler .event_handler
.federation_handletime .federation_handletime
@ -31,10 +36,10 @@ pub(super) async fn incoming_federation(_body: Vec<&str>) -> Result<RoomMessageE
Ok(RoomMessageEventContent::text_plain(&msg)) Ok(RoomMessageEventContent::text_plain(&msg))
} }
pub(super) async fn fetch_support_well_known( #[admin_command]
_body: Vec<&str>, server_name: Box<ServerName>, pub(super) async fn fetch_support_well_known(&self, server_name: Box<ServerName>) -> Result<RoomMessageEventContent> {
) -> Result<RoomMessageEventContent> { let response = self
let response = services() .services
.client .client
.default .default
.get(format!("https://{server_name}/.well-known/matrix/support")) .get(format!("https://{server_name}/.well-known/matrix/support"))
@ -72,25 +77,27 @@ pub(super) async fn fetch_support_well_known(
))) )))
} }
pub(super) async fn remote_user_in_rooms(_body: Vec<&str>, user_id: Box<UserId>) -> Result<RoomMessageEventContent> { #[admin_command]
if user_id.server_name() == services().globals.config.server_name { pub(super) async fn remote_user_in_rooms(&self, user_id: Box<UserId>) -> Result<RoomMessageEventContent> {
if user_id.server_name() == self.services.globals.config.server_name {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"User belongs to our server, please use `list-joined-rooms` user admin command instead.", "User belongs to our server, please use `list-joined-rooms` user admin command instead.",
)); ));
} }
if !services().users.exists(&user_id)? { if !self.services.users.exists(&user_id)? {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Remote user does not exist in our database.", "Remote user does not exist in our database.",
)); ));
} }
let mut rooms: Vec<(OwnedRoomId, u64, String)> = services() let mut rooms: Vec<(OwnedRoomId, u64, String)> = self
.services
.rooms .rooms
.state_cache .state_cache
.rooms_joined(&user_id) .rooms_joined(&user_id)
.filter_map(Result::ok) .filter_map(Result::ok)
.map(|room_id| get_room_info(services(), &room_id)) .map(|room_id| get_room_info(self.services, &room_id))
.collect(); .collect();
if rooms.is_empty() { if rooms.is_empty() {

View file

@ -2,10 +2,11 @@ mod commands;
use clap::Subcommand; use clap::Subcommand;
use conduit::Result; use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, RoomId, ServerName, UserId}; use ruma::{RoomId, ServerName, UserId};
use self::commands::*; use crate::admin_command_dispatch;
#[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub(super) enum FederationCommand { pub(super) enum FederationCommand {
/// - List all rooms we are currently handling an incoming pdu from /// - List all rooms we are currently handling an incoming pdu from
@ -39,21 +40,3 @@ pub(super) enum FederationCommand {
user_id: Box<UserId>, user_id: Box<UserId>,
}, },
} }
pub(super) async fn process(command: FederationCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
FederationCommand::DisableRoom {
room_id,
} => disable_room(body, room_id).await?,
FederationCommand::EnableRoom {
room_id,
} => enable_room(body, room_id).await?,
FederationCommand::IncomingFederation => incoming_federation(body).await?,
FederationCommand::FetchSupportWellKnown {
server_name,
} => fetch_support_well_known(body, server_name).await?,
FederationCommand::RemoteUserInRooms {
user_id,
} => remote_user_in_rooms(body, user_id).await?,
})
}

View file

@ -15,10 +15,10 @@ use service::{
Services, Services,
}; };
use crate::{admin, admin::AdminCommand}; use crate::{admin, admin::AdminCommand, Command};
struct Handler<'a> { struct Handler {
services: &'a Services, services: &'static Services,
} }
#[must_use] #[must_use]
@ -68,13 +68,12 @@ fn reply(mut content: RoomMessageEventContent, reply_id: Option<OwnedEventId>) -
Some(content) Some(content)
} }
impl Handler<'_> { impl Handler {
// Parse and process a message from the admin room // Parse and process a message from the admin room
async fn process(&self, msg: &str) -> CommandOutput { async fn process(&self, msg: &str) -> CommandOutput {
let mut lines = msg.lines().filter(|l| !l.trim().is_empty()); let mut lines = msg.lines().filter(|l| !l.trim().is_empty());
let command = lines.next().expect("each string has at least one line"); let command = lines.next().expect("each string has at least one line");
let body = lines.collect::<Vec<_>>(); let (parsed, body) = match self.parse_command(command) {
let parsed = match self.parse_command(command) {
Ok(parsed) => parsed, Ok(parsed) => parsed,
Err(error) => { Err(error) => {
let server_name = self.services.globals.server_name(); let server_name = self.services.globals.server_name();
@ -84,7 +83,12 @@ impl Handler<'_> {
}; };
let timer = Instant::now(); let timer = Instant::now();
let result = Box::pin(admin::process(parsed, body)).await; let body: Vec<&str> = body.iter().map(String::as_str).collect();
let context = Command {
services: self.services,
body: &body,
};
let result = Box::pin(admin::process(parsed, &context)).await;
let elapsed = timer.elapsed(); let elapsed = timer.elapsed();
conduit::debug!(?command, ok = result.is_ok(), "command processed in {elapsed:?}"); conduit::debug!(?command, ok = result.is_ok(), "command processed in {elapsed:?}");
match result { match result {
@ -96,9 +100,10 @@ impl Handler<'_> {
} }
// Parse chat messages from the admin room into an AdminCommand object // Parse chat messages from the admin room into an AdminCommand object
fn parse_command(&self, command_line: &str) -> Result<AdminCommand, String> { fn parse_command(&self, command_line: &str) -> Result<(AdminCommand, Vec<String>), String> {
let argv = self.parse_line(command_line); let argv = self.parse_line(command_line);
AdminCommand::try_parse_from(argv).map_err(|error| error.to_string()) let com = AdminCommand::try_parse_from(&argv).map_err(|error| error.to_string())?;
Ok((com, argv))
} }
fn complete_command(&self, mut cmd: clap::Command, line: &str) -> String { fn complete_command(&self, mut cmd: clap::Command, line: &str) -> String {

View file

@ -1,11 +1,11 @@
use conduit::Result; use conduit::{debug, info, Result};
use ruma::{events::room::message::RoomMessageEventContent, EventId, MxcUri}; use ruma::{events::room::message::RoomMessageEventContent, EventId, MxcUri};
use tracing::{debug, info};
use crate::services; use crate::admin_command;
#[admin_command]
pub(super) async fn delete( pub(super) async fn delete(
_body: Vec<&str>, mxc: Option<Box<MxcUri>>, event_id: Option<Box<EventId>>, &self, mxc: Option<Box<MxcUri>>, event_id: Option<Box<EventId>>,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
if event_id.is_some() && mxc.is_some() { if event_id.is_some() && mxc.is_some() {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
@ -15,7 +15,7 @@ pub(super) async fn delete(
if let Some(mxc) = mxc { if let Some(mxc) = mxc {
debug!("Got MXC URL: {mxc}"); debug!("Got MXC URL: {mxc}");
services().media.delete(mxc.as_ref()).await?; self.services.media.delete(mxc.as_ref()).await?;
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Deleted the MXC from our database and on our filesystem.", "Deleted the MXC from our database and on our filesystem.",
@ -27,7 +27,7 @@ pub(super) async fn delete(
let mut mxc_deletion_count: usize = 0; let mut mxc_deletion_count: usize = 0;
// parsing the PDU for any MXC URLs begins here // parsing the PDU for any MXC URLs begins here
if let Some(event_json) = services().rooms.timeline.get_pdu_json(&event_id)? { if let Some(event_json) = self.services.rooms.timeline.get_pdu_json(&event_id)? {
if let Some(content_key) = event_json.get("content") { if let Some(content_key) = event_json.get("content") {
debug!("Event ID has \"content\"."); debug!("Event ID has \"content\".");
let content_obj = content_key.as_object(); let content_obj = content_key.as_object();
@ -123,7 +123,7 @@ pub(super) async fn delete(
} }
for mxc_url in mxc_urls { for mxc_url in mxc_urls {
services().media.delete(&mxc_url).await?; self.services.media.delete(&mxc_url).await?;
mxc_deletion_count = mxc_deletion_count.saturating_add(1); mxc_deletion_count = mxc_deletion_count.saturating_add(1);
} }
@ -138,23 +138,26 @@ pub(super) async fn delete(
)) ))
} }
pub(super) async fn delete_list(body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { pub(super) async fn delete_list(&self) -> Result<RoomMessageEventContent> {
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
{
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.", "Expected code block in command body. Add --help for details.",
)); ));
} }
let mxc_list = body let mxc_list = self
.clone() .body
.drain(1..body.len().checked_sub(1).unwrap()) .to_vec()
.drain(1..self.body.len().checked_sub(1).unwrap())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut mxc_deletion_count: usize = 0; let mut mxc_deletion_count: usize = 0;
for mxc in mxc_list { for mxc in mxc_list {
debug!("Deleting MXC {mxc} in bulk"); debug!("Deleting MXC {mxc} in bulk");
services().media.delete(mxc).await?; self.services.media.delete(mxc).await?;
mxc_deletion_count = mxc_deletion_count mxc_deletion_count = mxc_deletion_count
.checked_add(1) .checked_add(1)
.expect("mxc_deletion_count should not get this high"); .expect("mxc_deletion_count should not get this high");
@ -165,10 +168,10 @@ pub(super) async fn delete_list(body: Vec<&str>) -> Result<RoomMessageEventConte
))) )))
} }
pub(super) async fn delete_past_remote_media( #[admin_command]
_body: Vec<&str>, duration: String, force: bool, pub(super) async fn delete_past_remote_media(&self, duration: String, force: bool) -> Result<RoomMessageEventContent> {
) -> Result<RoomMessageEventContent> { let deleted_count = self
let deleted_count = services() .services
.media .media
.delete_all_remote_media_at_after_time(duration, force) .delete_all_remote_media_at_after_time(duration, force)
.await?; .await?;

View file

@ -2,10 +2,11 @@ mod commands;
use clap::Subcommand; use clap::Subcommand;
use conduit::Result; use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, EventId, MxcUri}; use ruma::{EventId, MxcUri};
use self::commands::*; use crate::admin_command_dispatch;
#[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub(super) enum MediaCommand { pub(super) enum MediaCommand {
/// - Deletes a single media file from our database and on the filesystem /// - Deletes a single media file from our database and on the filesystem
@ -36,17 +37,3 @@ pub(super) enum MediaCommand {
force: bool, force: bool,
}, },
} }
pub(super) async fn process(command: MediaCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
MediaCommand::Delete {
mxc,
event_id,
} => delete(body, mxc, event_id).await?,
MediaCommand::DeleteList => delete_list(body).await?,
MediaCommand::DeletePastRemoteMedia {
duration,
force,
} => delete_past_remote_media(body, duration, force).await?,
})
}

View file

@ -3,6 +3,7 @@
#![allow(clippy::enum_glob_use)] #![allow(clippy::enum_glob_use)]
pub(crate) mod admin; pub(crate) mod admin;
pub(crate) mod command;
pub(crate) mod handler; pub(crate) mod handler;
mod tests; mod tests;
pub(crate) mod utils; pub(crate) mod utils;
@ -22,9 +23,13 @@ extern crate conduit_core as conduit;
extern crate conduit_service as service; extern crate conduit_service as service;
pub(crate) use conduit::Result; pub(crate) use conduit::Result;
pub(crate) use conduit_macros::{admin_command, admin_command_dispatch};
pub(crate) use service::services; pub(crate) use service::services;
pub(crate) use crate::utils::{escape_html, get_room_info}; pub(crate) use crate::{
command::Command,
utils::{escape_html, get_room_info},
};
pub(crate) const PAGE_SIZE: usize = 100; pub(crate) const PAGE_SIZE: usize = 100;

View file

@ -1,18 +1,48 @@
use ruma::events::room::message::RoomMessageEventContent; use clap::Subcommand;
use conduit::Result;
use ruma::{
events::{room::message::RoomMessageEventContent, RoomAccountDataEventType},
RoomId, UserId,
};
use super::AccountData; use crate::Command;
use crate::{services, Result};
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/account_data.rs
pub(crate) enum AccountDataCommand {
/// - Returns all changes to the account data that happened after `since`.
ChangesSince {
/// Full user ID
user_id: Box<UserId>,
/// UNIX timestamp since (u64)
since: u64,
/// Optional room ID of the account data
room_id: Option<Box<RoomId>>,
},
/// - Searches the account data for a specific kind.
Get {
/// Full user ID
user_id: Box<UserId>,
/// Account data event type
kind: RoomAccountDataEventType,
/// Optional room ID of the account data
room_id: Option<Box<RoomId>>,
},
}
/// All the getters and iterators from src/database/key_value/account_data.rs /// All the getters and iterators from src/database/key_value/account_data.rs
pub(super) async fn account_data(subcommand: AccountData) -> Result<RoomMessageEventContent> { pub(super) async fn process(subcommand: AccountDataCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand { match subcommand {
AccountData::ChangesSince { AccountDataCommand::ChangesSince {
user_id, user_id,
since, since,
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services() let results = services
.account_data .account_data
.changes_since(room_id.as_deref(), &user_id, since)?; .changes_since(room_id.as_deref(), &user_id, since)?;
let query_time = timer.elapsed(); let query_time = timer.elapsed();
@ -21,13 +51,13 @@ pub(super) async fn account_data(subcommand: AccountData) -> Result<RoomMessageE
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
AccountData::Get { AccountDataCommand::Get {
user_id, user_id,
kind, kind,
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services() let results = services
.account_data .account_data
.get(room_id.as_deref(), &user_id, kind)?; .get(room_id.as_deref(), &user_id, kind)?;
let query_time = timer.elapsed(); let query_time = timer.elapsed();

View file

@ -1,16 +1,32 @@
use clap::Subcommand;
use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent; use ruma::events::room::message::RoomMessageEventContent;
use super::Appservice; use crate::Command;
use crate::{services, Result};
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/appservice.rs
pub(crate) enum AppserviceCommand {
/// - Gets the appservice registration info/details from the ID as a string
GetRegistration {
/// Appservice registration ID
appservice_id: Box<str>,
},
/// - Gets all appservice registrations with their ID and registration info
All,
}
/// All the getters and iterators from src/database/key_value/appservice.rs /// All the getters and iterators from src/database/key_value/appservice.rs
pub(super) async fn appservice(subcommand: Appservice) -> Result<RoomMessageEventContent> { pub(super) async fn process(subcommand: AppserviceCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand { match subcommand {
Appservice::GetRegistration { AppserviceCommand::GetRegistration {
appservice_id, appservice_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services() let results = services
.appservice .appservice
.db .db
.get_registration(appservice_id.as_ref()); .get_registration(appservice_id.as_ref());
@ -20,9 +36,9 @@ pub(super) async fn appservice(subcommand: Appservice) -> Result<RoomMessageEven
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
Appservice::All => { AppserviceCommand::All => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().appservice.all(); let results = services.appservice.all();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(

View file

@ -1,52 +1,73 @@
use ruma::events::room::message::RoomMessageEventContent; use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, ServerName};
use super::Globals; use crate::Command;
use crate::{services, Result};
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/globals.rs
pub(crate) enum GlobalsCommand {
DatabaseVersion,
CurrentCount,
LastCheckForUpdatesId,
LoadKeypair,
/// - This returns an empty `Ok(BTreeMap<..>)` when there are no keys found
/// for the server.
SigningKeysFor {
origin: Box<ServerName>,
},
}
/// All the getters and iterators from src/database/key_value/globals.rs /// All the getters and iterators from src/database/key_value/globals.rs
pub(super) async fn globals(subcommand: Globals) -> Result<RoomMessageEventContent> { pub(super) async fn process(subcommand: GlobalsCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand { match subcommand {
Globals::DatabaseVersion => { GlobalsCommand::DatabaseVersion => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().globals.db.database_version(); let results = services.globals.db.database_version();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
Globals::CurrentCount => { GlobalsCommand::CurrentCount => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().globals.db.current_count(); let results = services.globals.db.current_count();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
Globals::LastCheckForUpdatesId => { GlobalsCommand::LastCheckForUpdatesId => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().updates.last_check_for_updates_id(); let results = services.updates.last_check_for_updates_id();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
Globals::LoadKeypair => { GlobalsCommand::LoadKeypair => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().globals.db.load_keypair(); let results = services.globals.db.load_keypair();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
Globals::SigningKeysFor { GlobalsCommand::SigningKeysFor {
origin, origin,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().globals.db.verify_keys_for(&origin); let results = services.globals.db.verify_keys_for(&origin);
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(

View file

@ -10,304 +10,51 @@ mod users;
use clap::Subcommand; use clap::Subcommand;
use conduit::Result; use conduit::Result;
use room_state_cache::room_state_cache;
use ruma::{
events::{room::message::RoomMessageEventContent, RoomAccountDataEventType},
OwnedServerName, RoomAliasId, RoomId, ServerName, UserId,
};
use self::{ use self::{
account_data::account_data, appservice::appservice, globals::globals, presence::presence, resolver::resolver, account_data::AccountDataCommand, appservice::AppserviceCommand, globals::GlobalsCommand,
room_alias::room_alias, sending::sending, users::users, presence::PresenceCommand, resolver::ResolverCommand, room_alias::RoomAliasCommand,
room_state_cache::RoomStateCacheCommand, sending::SendingCommand, users::UsersCommand,
}; };
use crate::admin_command_dispatch;
#[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
/// Query tables from database /// Query tables from database
pub(super) enum QueryCommand { pub(super) enum QueryCommand {
/// - account_data.rs iterators and getters /// - account_data.rs iterators and getters
#[command(subcommand)] #[command(subcommand)]
AccountData(AccountData), AccountData(AccountDataCommand),
/// - appservice.rs iterators and getters /// - appservice.rs iterators and getters
#[command(subcommand)] #[command(subcommand)]
Appservice(Appservice), Appservice(AppserviceCommand),
/// - presence.rs iterators and getters /// - presence.rs iterators and getters
#[command(subcommand)] #[command(subcommand)]
Presence(Presence), Presence(PresenceCommand),
/// - rooms/alias.rs iterators and getters /// - rooms/alias.rs iterators and getters
#[command(subcommand)] #[command(subcommand)]
RoomAlias(RoomAlias), RoomAlias(RoomAliasCommand),
/// - rooms/state_cache iterators and getters /// - rooms/state_cache iterators and getters
#[command(subcommand)] #[command(subcommand)]
RoomStateCache(RoomStateCache), RoomStateCache(RoomStateCacheCommand),
/// - globals.rs iterators and getters /// - globals.rs iterators and getters
#[command(subcommand)] #[command(subcommand)]
Globals(Globals), Globals(GlobalsCommand),
/// - sending.rs iterators and getters /// - sending.rs iterators and getters
#[command(subcommand)] #[command(subcommand)]
Sending(Sending), Sending(SendingCommand),
/// - users.rs iterators and getters /// - users.rs iterators and getters
#[command(subcommand)] #[command(subcommand)]
Users(Users), Users(UsersCommand),
/// - resolver service /// - resolver service
#[command(subcommand)] #[command(subcommand)]
Resolver(Resolver), Resolver(ResolverCommand),
}
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/account_data.rs
pub(super) enum AccountData {
/// - Returns all changes to the account data that happened after `since`.
ChangesSince {
/// Full user ID
user_id: Box<UserId>,
/// UNIX timestamp since (u64)
since: u64,
/// Optional room ID of the account data
room_id: Option<Box<RoomId>>,
},
/// - Searches the account data for a specific kind.
Get {
/// Full user ID
user_id: Box<UserId>,
/// Account data event type
kind: RoomAccountDataEventType,
/// Optional room ID of the account data
room_id: Option<Box<RoomId>>,
},
}
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/appservice.rs
pub(super) enum Appservice {
/// - Gets the appservice registration info/details from the ID as a string
GetRegistration {
/// Appservice registration ID
appservice_id: Box<str>,
},
/// - Gets all appservice registrations with their ID and registration info
All,
}
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/presence.rs
pub(super) enum Presence {
/// - Returns the latest presence event for the given user.
GetPresence {
/// Full user ID
user_id: Box<UserId>,
},
/// - Iterator of the most recent presence updates that happened after the
/// event with id `since`.
PresenceSince {
/// UNIX timestamp since (u64)
since: u64,
},
}
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/rooms/alias.rs
pub(super) enum RoomAlias {
ResolveLocalAlias {
/// Full room alias
alias: Box<RoomAliasId>,
},
/// - Iterator of all our local room aliases for the room ID
LocalAliasesForRoom {
/// Full room ID
room_id: Box<RoomId>,
},
/// - Iterator of all our local aliases in our database with their room IDs
AllLocalAliases,
}
#[derive(Debug, Subcommand)]
pub(super) enum RoomStateCache {
ServerInRoom {
server: Box<ServerName>,
room_id: Box<RoomId>,
},
RoomServers {
room_id: Box<RoomId>,
},
ServerRooms {
server: Box<ServerName>,
},
RoomMembers {
room_id: Box<RoomId>,
},
LocalUsersInRoom {
room_id: Box<RoomId>,
},
ActiveLocalUsersInRoom {
room_id: Box<RoomId>,
},
RoomJoinedCount {
room_id: Box<RoomId>,
},
RoomInvitedCount {
room_id: Box<RoomId>,
},
RoomUserOnceJoined {
room_id: Box<RoomId>,
},
RoomMembersInvited {
room_id: Box<RoomId>,
},
GetInviteCount {
room_id: Box<RoomId>,
user_id: Box<UserId>,
},
GetLeftCount {
room_id: Box<RoomId>,
user_id: Box<UserId>,
},
RoomsJoined {
user_id: Box<UserId>,
},
RoomsLeft {
user_id: Box<UserId>,
},
RoomsInvited {
user_id: Box<UserId>,
},
InviteState {
user_id: Box<UserId>,
room_id: Box<RoomId>,
},
}
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/globals.rs
pub(super) enum Globals {
DatabaseVersion,
CurrentCount,
LastCheckForUpdatesId,
LoadKeypair,
/// - This returns an empty `Ok(BTreeMap<..>)` when there are no keys found
/// for the server.
SigningKeysFor {
origin: Box<ServerName>,
},
}
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/sending.rs
pub(super) enum Sending {
/// - Queries database for all `servercurrentevent_data`
ActiveRequests,
/// - Queries database for `servercurrentevent_data` but for a specific
/// destination
///
/// This command takes only *one* format of these arguments:
///
/// appservice_id
/// server_name
/// user_id AND push_key
///
/// See src/service/sending/mod.rs for the definition of the `Destination`
/// enum
ActiveRequestsFor {
#[arg(short, long)]
appservice_id: Option<String>,
#[arg(short, long)]
server_name: Option<Box<ServerName>>,
#[arg(short, long)]
user_id: Option<Box<UserId>>,
#[arg(short, long)]
push_key: Option<String>,
},
/// - Queries database for `servernameevent_data` which are the queued up
/// requests that will eventually be sent
///
/// This command takes only *one* format of these arguments:
///
/// appservice_id
/// server_name
/// user_id AND push_key
///
/// See src/service/sending/mod.rs for the definition of the `Destination`
/// enum
QueuedRequests {
#[arg(short, long)]
appservice_id: Option<String>,
#[arg(short, long)]
server_name: Option<Box<ServerName>>,
#[arg(short, long)]
user_id: Option<Box<UserId>>,
#[arg(short, long)]
push_key: Option<String>,
},
GetLatestEduCount {
server_name: Box<ServerName>,
},
}
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/users.rs
pub(super) enum Users {
Iter,
}
#[derive(Debug, Subcommand)]
/// Resolver service and caches
pub(super) enum Resolver {
/// Query the destinations cache
DestinationsCache {
server_name: Option<OwnedServerName>,
},
/// Query the overrides cache
OverridesCache {
name: Option<String>,
},
}
/// Processes admin query commands
pub(super) async fn process(command: QueryCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
QueryCommand::AccountData(command) => account_data(command).await?,
QueryCommand::Appservice(command) => appservice(command).await?,
QueryCommand::Presence(command) => presence(command).await?,
QueryCommand::RoomAlias(command) => room_alias(command).await?,
QueryCommand::RoomStateCache(command) => room_state_cache(command).await?,
QueryCommand::Globals(command) => globals(command).await?,
QueryCommand::Sending(command) => sending(command).await?,
QueryCommand::Users(command) => users(command).await?,
QueryCommand::Resolver(command) => resolver(command).await?,
})
} }

View file

@ -1,27 +1,47 @@
use ruma::events::room::message::RoomMessageEventContent; use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, UserId};
use super::Presence; use crate::Command;
use crate::{services, Result};
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/presence.rs
pub(crate) enum PresenceCommand {
/// - Returns the latest presence event for the given user.
GetPresence {
/// Full user ID
user_id: Box<UserId>,
},
/// - Iterator of the most recent presence updates that happened after the
/// event with id `since`.
PresenceSince {
/// UNIX timestamp since (u64)
since: u64,
},
}
/// All the getters and iterators in key_value/presence.rs /// All the getters and iterators in key_value/presence.rs
pub(super) async fn presence(subcommand: Presence) -> Result<RoomMessageEventContent> { pub(super) async fn process(subcommand: PresenceCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand { match subcommand {
Presence::GetPresence { PresenceCommand::GetPresence {
user_id, user_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().presence.db.get_presence(&user_id)?; let results = services.presence.db.get_presence(&user_id)?;
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
Presence::PresenceSince { PresenceCommand::PresenceSince {
since, since,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().presence.db.presence_since(since); let results = services.presence.db.presence_since(since);
let presence_since: Vec<(_, _, _)> = results.collect(); let presence_since: Vec<(_, _, _)> = results.collect();
let query_time = timer.elapsed(); let query_time = timer.elapsed();

View file

@ -1,24 +1,28 @@
use std::fmt::Write; use std::fmt::Write;
use clap::Subcommand;
use conduit::{utils::time, Result}; use conduit::{utils::time, Result};
use ruma::{events::room::message::RoomMessageEventContent, OwnedServerName}; use ruma::{events::room::message::RoomMessageEventContent, OwnedServerName};
use super::Resolver; use crate::{admin_command, admin_command_dispatch};
use crate::services;
/// All the getters and iterators in key_value/users.rs #[admin_command_dispatch]
pub(super) async fn resolver(subcommand: Resolver) -> Result<RoomMessageEventContent> { #[derive(Debug, Subcommand)]
match subcommand { /// Resolver service and caches
Resolver::DestinationsCache { pub(crate) enum ResolverCommand {
server_name, /// Query the destinations cache
} => destinations_cache(server_name).await, DestinationsCache {
Resolver::OverridesCache { server_name: Option<OwnedServerName>,
name, },
} => overrides_cache(name).await,
} /// Query the overrides cache
OverridesCache {
name: Option<String>,
},
} }
async fn destinations_cache(server_name: Option<OwnedServerName>) -> Result<RoomMessageEventContent> { #[admin_command]
async fn destinations_cache(&self, server_name: Option<OwnedServerName>) -> Result<RoomMessageEventContent> {
use service::resolver::cache::CachedDest; use service::resolver::cache::CachedDest;
let mut out = String::new(); let mut out = String::new();
@ -36,7 +40,8 @@ async fn destinations_cache(server_name: Option<OwnedServerName>) -> Result<Room
writeln!(out, "| {name} | {dest} | {host} | {expire} |").expect("wrote line"); writeln!(out, "| {name} | {dest} | {host} | {expire} |").expect("wrote line");
}; };
let map = services() let map = self
.services
.resolver .resolver
.cache .cache
.destinations .destinations
@ -52,7 +57,8 @@ async fn destinations_cache(server_name: Option<OwnedServerName>) -> Result<Room
Ok(RoomMessageEventContent::notice_markdown(out)) Ok(RoomMessageEventContent::notice_markdown(out))
} }
async fn overrides_cache(server_name: Option<String>) -> Result<RoomMessageEventContent> { #[admin_command]
async fn overrides_cache(&self, server_name: Option<String>) -> Result<RoomMessageEventContent> {
use service::resolver::cache::CachedOverride; use service::resolver::cache::CachedOverride;
let mut out = String::new(); let mut out = String::new();
@ -70,7 +76,13 @@ async fn overrides_cache(server_name: Option<String>) -> Result<RoomMessageEvent
writeln!(out, "| {name} | {ips:?} | {port} | {expire} |").expect("wrote line"); writeln!(out, "| {name} | {ips:?} | {port} | {expire} |").expect("wrote line");
}; };
let map = services().resolver.cache.overrides.read().expect("locked"); let map = self
.services
.resolver
.cache
.overrides
.read()
.expect("locked");
if let Some(server_name) = server_name.as_ref() { if let Some(server_name) = server_name.as_ref() {
map.get_key_value(server_name).map(row); map.get_key_value(server_name).map(row);

View file

@ -1,27 +1,48 @@
use ruma::events::room::message::RoomMessageEventContent; use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, RoomAliasId, RoomId};
use super::RoomAlias; use crate::Command;
use crate::{services, Result};
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/rooms/alias.rs
pub(crate) enum RoomAliasCommand {
ResolveLocalAlias {
/// Full room alias
alias: Box<RoomAliasId>,
},
/// - Iterator of all our local room aliases for the room ID
LocalAliasesForRoom {
/// Full room ID
room_id: Box<RoomId>,
},
/// - Iterator of all our local aliases in our database with their room IDs
AllLocalAliases,
}
/// All the getters and iterators in src/database/key_value/rooms/alias.rs /// All the getters and iterators in src/database/key_value/rooms/alias.rs
pub(super) async fn room_alias(subcommand: RoomAlias) -> Result<RoomMessageEventContent> { pub(super) async fn process(subcommand: RoomAliasCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand { match subcommand {
RoomAlias::ResolveLocalAlias { RoomAliasCommand::ResolveLocalAlias {
alias, alias,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().rooms.alias.resolve_local_alias(&alias); let results = services.rooms.alias.resolve_local_alias(&alias);
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomAlias::LocalAliasesForRoom { RoomAliasCommand::LocalAliasesForRoom {
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().rooms.alias.local_aliases_for_room(&room_id); let results = services.rooms.alias.local_aliases_for_room(&room_id);
let aliases: Vec<_> = results.collect(); let aliases: Vec<_> = results.collect();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
@ -29,9 +50,9 @@ pub(super) async fn room_alias(subcommand: RoomAlias) -> Result<RoomMessageEvent
"Query completed in {query_time:?}:\n\n```rs\n{aliases:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{aliases:#?}\n```"
))) )))
}, },
RoomAlias::AllLocalAliases => { RoomAliasCommand::AllLocalAliases => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().rooms.alias.all_local_aliases(); let results = services.rooms.alias.all_local_aliases();
let aliases: Vec<_> = results.collect(); let aliases: Vec<_> = results.collect();
let query_time = timer.elapsed(); let query_time = timer.elapsed();

View file

@ -1,71 +1,136 @@
use ruma::events::room::message::RoomMessageEventContent; use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, RoomId, ServerName, UserId};
use super::RoomStateCache; use crate::Command;
use crate::{services, Result};
#[derive(Debug, Subcommand)]
pub(crate) enum RoomStateCacheCommand {
ServerInRoom {
server: Box<ServerName>,
room_id: Box<RoomId>,
},
RoomServers {
room_id: Box<RoomId>,
},
ServerRooms {
server: Box<ServerName>,
},
RoomMembers {
room_id: Box<RoomId>,
},
LocalUsersInRoom {
room_id: Box<RoomId>,
},
ActiveLocalUsersInRoom {
room_id: Box<RoomId>,
},
RoomJoinedCount {
room_id: Box<RoomId>,
},
RoomInvitedCount {
room_id: Box<RoomId>,
},
RoomUserOnceJoined {
room_id: Box<RoomId>,
},
RoomMembersInvited {
room_id: Box<RoomId>,
},
GetInviteCount {
room_id: Box<RoomId>,
user_id: Box<UserId>,
},
GetLeftCount {
room_id: Box<RoomId>,
user_id: Box<UserId>,
},
RoomsJoined {
user_id: Box<UserId>,
},
RoomsLeft {
user_id: Box<UserId>,
},
RoomsInvited {
user_id: Box<UserId>,
},
InviteState {
user_id: Box<UserId>,
room_id: Box<RoomId>,
},
}
pub(super) async fn process(
subcommand: RoomStateCacheCommand, context: &Command<'_>,
) -> Result<RoomMessageEventContent> {
let services = context.services;
pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomMessageEventContent> {
match subcommand { match subcommand {
RoomStateCache::ServerInRoom { RoomStateCacheCommand::ServerInRoom {
server, server,
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let result = services() let result = services.rooms.state_cache.server_in_room(&server, &room_id);
.rooms
.state_cache
.server_in_room(&server, &room_id);
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{result:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{result:#?}\n```"
))) )))
}, },
RoomStateCache::RoomServers { RoomStateCacheCommand::RoomServers {
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services() let results: Result<Vec<_>> = services.rooms.state_cache.room_servers(&room_id).collect();
.rooms
.state_cache
.room_servers(&room_id)
.collect();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::ServerRooms { RoomStateCacheCommand::ServerRooms {
server, server,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services().rooms.state_cache.server_rooms(&server).collect(); let results: Result<Vec<_>> = services.rooms.state_cache.server_rooms(&server).collect();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::RoomMembers { RoomStateCacheCommand::RoomMembers {
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services() let results: Result<Vec<_>> = services.rooms.state_cache.room_members(&room_id).collect();
.rooms
.state_cache
.room_members(&room_id)
.collect();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::LocalUsersInRoom { RoomStateCacheCommand::LocalUsersInRoom {
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results: Vec<_> = services() let results: Vec<_> = services
.rooms .rooms
.state_cache .state_cache
.local_users_in_room(&room_id) .local_users_in_room(&room_id)
@ -76,11 +141,11 @@ pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::ActiveLocalUsersInRoom { RoomStateCacheCommand::ActiveLocalUsersInRoom {
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results: Vec<_> = services() let results: Vec<_> = services
.rooms .rooms
.state_cache .state_cache
.active_local_users_in_room(&room_id) .active_local_users_in_room(&room_id)
@ -91,33 +156,33 @@ pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::RoomJoinedCount { RoomStateCacheCommand::RoomJoinedCount {
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().rooms.state_cache.room_joined_count(&room_id); let results = services.rooms.state_cache.room_joined_count(&room_id);
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::RoomInvitedCount { RoomStateCacheCommand::RoomInvitedCount {
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().rooms.state_cache.room_invited_count(&room_id); let results = services.rooms.state_cache.room_invited_count(&room_id);
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::RoomUserOnceJoined { RoomStateCacheCommand::RoomUserOnceJoined {
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services() let results: Result<Vec<_>> = services
.rooms .rooms
.state_cache .state_cache
.room_useroncejoined(&room_id) .room_useroncejoined(&room_id)
@ -128,11 +193,11 @@ pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::RoomMembersInvited { RoomStateCacheCommand::RoomMembersInvited {
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services() let results: Result<Vec<_>> = services
.rooms .rooms
.state_cache .state_cache
.room_members_invited(&room_id) .room_members_invited(&room_id)
@ -143,12 +208,12 @@ pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::GetInviteCount { RoomStateCacheCommand::GetInviteCount {
room_id, room_id,
user_id, user_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services() let results = services
.rooms .rooms
.state_cache .state_cache
.get_invite_count(&room_id, &user_id); .get_invite_count(&room_id, &user_id);
@ -158,12 +223,12 @@ pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::GetLeftCount { RoomStateCacheCommand::GetLeftCount {
room_id, room_id,
user_id, user_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services() let results = services
.rooms .rooms
.state_cache .state_cache
.get_left_count(&room_id, &user_id); .get_left_count(&room_id, &user_id);
@ -173,56 +238,45 @@ pub(super) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomM
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::RoomsJoined { RoomStateCacheCommand::RoomsJoined {
user_id, user_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services() let results: Result<Vec<_>> = services.rooms.state_cache.rooms_joined(&user_id).collect();
.rooms
.state_cache
.rooms_joined(&user_id)
.collect();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::RoomsInvited { RoomStateCacheCommand::RoomsInvited {
user_id, user_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services() let results: Result<Vec<_>> = services.rooms.state_cache.rooms_invited(&user_id).collect();
.rooms
.state_cache
.rooms_invited(&user_id)
.collect();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::RoomsLeft { RoomStateCacheCommand::RoomsLeft {
user_id, user_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results: Result<Vec<_>> = services().rooms.state_cache.rooms_left(&user_id).collect(); let results: Result<Vec<_>> = services.rooms.state_cache.rooms_left(&user_id).collect();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
))) )))
}, },
RoomStateCache::InviteState { RoomStateCacheCommand::InviteState {
user_id, user_id,
room_id, room_id,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services() let results = services.rooms.state_cache.invite_state(&user_id, &room_id);
.rooms
.state_cache
.invite_state(&user_id, &room_id);
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(

View file

@ -1,14 +1,73 @@
use ruma::events::room::message::RoomMessageEventContent; use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, ServerName, UserId};
use service::sending::Destination;
use super::Sending; use crate::Command;
use crate::{service::sending::Destination, services, Result};
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/sending.rs
pub(crate) enum SendingCommand {
/// - Queries database for all `servercurrentevent_data`
ActiveRequests,
/// - Queries database for `servercurrentevent_data` but for a specific
/// destination
///
/// This command takes only *one* format of these arguments:
///
/// appservice_id
/// server_name
/// user_id AND push_key
///
/// See src/service/sending/mod.rs for the definition of the `Destination`
/// enum
ActiveRequestsFor {
#[arg(short, long)]
appservice_id: Option<String>,
#[arg(short, long)]
server_name: Option<Box<ServerName>>,
#[arg(short, long)]
user_id: Option<Box<UserId>>,
#[arg(short, long)]
push_key: Option<String>,
},
/// - Queries database for `servernameevent_data` which are the queued up
/// requests that will eventually be sent
///
/// This command takes only *one* format of these arguments:
///
/// appservice_id
/// server_name
/// user_id AND push_key
///
/// See src/service/sending/mod.rs for the definition of the `Destination`
/// enum
QueuedRequests {
#[arg(short, long)]
appservice_id: Option<String>,
#[arg(short, long)]
server_name: Option<Box<ServerName>>,
#[arg(short, long)]
user_id: Option<Box<UserId>>,
#[arg(short, long)]
push_key: Option<String>,
},
GetLatestEduCount {
server_name: Box<ServerName>,
},
}
/// All the getters and iterators in key_value/sending.rs /// All the getters and iterators in key_value/sending.rs
pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventContent> { pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand { match subcommand {
Sending::ActiveRequests => { SendingCommand::ActiveRequests => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().sending.db.active_requests(); let results = services.sending.db.active_requests();
let active_requests: Result<Vec<(_, _, _)>> = results.collect(); let active_requests: Result<Vec<(_, _, _)>> = results.collect();
let query_time = timer.elapsed(); let query_time = timer.elapsed();
@ -16,7 +75,7 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
"Query completed in {query_time:?}:\n\n```rs\n{active_requests:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{active_requests:#?}\n```"
))) )))
}, },
Sending::QueuedRequests { SendingCommand::QueuedRequests {
appservice_id, appservice_id,
server_name, server_name,
user_id, user_id,
@ -38,12 +97,12 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
)); ));
} }
services() services
.sending .sending
.db .db
.queued_requests(&Destination::Appservice(appservice_id)) .queued_requests(&Destination::Appservice(appservice_id))
}, },
(None, Some(server_name), None, None) => services() (None, Some(server_name), None, None) => services
.sending .sending
.db .db
.queued_requests(&Destination::Normal(server_name.into())), .queued_requests(&Destination::Normal(server_name.into())),
@ -55,7 +114,7 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
)); ));
} }
services() services
.sending .sending
.db .db
.queued_requests(&Destination::Push(user_id.into(), push_key)) .queued_requests(&Destination::Push(user_id.into(), push_key))
@ -81,7 +140,7 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
"Query completed in {query_time:?}:\n\n```rs\n{queued_requests:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{queued_requests:#?}\n```"
))) )))
}, },
Sending::ActiveRequestsFor { SendingCommand::ActiveRequestsFor {
appservice_id, appservice_id,
server_name, server_name,
user_id, user_id,
@ -104,12 +163,12 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
)); ));
} }
services() services
.sending .sending
.db .db
.active_requests_for(&Destination::Appservice(appservice_id)) .active_requests_for(&Destination::Appservice(appservice_id))
}, },
(None, Some(server_name), None, None) => services() (None, Some(server_name), None, None) => services
.sending .sending
.db .db
.active_requests_for(&Destination::Normal(server_name.into())), .active_requests_for(&Destination::Normal(server_name.into())),
@ -121,7 +180,7 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
)); ));
} }
services() services
.sending .sending
.db .db
.active_requests_for(&Destination::Push(user_id.into(), push_key)) .active_requests_for(&Destination::Push(user_id.into(), push_key))
@ -147,11 +206,11 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
"Query completed in {query_time:?}:\n\n```rs\n{active_requests:#?}\n```" "Query completed in {query_time:?}:\n\n```rs\n{active_requests:#?}\n```"
))) )))
}, },
Sending::GetLatestEduCount { SendingCommand::GetLatestEduCount {
server_name, server_name,
} => { } => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().sending.db.get_latest_educount(&server_name); let results = services.sending.db.get_latest_educount(&server_name);
let query_time = timer.elapsed(); let query_time = timer.elapsed();
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(

View file

@ -1,14 +1,23 @@
use clap::Subcommand;
use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent; use ruma::events::room::message::RoomMessageEventContent;
use super::Users; use crate::Command;
use crate::{services, Result};
#[derive(Debug, Subcommand)]
/// All the getters and iterators from src/database/key_value/users.rs
pub(crate) enum UsersCommand {
Iter,
}
/// All the getters and iterators in key_value/users.rs /// All the getters and iterators in key_value/users.rs
pub(super) async fn users(subcommand: Users) -> Result<RoomMessageEventContent> { pub(super) async fn process(subcommand: UsersCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
let services = context.services;
match subcommand { match subcommand {
Users::Iter => { UsersCommand::Iter => {
let timer = tokio::time::Instant::now(); let timer = tokio::time::Instant::now();
let results = services().users.db.iter(); let results = services.users.db.iter();
let users = results.collect::<Vec<_>>(); let users = results.collect::<Vec<_>>();
let query_time = timer.elapsed(); let query_time = timer.elapsed();

View file

@ -1,12 +1,49 @@
use std::fmt::Write; use std::fmt::Write;
use ruma::{events::room::message::RoomMessageEventContent, RoomAliasId}; use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, RoomAliasId, RoomId};
use super::RoomAliasCommand; use crate::{escape_html, Command};
use crate::{escape_html, services, Result};
pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> { #[derive(Debug, Subcommand)]
let server_user = &services().globals.server_user; pub(crate) enum RoomAliasCommand {
/// - Make an alias point to a room.
Set {
#[arg(short, long)]
/// Set the alias even if a room is already using it
force: bool,
/// The room id to set the alias on
room_id: Box<RoomId>,
/// The alias localpart to use (`alias`, not `#alias:servername.tld`)
room_alias_localpart: String,
},
/// - Remove a local alias
Remove {
/// The alias localpart to remove (`alias`, not `#alias:servername.tld`)
room_alias_localpart: String,
},
/// - Show which room is using an alias
Which {
/// The alias localpart to look up (`alias`, not
/// `#alias:servername.tld`)
room_alias_localpart: String,
},
/// - List aliases currently being used
List {
/// If set, only list the aliases for this room
room_id: Option<Box<RoomId>>,
},
}
pub(super) async fn process(command: RoomAliasCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
let services = context.services;
let server_user = &services.globals.server_user;
match command { match command {
RoomAliasCommand::Set { RoomAliasCommand::Set {
@ -19,7 +56,7 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
| RoomAliasCommand::Which { | RoomAliasCommand::Which {
ref room_alias_localpart, ref room_alias_localpart,
} => { } => {
let room_alias_str = format!("#{}:{}", room_alias_localpart, services().globals.server_name()); let room_alias_str = format!("#{}:{}", room_alias_localpart, services.globals.server_name());
let room_alias = match RoomAliasId::parse_box(room_alias_str) { let room_alias = match RoomAliasId::parse_box(room_alias_str) {
Ok(alias) => alias, Ok(alias) => alias,
Err(err) => return Ok(RoomMessageEventContent::text_plain(format!("Failed to parse alias: {err}"))), Err(err) => return Ok(RoomMessageEventContent::text_plain(format!("Failed to parse alias: {err}"))),
@ -29,8 +66,8 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
force, force,
room_id, room_id,
.. ..
} => match (force, services().rooms.alias.resolve_local_alias(&room_alias)) { } => match (force, services.rooms.alias.resolve_local_alias(&room_alias)) {
(true, Ok(Some(id))) => match services() (true, Ok(Some(id))) => match services
.rooms .rooms
.alias .alias
.set_alias(&room_alias, &room_id, server_user) .set_alias(&room_alias, &room_id, server_user)
@ -43,7 +80,7 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
(false, Ok(Some(id))) => Ok(RoomMessageEventContent::text_plain(format!( (false, Ok(Some(id))) => Ok(RoomMessageEventContent::text_plain(format!(
"Refusing to overwrite in use alias for {id}, use -f or --force to overwrite" "Refusing to overwrite in use alias for {id}, use -f or --force to overwrite"
))), ))),
(_, Ok(None)) => match services() (_, Ok(None)) => match services
.rooms .rooms
.alias .alias
.set_alias(&room_alias, &room_id, server_user) .set_alias(&room_alias, &room_id, server_user)
@ -55,8 +92,8 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
}, },
RoomAliasCommand::Remove { RoomAliasCommand::Remove {
.. ..
} => match services().rooms.alias.resolve_local_alias(&room_alias) { } => match services.rooms.alias.resolve_local_alias(&room_alias) {
Ok(Some(id)) => match services() Ok(Some(id)) => match services
.rooms .rooms
.alias .alias
.remove_alias(&room_alias, server_user) .remove_alias(&room_alias, server_user)
@ -70,7 +107,7 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
}, },
RoomAliasCommand::Which { RoomAliasCommand::Which {
.. ..
} => match services().rooms.alias.resolve_local_alias(&room_alias) { } => match services.rooms.alias.resolve_local_alias(&room_alias) {
Ok(Some(id)) => Ok(RoomMessageEventContent::text_plain(format!("Alias resolves to {id}"))), Ok(Some(id)) => Ok(RoomMessageEventContent::text_plain(format!("Alias resolves to {id}"))),
Ok(None) => Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")), Ok(None) => Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")),
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to lookup alias: {err}"))), Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to lookup alias: {err}"))),
@ -84,7 +121,7 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
room_id, room_id,
} => { } => {
if let Some(room_id) = room_id { if let Some(room_id) = room_id {
let aliases = services() let aliases = services
.rooms .rooms
.alias .alias
.local_aliases_for_room(&room_id) .local_aliases_for_room(&room_id)
@ -109,14 +146,14 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to list aliases: {err}"))), Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to list aliases: {err}"))),
} }
} else { } else {
let aliases = services() let aliases = services
.rooms .rooms
.alias .alias
.all_local_aliases() .all_local_aliases()
.collect::<Result<Vec<_>, _>>(); .collect::<Result<Vec<_>, _>>();
match aliases { match aliases {
Ok(aliases) => { Ok(aliases) => {
let server_name = services().globals.server_name(); let server_name = services.globals.server_name();
let plain_list = aliases let plain_list = aliases
.iter() .iter()
.fold(String::new(), |mut output, (alias, id)| { .fold(String::new(), |mut output, (alias, id)| {

View file

@ -1,15 +1,18 @@
use std::fmt::Write; use std::fmt::Write;
use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent; use ruma::events::room::message::RoomMessageEventContent;
use crate::{escape_html, get_room_info, services, Result, PAGE_SIZE}; use crate::{admin_command, escape_html, get_room_info, PAGE_SIZE};
pub(super) async fn list( #[admin_command]
_body: Vec<&str>, page: Option<usize>, exclude_disabled: bool, exclude_banned: bool, pub(super) async fn list_rooms(
&self, page: Option<usize>, exclude_disabled: bool, exclude_banned: bool,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
// TODO: i know there's a way to do this with clap, but i can't seem to find it // TODO: i know there's a way to do this with clap, but i can't seem to find it
let page = page.unwrap_or(1); let page = page.unwrap_or(1);
let mut rooms = services() let mut rooms = self
.services
.rooms .rooms
.metadata .metadata
.iter_ids() .iter_ids()
@ -18,7 +21,8 @@ pub(super) async fn list(
.ok() .ok()
.filter(|room_id| { .filter(|room_id| {
if exclude_disabled if exclude_disabled
&& services() && self
.services
.rooms .rooms
.metadata .metadata
.is_disabled(room_id) .is_disabled(room_id)
@ -28,7 +32,8 @@ pub(super) async fn list(
} }
if exclude_banned if exclude_banned
&& services() && self
.services
.rooms .rooms
.metadata .metadata
.is_banned(room_id) .is_banned(room_id)
@ -39,7 +44,7 @@ pub(super) async fn list(
true true
}) })
.map(|room_id| get_room_info(services(), &room_id)) .map(|room_id| get_room_info(self.services, &room_id))
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
rooms.sort_by_key(|r| r.1); rooms.sort_by_key(|r| r.1);

View file

@ -1,21 +1,43 @@
use std::fmt::Write; use std::fmt::Write;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId}; use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomId};
use super::RoomDirectoryCommand; use crate::{escape_html, get_room_info, Command, PAGE_SIZE};
use crate::{escape_html, get_room_info, services, Result, PAGE_SIZE};
pub(super) async fn process(command: RoomDirectoryCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> { #[derive(Debug, Subcommand)]
pub(crate) enum RoomDirectoryCommand {
/// - Publish a room to the room directory
Publish {
/// The room id of the room to publish
room_id: Box<RoomId>,
},
/// - Unpublish a room to the room directory
Unpublish {
/// The room id of the room to unpublish
room_id: Box<RoomId>,
},
/// - List rooms that are published
List {
page: Option<usize>,
},
}
pub(super) async fn process(command: RoomDirectoryCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
let services = context.services;
match command { match command {
RoomDirectoryCommand::Publish { RoomDirectoryCommand::Publish {
room_id, room_id,
} => match services().rooms.directory.set_public(&room_id) { } => match services.rooms.directory.set_public(&room_id) {
Ok(()) => Ok(RoomMessageEventContent::text_plain("Room published")), Ok(()) => Ok(RoomMessageEventContent::text_plain("Room published")),
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to update room: {err}"))), Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to update room: {err}"))),
}, },
RoomDirectoryCommand::Unpublish { RoomDirectoryCommand::Unpublish {
room_id, room_id,
} => match services().rooms.directory.set_not_public(&room_id) { } => match services.rooms.directory.set_not_public(&room_id) {
Ok(()) => Ok(RoomMessageEventContent::text_plain("Room unpublished")), Ok(()) => Ok(RoomMessageEventContent::text_plain("Room unpublished")),
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to update room: {err}"))), Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to update room: {err}"))),
}, },
@ -24,12 +46,12 @@ pub(super) async fn process(command: RoomDirectoryCommand, _body: Vec<&str>) ->
} => { } => {
// TODO: i know there's a way to do this with clap, but i can't seem to find it // TODO: i know there's a way to do this with clap, but i can't seem to find it
let page = page.unwrap_or(1); let page = page.unwrap_or(1);
let mut rooms = services() let mut rooms = services
.rooms .rooms
.directory .directory
.public_rooms() .public_rooms()
.filter_map(Result::ok) .filter_map(Result::ok)
.map(|id: OwnedRoomId| get_room_info(services(), &id)) .map(|id: OwnedRoomId| get_room_info(services, &id))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
rooms.sort_by_key(|r| r.1); rooms.sort_by_key(|r| r.1);
rooms.reverse(); rooms.reverse();

View file

@ -1,22 +1,30 @@
use clap::Subcommand;
use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, RoomId}; use ruma::{events::room::message::RoomMessageEventContent, RoomId};
use service::services;
use super::RoomInfoCommand; use crate::{admin_command, admin_command_dispatch};
use crate::Result;
pub(super) async fn process(command: RoomInfoCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command_dispatch]
match command { #[derive(Debug, Subcommand)]
RoomInfoCommand::ListJoinedMembers { pub(crate) enum RoomInfoCommand {
room_id, /// - List joined members in a room
} => list_joined_members(body, room_id).await, ListJoinedMembers {
RoomInfoCommand::ViewRoomTopic { room_id: Box<RoomId>,
room_id, },
} => view_room_topic(body, room_id).await,
} /// - Displays room topic
///
/// Room topics can be huge, so this is in its
/// own separate command
ViewRoomTopic {
room_id: Box<RoomId>,
},
} }
async fn list_joined_members(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { #[admin_command]
let room_name = services() async fn list_joined_members(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
let room_name = self
.services
.rooms .rooms
.state_accessor .state_accessor
.get_name(&room_id) .get_name(&room_id)
@ -24,7 +32,8 @@ async fn list_joined_members(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<R
.flatten() .flatten()
.unwrap_or_else(|| room_id.to_string()); .unwrap_or_else(|| room_id.to_string());
let members = services() let members = self
.services
.rooms .rooms
.state_cache .state_cache
.room_members(&room_id) .room_members(&room_id)
@ -35,7 +44,7 @@ async fn list_joined_members(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<R
.map(|user_id| { .map(|user_id| {
( (
user_id.clone(), user_id.clone(),
services() self.services
.users .users
.displayname(&user_id) .displayname(&user_id)
.unwrap_or(None) .unwrap_or(None)
@ -58,8 +67,14 @@ async fn list_joined_members(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<R
Ok(RoomMessageEventContent::notice_markdown(output_plain)) Ok(RoomMessageEventContent::notice_markdown(output_plain))
} }
async fn view_room_topic(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { #[admin_command]
let Some(room_topic) = services().rooms.state_accessor.get_room_topic(&room_id)? else { async fn view_room_topic(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
let Some(room_topic) = self
.services
.rooms
.state_accessor
.get_room_topic(&room_id)?
else {
return Ok(RoomMessageEventContent::text_plain("Room does not have a room topic set.")); return Ok(RoomMessageEventContent::text_plain("Room does not have a room topic set."));
}; };

View file

@ -1,19 +1,23 @@
mod room_alias_commands; mod alias;
mod room_commands; mod commands;
mod room_directory_commands; mod directory;
mod room_info_commands; mod info;
mod room_moderation_commands; mod moderation;
use clap::Subcommand; use clap::Subcommand;
use conduit::Result; use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, RoomId, RoomOrAliasId};
use self::room_commands::list; use self::{
alias::RoomAliasCommand, directory::RoomDirectoryCommand, info::RoomInfoCommand, moderation::RoomModerationCommand,
};
use crate::admin_command_dispatch;
#[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub(super) enum RoomCommand { pub(super) enum RoomCommand {
/// - List all rooms the server knows about /// - List all rooms the server knows about
List { #[clap(alias = "list")]
ListRooms {
page: Option<usize>, page: Option<usize>,
/// Excludes rooms that we have federation disabled with /// Excludes rooms that we have federation disabled with
@ -41,149 +45,3 @@ pub(super) enum RoomCommand {
/// - Manage the room directory /// - Manage the room directory
Directory(RoomDirectoryCommand), Directory(RoomDirectoryCommand),
} }
#[derive(Debug, Subcommand)]
pub(super) enum RoomInfoCommand {
/// - List joined members in a room
ListJoinedMembers {
room_id: Box<RoomId>,
},
/// - Displays room topic
///
/// Room topics can be huge, so this is in its
/// own separate command
ViewRoomTopic {
room_id: Box<RoomId>,
},
}
#[derive(Debug, Subcommand)]
pub(super) enum RoomAliasCommand {
/// - Make an alias point to a room.
Set {
#[arg(short, long)]
/// Set the alias even if a room is already using it
force: bool,
/// The room id to set the alias on
room_id: Box<RoomId>,
/// The alias localpart to use (`alias`, not `#alias:servername.tld`)
room_alias_localpart: String,
},
/// - Remove a local alias
Remove {
/// The alias localpart to remove (`alias`, not `#alias:servername.tld`)
room_alias_localpart: String,
},
/// - Show which room is using an alias
Which {
/// The alias localpart to look up (`alias`, not
/// `#alias:servername.tld`)
room_alias_localpart: String,
},
/// - List aliases currently being used
List {
/// If set, only list the aliases for this room
room_id: Option<Box<RoomId>>,
},
}
#[derive(Debug, Subcommand)]
pub(super) enum RoomDirectoryCommand {
/// - Publish a room to the room directory
Publish {
/// The room id of the room to publish
room_id: Box<RoomId>,
},
/// - Unpublish a room to the room directory
Unpublish {
/// The room id of the room to unpublish
room_id: Box<RoomId>,
},
/// - List rooms that are published
List {
page: Option<usize>,
},
}
#[derive(Debug, Subcommand)]
pub(super) enum RoomModerationCommand {
/// - Bans a room from local users joining and evicts all our local users
/// from the room. Also blocks any invites (local and remote) for the
/// banned room.
///
/// Server admins (users in the conduwuit admin room) will not be evicted
/// and server admins can still join the room. To evict admins too, use
/// --force (also ignores errors) To disable incoming federation of the
/// room, use --disable-federation
BanRoom {
#[arg(short, long)]
/// Evicts admins out of the room and ignores any potential errors when
/// making our local users leave the room
force: bool,
#[arg(long)]
/// Disables incoming federation of the room after banning and evicting
/// users
disable_federation: bool,
/// The room in the format of `!roomid:example.com` or a room alias in
/// the format of `#roomalias:example.com`
room: Box<RoomOrAliasId>,
},
/// - Bans a list of rooms (room IDs and room aliases) from a newline
/// delimited codeblock similar to `user deactivate-all`
BanListOfRooms {
#[arg(short, long)]
/// Evicts admins out of the room and ignores any potential errors when
/// making our local users leave the room
force: bool,
#[arg(long)]
/// Disables incoming federation of the room after banning and evicting
/// users
disable_federation: bool,
},
/// - Unbans a room to allow local users to join again
///
/// To re-enable incoming federation of the room, use --enable-federation
UnbanRoom {
#[arg(long)]
/// Enables incoming federation of the room after unbanning
enable_federation: bool,
/// The room in the format of `!roomid:example.com` or a room alias in
/// the format of `#roomalias:example.com`
room: Box<RoomOrAliasId>,
},
/// - List of all rooms we have banned
ListBannedRooms,
}
pub(super) async fn process(command: RoomCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
RoomCommand::Info(command) => room_info_commands::process(command, body).await?,
RoomCommand::Alias(command) => room_alias_commands::process(command, body).await?,
RoomCommand::Directory(command) => room_directory_commands::process(command, body).await?,
RoomCommand::Moderation(command) => room_moderation_commands::process(command, body).await?,
RoomCommand::List {
page,
exclude_disabled,
exclude_banned,
} => list(body, page, exclude_disabled, exclude_banned).await?,
})
}

View file

@ -1,37 +1,77 @@
use api::client::leave_room; use api::client::leave_room;
use clap::Subcommand;
use conduit::{debug, error, info, warn, Result}; use conduit::{debug, error, info, warn, Result};
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomAliasId, RoomId, RoomOrAliasId}; use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomAliasId, RoomId, RoomOrAliasId};
use super::RoomModerationCommand; use crate::{admin_command, admin_command_dispatch, get_room_info};
use crate::{get_room_info, services};
pub(super) async fn process(command: RoomModerationCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command_dispatch]
match command { #[derive(Debug, Subcommand)]
RoomModerationCommand::BanRoom { pub(crate) enum RoomModerationCommand {
force, /// - Bans a room from local users joining and evicts all our local users
room, /// from the room. Also blocks any invites (local and remote) for the
disable_federation, /// banned room.
} => ban_room(body, force, room, disable_federation).await, ///
RoomModerationCommand::BanListOfRooms { /// Server admins (users in the conduwuit admin room) will not be evicted
force, /// and server admins can still join the room. To evict admins too, use
disable_federation, /// --force (also ignores errors) To disable incoming federation of the
} => ban_list_of_rooms(body, force, disable_federation).await, /// room, use --disable-federation
RoomModerationCommand::UnbanRoom { BanRoom {
room, #[arg(short, long)]
enable_federation, /// Evicts admins out of the room and ignores any potential errors when
} => unban_room(body, room, enable_federation).await, /// making our local users leave the room
RoomModerationCommand::ListBannedRooms => list_banned_rooms(body).await, force: bool,
}
#[arg(long)]
/// Disables incoming federation of the room after banning and evicting
/// users
disable_federation: bool,
/// The room in the format of `!roomid:example.com` or a room alias in
/// the format of `#roomalias:example.com`
room: Box<RoomOrAliasId>,
},
/// - Bans a list of rooms (room IDs and room aliases) from a newline
/// delimited codeblock similar to `user deactivate-all`
BanListOfRooms {
#[arg(short, long)]
/// Evicts admins out of the room and ignores any potential errors when
/// making our local users leave the room
force: bool,
#[arg(long)]
/// Disables incoming federation of the room after banning and evicting
/// users
disable_federation: bool,
},
/// - Unbans a room to allow local users to join again
///
/// To re-enable incoming federation of the room, use --enable-federation
UnbanRoom {
#[arg(long)]
/// Enables incoming federation of the room after unbanning
enable_federation: bool,
/// The room in the format of `!roomid:example.com` or a room alias in
/// the format of `#roomalias:example.com`
room: Box<RoomOrAliasId>,
},
/// - List of all rooms we have banned
ListBannedRooms,
} }
#[admin_command]
async fn ban_room( async fn ban_room(
_body: Vec<&str>, force: bool, room: Box<RoomOrAliasId>, disable_federation: bool, &self, force: bool, disable_federation: bool, room: Box<RoomOrAliasId>,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
debug!("Got room alias or ID: {}", room); debug!("Got room alias or ID: {}", room);
let admin_room_alias = &services().globals.admin_alias; let admin_room_alias = &self.services.globals.admin_alias;
if let Some(admin_room_id) = services().admin.get_admin_room()? { if let Some(admin_room_id) = self.services.admin.get_admin_room()? {
if room.to_string().eq(&admin_room_id) || room.to_string().eq(admin_room_alias) { if room.to_string().eq(&admin_room_id) || room.to_string().eq(admin_room_alias) {
return Ok(RoomMessageEventContent::text_plain("Not allowed to ban the admin room.")); return Ok(RoomMessageEventContent::text_plain("Not allowed to ban the admin room."));
} }
@ -50,7 +90,7 @@ async fn ban_room(
debug!("Room specified is a room ID, banning room ID"); debug!("Room specified is a room ID, banning room ID");
services().rooms.metadata.ban_room(&room_id, true)?; self.services.rooms.metadata.ban_room(&room_id, true)?;
room_id room_id
} else if room.is_room_alias_id() { } else if room.is_room_alias_id() {
@ -69,12 +109,13 @@ async fn ban_room(
get_alias_helper to fetch room ID remotely" get_alias_helper to fetch room ID remotely"
); );
let room_id = if let Some(room_id) = services().rooms.alias.resolve_local_alias(&room_alias)? { let room_id = if let Some(room_id) = self.services.rooms.alias.resolve_local_alias(&room_alias)? {
room_id room_id
} else { } else {
debug!("We don't have this room alias to a room ID locally, attempting to fetch room ID over federation"); debug!("We don't have this room alias to a room ID locally, attempting to fetch room ID over federation");
match services() match self
.services
.rooms .rooms
.alias .alias
.resolve_alias(&room_alias, None) .resolve_alias(&room_alias, None)
@ -92,7 +133,7 @@ async fn ban_room(
} }
}; };
services().rooms.metadata.ban_room(&room_id, true)?; self.services.rooms.metadata.ban_room(&room_id, true)?;
room_id room_id
} else { } else {
@ -104,20 +145,21 @@ async fn ban_room(
debug!("Making all users leave the room {}", &room); debug!("Making all users leave the room {}", &room);
if force { if force {
for local_user in services() for local_user in self
.services
.rooms .rooms
.state_cache .state_cache
.room_members(&room_id) .room_members(&room_id)
.filter_map(|user| { .filter_map(|user| {
user.ok().filter(|local_user| { user.ok().filter(|local_user| {
services().globals.user_is_local(local_user) self.services.globals.user_is_local(local_user)
// additional wrapped check here is to avoid adding remote users // additional wrapped check here is to avoid adding remote users
// who are in the admin room to the list of local users (would // who are in the admin room to the list of local users (would
// fail auth check) // fail auth check)
&& (services().globals.user_is_local(local_user) && (self.services.globals.user_is_local(local_user)
// since this is a force operation, assume user is an admin // since this is a force operation, assume user is an admin
// if somehow this fails // if somehow this fails
&& services() && self.services
.users .users
.is_admin(local_user) .is_admin(local_user)
.unwrap_or(true)) .unwrap_or(true))
@ -128,30 +170,31 @@ async fn ban_room(
&local_user, &room_id &local_user, &room_id
); );
if let Err(e) = leave_room(services(), &local_user, &room_id, None).await { if let Err(e) = leave_room(self.services, &local_user, &room_id, None).await {
warn!(%e, "Failed to leave room"); warn!(%e, "Failed to leave room");
} }
} }
} else { } else {
for local_user in services() for local_user in self
.services
.rooms .rooms
.state_cache .state_cache
.room_members(&room_id) .room_members(&room_id)
.filter_map(|user| { .filter_map(|user| {
user.ok().filter(|local_user| { user.ok().filter(|local_user| {
local_user.server_name() == services().globals.server_name() local_user.server_name() == self.services.globals.server_name()
// additional wrapped check here is to avoid adding remote users // additional wrapped check here is to avoid adding remote users
// who are in the admin room to the list of local users (would fail auth check) // who are in the admin room to the list of local users (would fail auth check)
&& (local_user.server_name() && (local_user.server_name()
== services().globals.server_name() == self.services.globals.server_name()
&& !services() && !self.services
.users .users
.is_admin(local_user) .is_admin(local_user)
.unwrap_or(false)) .unwrap_or(false))
}) })
}) { }) {
debug!("Attempting leave for user {} in room {}", &local_user, &room_id); debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
if let Err(e) = leave_room(services(), &local_user, &room_id, None).await { if let Err(e) = leave_room(self.services, &local_user, &room_id, None).await {
error!( error!(
"Error attempting to make local user {} leave room {} during room banning: {}", "Error attempting to make local user {} leave room {} during room banning: {}",
&local_user, &room_id, e &local_user, &room_id, e
@ -166,7 +209,7 @@ async fn ban_room(
} }
if disable_federation { if disable_federation {
services().rooms.metadata.disable_room(&room_id, true)?; self.services.rooms.metadata.disable_room(&room_id, true)?;
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Room banned, removed all our local users, and disabled incoming federation with room.", "Room banned, removed all our local users, and disabled incoming federation with room.",
)); ));
@ -178,19 +221,22 @@ async fn ban_room(
)) ))
} }
async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: bool) -> Result<RoomMessageEventContent> { #[admin_command]
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { async fn ban_list_of_rooms(&self, force: bool, disable_federation: bool) -> Result<RoomMessageEventContent> {
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
{
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.", "Expected code block in command body. Add --help for details.",
)); ));
} }
let rooms_s = body let rooms_s = self
.clone() .body
.drain(1..body.len().saturating_sub(1)) .to_vec()
.drain(1..self.body.len().saturating_sub(1))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let admin_room_alias = &services().globals.admin_alias; let admin_room_alias = &self.services.globals.admin_alias;
let mut room_ban_count: usize = 0; let mut room_ban_count: usize = 0;
let mut room_ids: Vec<OwnedRoomId> = Vec::new(); let mut room_ids: Vec<OwnedRoomId> = Vec::new();
@ -198,7 +244,7 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
for &room in &rooms_s { for &room in &rooms_s {
match <&RoomOrAliasId>::try_from(room) { match <&RoomOrAliasId>::try_from(room) {
Ok(room_alias_or_id) => { Ok(room_alias_or_id) => {
if let Some(admin_room_id) = services().admin.get_admin_room()? { if let Some(admin_room_id) = self.services.admin.get_admin_room()? {
if room.to_owned().eq(&admin_room_id) || room.to_owned().eq(admin_room_alias) { if room.to_owned().eq(&admin_room_id) || room.to_owned().eq(admin_room_alias) {
info!("User specified admin room in bulk ban list, ignoring"); info!("User specified admin room in bulk ban list, ignoring");
continue; continue;
@ -231,7 +277,7 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
match RoomAliasId::parse(room_alias_or_id) { match RoomAliasId::parse(room_alias_or_id) {
Ok(room_alias) => { Ok(room_alias) => {
let room_id = let room_id =
if let Some(room_id) = services().rooms.alias.resolve_local_alias(&room_alias)? { if let Some(room_id) = self.services.rooms.alias.resolve_local_alias(&room_alias)? {
room_id room_id
} else { } else {
debug!( debug!(
@ -239,7 +285,8 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
ID over federation" ID over federation"
); );
match services() match self
.services
.rooms .rooms
.alias .alias
.resolve_alias(&room_alias, None) .resolve_alias(&room_alias, None)
@ -303,28 +350,35 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
} }
for room_id in room_ids { for room_id in room_ids {
if services().rooms.metadata.ban_room(&room_id, true).is_ok() { if self
.services
.rooms
.metadata
.ban_room(&room_id, true)
.is_ok()
{
debug!("Banned {room_id} successfully"); debug!("Banned {room_id} successfully");
room_ban_count = room_ban_count.saturating_add(1); room_ban_count = room_ban_count.saturating_add(1);
} }
debug!("Making all users leave the room {}", &room_id); debug!("Making all users leave the room {}", &room_id);
if force { if force {
for local_user in services() for local_user in self
.services
.rooms .rooms
.state_cache .state_cache
.room_members(&room_id) .room_members(&room_id)
.filter_map(|user| { .filter_map(|user| {
user.ok().filter(|local_user| { user.ok().filter(|local_user| {
local_user.server_name() == services().globals.server_name() local_user.server_name() == self.services.globals.server_name()
// additional wrapped check here is to avoid adding remote // additional wrapped check here is to avoid adding remote
// users who are in the admin room to the list of local // users who are in the admin room to the list of local
// users (would fail auth check) // users (would fail auth check)
&& (local_user.server_name() && (local_user.server_name()
== services().globals.server_name() == self.services.globals.server_name()
// since this is a force operation, assume user is an // since this is a force operation, assume user is an
// admin if somehow this fails // admin if somehow this fails
&& services() && self.services
.users .users
.is_admin(local_user) .is_admin(local_user)
.unwrap_or(true)) .unwrap_or(true))
@ -334,31 +388,32 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
"Attempting leave for user {} in room {} (forced, ignoring all errors, evicting admins too)", "Attempting leave for user {} in room {} (forced, ignoring all errors, evicting admins too)",
&local_user, room_id &local_user, room_id
); );
if let Err(e) = leave_room(services(), &local_user, &room_id, None).await { if let Err(e) = leave_room(self.services, &local_user, &room_id, None).await {
warn!(%e, "Failed to leave room"); warn!(%e, "Failed to leave room");
} }
} }
} else { } else {
for local_user in services() for local_user in self
.services
.rooms .rooms
.state_cache .state_cache
.room_members(&room_id) .room_members(&room_id)
.filter_map(|user| { .filter_map(|user| {
user.ok().filter(|local_user| { user.ok().filter(|local_user| {
local_user.server_name() == services().globals.server_name() local_user.server_name() == self.services.globals.server_name()
// additional wrapped check here is to avoid adding remote // additional wrapped check here is to avoid adding remote
// users who are in the admin room to the list of local // users who are in the admin room to the list of local
// users (would fail auth check) // users (would fail auth check)
&& (local_user.server_name() && (local_user.server_name()
== services().globals.server_name() == self.services.globals.server_name()
&& !services() && !self.services
.users .users
.is_admin(local_user) .is_admin(local_user)
.unwrap_or(false)) .unwrap_or(false))
}) })
}) { }) {
debug!("Attempting leave for user {} in room {}", &local_user, &room_id); debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
if let Err(e) = leave_room(services(), &local_user, &room_id, None).await { if let Err(e) = leave_room(self.services, &local_user, &room_id, None).await {
error!( error!(
"Error attempting to make local user {} leave room {} during bulk room banning: {}", "Error attempting to make local user {} leave room {} during bulk room banning: {}",
&local_user, &room_id, e &local_user, &room_id, e
@ -374,7 +429,7 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
} }
if disable_federation { if disable_federation {
services().rooms.metadata.disable_room(&room_id, true)?; self.services.rooms.metadata.disable_room(&room_id, true)?;
} }
} }
@ -390,9 +445,8 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
} }
} }
async fn unban_room( #[admin_command]
_body: Vec<&str>, room: Box<RoomOrAliasId>, enable_federation: bool, async fn unban_room(&self, enable_federation: bool, room: Box<RoomOrAliasId>) -> Result<RoomMessageEventContent> {
) -> Result<RoomMessageEventContent> {
let room_id = if room.is_room_id() { let room_id = if room.is_room_id() {
let room_id = match RoomId::parse(&room) { let room_id = match RoomId::parse(&room) {
Ok(room_id) => room_id, Ok(room_id) => room_id,
@ -406,7 +460,7 @@ async fn unban_room(
debug!("Room specified is a room ID, unbanning room ID"); debug!("Room specified is a room ID, unbanning room ID");
services().rooms.metadata.ban_room(&room_id, false)?; self.services.rooms.metadata.ban_room(&room_id, false)?;
room_id room_id
} else if room.is_room_alias_id() { } else if room.is_room_alias_id() {
@ -425,12 +479,13 @@ async fn unban_room(
get_alias_helper to fetch room ID remotely" get_alias_helper to fetch room ID remotely"
); );
let room_id = if let Some(room_id) = services().rooms.alias.resolve_local_alias(&room_alias)? { let room_id = if let Some(room_id) = self.services.rooms.alias.resolve_local_alias(&room_alias)? {
room_id room_id
} else { } else {
debug!("We don't have this room alias to a room ID locally, attempting to fetch room ID over federation"); debug!("We don't have this room alias to a room ID locally, attempting to fetch room ID over federation");
match services() match self
.services
.rooms .rooms
.alias .alias
.resolve_alias(&room_alias, None) .resolve_alias(&room_alias, None)
@ -448,7 +503,7 @@ async fn unban_room(
} }
}; };
services().rooms.metadata.ban_room(&room_id, false)?; self.services.rooms.metadata.ban_room(&room_id, false)?;
room_id room_id
} else { } else {
@ -459,7 +514,7 @@ async fn unban_room(
}; };
if enable_federation { if enable_federation {
services().rooms.metadata.disable_room(&room_id, false)?; self.services.rooms.metadata.disable_room(&room_id, false)?;
return Ok(RoomMessageEventContent::text_plain("Room unbanned.")); return Ok(RoomMessageEventContent::text_plain("Room unbanned."));
} }
@ -469,8 +524,10 @@ async fn unban_room(
)) ))
} }
async fn list_banned_rooms(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
let rooms = services() async fn list_banned_rooms(&self) -> Result<RoomMessageEventContent> {
let rooms = self
.services
.rooms .rooms
.metadata .metadata
.list_banned_rooms() .list_banned_rooms()
@ -484,7 +541,7 @@ async fn list_banned_rooms(_body: Vec<&str>) -> Result<RoomMessageEventContent>
let mut rooms = room_ids let mut rooms = room_ids
.into_iter() .into_iter()
.map(|room_id| get_room_info(services(), &room_id)) .map(|room_id| get_room_info(self.services, &room_id))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
rooms.sort_by_key(|r| r.1); rooms.sort_by_key(|r| r.1);
rooms.reverse(); rooms.reverse();

View file

@ -1,12 +1,14 @@
use std::fmt::Write; use std::{fmt::Write, sync::Arc};
use conduit::{info, utils::time, warn, Err, Result}; use conduit::{info, utils::time, warn, Err, Result};
use ruma::events::room::message::RoomMessageEventContent; use ruma::events::room::message::RoomMessageEventContent;
use crate::services; use crate::admin_command;
pub(super) async fn uptime(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
let elapsed = services() pub(super) async fn uptime(&self) -> Result<RoomMessageEventContent> {
let elapsed = self
.services
.server .server
.started .started
.elapsed() .elapsed()
@ -16,13 +18,15 @@ pub(super) async fn uptime(_body: Vec<&str>) -> Result<RoomMessageEventContent>
Ok(RoomMessageEventContent::notice_plain(format!("{result}."))) Ok(RoomMessageEventContent::notice_plain(format!("{result}.")))
} }
pub(super) async fn show_config(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
pub(super) async fn show_config(&self) -> Result<RoomMessageEventContent> {
// Construct and send the response // Construct and send the response
Ok(RoomMessageEventContent::text_plain(format!("{}", services().globals.config))) Ok(RoomMessageEventContent::text_plain(format!("{}", self.services.globals.config)))
} }
#[admin_command]
pub(super) async fn list_features( pub(super) async fn list_features(
_body: Vec<&str>, available: bool, enabled: bool, comma: bool, &self, available: bool, enabled: bool, comma: bool,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
let delim = if comma { let delim = if comma {
"," ","
@ -62,9 +66,10 @@ pub(super) async fn list_features(
Ok(RoomMessageEventContent::text_markdown(features)) Ok(RoomMessageEventContent::text_markdown(features))
} }
pub(super) async fn memory_usage(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
let services_usage = services().memory_usage().await?; pub(super) async fn memory_usage(&self) -> Result<RoomMessageEventContent> {
let database_usage = services().db.db.memory_usage()?; let services_usage = self.services.memory_usage().await?;
let database_usage = self.services.db.db.memory_usage()?;
let allocator_usage = conduit::alloc::memory_usage().map_or(String::new(), |s| format!("\nAllocator:\n{s}")); let allocator_usage = conduit::alloc::memory_usage().map_or(String::new(), |s| format!("\nAllocator:\n{s}"));
Ok(RoomMessageEventContent::text_plain(format!( Ok(RoomMessageEventContent::text_plain(format!(
@ -72,14 +77,16 @@ pub(super) async fn memory_usage(_body: Vec<&str>) -> Result<RoomMessageEventCon
))) )))
} }
pub(super) async fn clear_caches(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
services().clear_cache().await; pub(super) async fn clear_caches(&self) -> Result<RoomMessageEventContent> {
self.services.clear_cache().await;
Ok(RoomMessageEventContent::text_plain("Done.")) Ok(RoomMessageEventContent::text_plain("Done."))
} }
pub(super) async fn list_backups(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
let result = services().globals.db.backup_list()?; pub(super) async fn list_backups(&self) -> Result<RoomMessageEventContent> {
let result = self.services.globals.db.backup_list()?;
if result.is_empty() { if result.is_empty() {
Ok(RoomMessageEventContent::text_plain("No backups found.")) Ok(RoomMessageEventContent::text_plain("No backups found."))
@ -88,46 +95,51 @@ pub(super) async fn list_backups(_body: Vec<&str>) -> Result<RoomMessageEventCon
} }
} }
pub(super) async fn backup_database(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
let mut result = services() pub(super) async fn backup_database(&self) -> Result<RoomMessageEventContent> {
let globals = Arc::clone(&self.services.globals);
let mut result = self
.services
.server .server
.runtime() .runtime()
.spawn_blocking(move || match services().globals.db.backup() { .spawn_blocking(move || match globals.db.backup() {
Ok(()) => String::new(), Ok(()) => String::new(),
Err(e) => (*e).to_string(), Err(e) => (*e).to_string(),
}) })
.await .await?;
.unwrap();
if result.is_empty() { if result.is_empty() {
result = services().globals.db.backup_list()?; result = self.services.globals.db.backup_list()?;
} }
Ok(RoomMessageEventContent::text_plain(&result))
}
pub(super) async fn list_database_files(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
let result = services().globals.db.file_list()?;
Ok(RoomMessageEventContent::notice_markdown(result)) Ok(RoomMessageEventContent::notice_markdown(result))
} }
pub(super) async fn admin_notice(_body: Vec<&str>, message: Vec<String>) -> Result<RoomMessageEventContent> { #[admin_command]
pub(super) async fn list_database_files(&self) -> Result<RoomMessageEventContent> {
let result = self.services.globals.db.file_list()?;
Ok(RoomMessageEventContent::notice_markdown(result))
}
#[admin_command]
pub(super) async fn admin_notice(&self, message: Vec<String>) -> Result<RoomMessageEventContent> {
let message = message.join(" "); let message = message.join(" ");
services().admin.send_text(&message).await; self.services.admin.send_text(&message).await;
Ok(RoomMessageEventContent::notice_plain("Notice was sent to #admins")) Ok(RoomMessageEventContent::notice_plain("Notice was sent to #admins"))
} }
#[cfg(conduit_mods)] #[admin_command]
pub(super) async fn reload(_body: Vec<&str>) -> Result<RoomMessageEventContent> { pub(super) async fn reload_mods(&self) -> Result<RoomMessageEventContent> {
services().server.reload()?; self.services.server.reload()?;
Ok(RoomMessageEventContent::notice_plain("Reloading server...")) Ok(RoomMessageEventContent::notice_plain("Reloading server..."))
} }
#[admin_command]
#[cfg(unix)] #[cfg(unix)]
pub(super) async fn restart(_body: Vec<&str>, force: bool) -> Result<RoomMessageEventContent> { pub(super) async fn restart(&self, force: bool) -> Result<RoomMessageEventContent> {
use conduit::utils::sys::current_exe_deleted; use conduit::utils::sys::current_exe_deleted;
if !force && current_exe_deleted() { if !force && current_exe_deleted() {
@ -137,14 +149,15 @@ pub(super) async fn restart(_body: Vec<&str>, force: bool) -> Result<RoomMessage
); );
} }
services().server.restart()?; self.services.server.restart()?;
Ok(RoomMessageEventContent::notice_plain("Restarting server...")) Ok(RoomMessageEventContent::notice_plain("Restarting server..."))
} }
pub(super) async fn shutdown(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
pub(super) async fn shutdown(&self) -> Result<RoomMessageEventContent> {
warn!("shutdown command"); warn!("shutdown command");
services().server.shutdown()?; self.services.server.shutdown()?;
Ok(RoomMessageEventContent::notice_plain("Shutting down server...")) Ok(RoomMessageEventContent::notice_plain("Shutting down server..."))
} }

View file

@ -2,10 +2,10 @@ mod commands;
use clap::Subcommand; use clap::Subcommand;
use conduit::Result; use conduit::Result;
use ruma::events::room::message::RoomMessageEventContent;
use self::commands::*; use crate::admin_command_dispatch;
#[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub(super) enum ServerCommand { pub(super) enum ServerCommand {
/// - Time elapsed since startup /// - Time elapsed since startup
@ -47,9 +47,9 @@ pub(super) enum ServerCommand {
message: Vec<String>, message: Vec<String>,
}, },
#[cfg(conduit_mods)]
/// - Hot-reload the server /// - Hot-reload the server
Reload, #[clap(alias = "reload")]
ReloadMods,
#[cfg(unix)] #[cfg(unix)]
/// - Restart the server /// - Restart the server
@ -61,30 +61,3 @@ pub(super) enum ServerCommand {
/// - Shutdown the server /// - Shutdown the server
Shutdown, Shutdown,
} }
pub(super) async fn process(command: ServerCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
ServerCommand::Uptime => uptime(body).await?,
ServerCommand::ShowConfig => show_config(body).await?,
ServerCommand::ListFeatures {
available,
enabled,
comma,
} => list_features(body, available, enabled, comma).await?,
ServerCommand::MemoryUsage => memory_usage(body).await?,
ServerCommand::ClearCaches => clear_caches(body).await?,
ServerCommand::ListBackups => list_backups(body).await?,
ServerCommand::BackupDatabase => backup_database(body).await?,
ServerCommand::ListDatabaseFiles => list_database_files(body).await?,
ServerCommand::AdminNotice {
message,
} => admin_notice(body, message).await?,
#[cfg(conduit_mods)]
ServerCommand::Reload => reload(body).await?,
#[cfg(unix)]
ServerCommand::Restart {
force,
} => restart(body, force).await?,
ServerCommand::Shutdown => shutdown(body).await?,
})
}

View file

@ -1,7 +1,7 @@
use std::{collections::BTreeMap, fmt::Write as _}; use std::{collections::BTreeMap, fmt::Write as _};
use api::client::{join_room_by_id_helper, leave_all_rooms, update_avatar_url, update_displayname}; use api::client::{join_room_by_id_helper, leave_all_rooms, update_avatar_url, update_displayname};
use conduit::{utils, Result}; use conduit::{error, info, utils, warn, Result};
use ruma::{ use ruma::{
events::{ events::{
room::message::RoomMessageEventContent, room::message::RoomMessageEventContent,
@ -10,17 +10,17 @@ use ruma::{
}, },
OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, RoomId, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, RoomId,
}; };
use tracing::{error, info, warn};
use crate::{ use crate::{
escape_html, get_room_info, services, admin_command, escape_html, get_room_info,
utils::{parse_active_local_user_id, parse_local_user_id}, utils::{parse_active_local_user_id, parse_local_user_id},
}; };
const AUTO_GEN_PASSWORD_LENGTH: usize = 25; const AUTO_GEN_PASSWORD_LENGTH: usize = 25;
pub(super) async fn list(_body: Vec<&str>) -> Result<RoomMessageEventContent> { #[admin_command]
match services().users.list_local_users() { pub(super) async fn list_users(&self) -> Result<RoomMessageEventContent> {
match self.services.users.list_local_users() {
Ok(users) => { Ok(users) => {
let mut plain_msg = format!("Found {} local user account(s):\n```\n", users.len()); let mut plain_msg = format!("Found {} local user account(s):\n```\n", users.len());
plain_msg += users.join("\n").as_str(); plain_msg += users.join("\n").as_str();
@ -32,13 +32,12 @@ pub(super) async fn list(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
} }
} }
pub(super) async fn create( #[admin_command]
_body: Vec<&str>, username: String, password: Option<String>, pub(super) async fn create_user(&self, username: String, password: Option<String>) -> Result<RoomMessageEventContent> {
) -> Result<RoomMessageEventContent> {
// Validate user id // Validate user id
let user_id = parse_local_user_id(services(), &username)?; let user_id = parse_local_user_id(self.services, &username)?;
if services().users.exists(&user_id)? { if self.services.users.exists(&user_id)? {
return Ok(RoomMessageEventContent::text_plain(format!("Userid {user_id} already exists"))); return Ok(RoomMessageEventContent::text_plain(format!("Userid {user_id} already exists")));
} }
@ -51,30 +50,33 @@ pub(super) async fn create(
let password = password.unwrap_or_else(|| utils::random_string(AUTO_GEN_PASSWORD_LENGTH)); let password = password.unwrap_or_else(|| utils::random_string(AUTO_GEN_PASSWORD_LENGTH));
// Create user // Create user
services().users.create(&user_id, Some(password.as_str()))?; self.services
.users
.create(&user_id, Some(password.as_str()))?;
// Default to pretty displayname // Default to pretty displayname
let mut displayname = user_id.localpart().to_owned(); let mut displayname = user_id.localpart().to_owned();
// If `new_user_displayname_suffix` is set, registration will push whatever // If `new_user_displayname_suffix` is set, registration will push whatever
// content is set to the user's display name with a space before it // content is set to the user's display name with a space before it
if !services() if !self
.services
.globals .globals
.config .config
.new_user_displayname_suffix .new_user_displayname_suffix
.is_empty() .is_empty()
{ {
write!(displayname, " {}", services().globals.config.new_user_displayname_suffix) write!(displayname, " {}", self.services.globals.config.new_user_displayname_suffix)
.expect("should be able to write to string buffer"); .expect("should be able to write to string buffer");
} }
services() self.services
.users .users
.set_displayname(&user_id, Some(displayname)) .set_displayname(&user_id, Some(displayname))
.await?; .await?;
// Initial account data // Initial account data
services().account_data.update( self.services.account_data.update(
None, None,
&user_id, &user_id,
ruma::events::GlobalAccountDataEventType::PushRules ruma::events::GlobalAccountDataEventType::PushRules
@ -88,12 +90,13 @@ pub(super) async fn create(
.expect("to json value always works"), .expect("to json value always works"),
)?; )?;
if !services().globals.config.auto_join_rooms.is_empty() { if !self.services.globals.config.auto_join_rooms.is_empty() {
for room in &services().globals.config.auto_join_rooms { for room in &self.services.globals.config.auto_join_rooms {
if !services() if !self
.services
.rooms .rooms
.state_cache .state_cache
.server_in_room(services().globals.server_name(), room)? .server_in_room(self.services.globals.server_name(), room)?
{ {
warn!("Skipping room {room} to automatically join as we have never joined before."); warn!("Skipping room {room} to automatically join as we have never joined before.");
continue; continue;
@ -101,11 +104,11 @@ pub(super) async fn create(
if let Some(room_id_server_name) = room.server_name() { if let Some(room_id_server_name) = room.server_name() {
match join_room_by_id_helper( match join_room_by_id_helper(
services(), self.services,
&user_id, &user_id,
room, room,
Some("Automatically joining this room upon registration".to_owned()), Some("Automatically joining this room upon registration".to_owned()),
&[room_id_server_name.to_owned(), services().globals.server_name().to_owned()], &[room_id_server_name.to_owned(), self.services.globals.server_name().to_owned()],
None, None,
) )
.await .await
@ -130,38 +133,38 @@ pub(super) async fn create(
))) )))
} }
pub(super) async fn deactivate( #[admin_command]
_body: Vec<&str>, no_leave_rooms: bool, user_id: String, pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) -> Result<RoomMessageEventContent> {
) -> Result<RoomMessageEventContent> {
// Validate user id // Validate user id
let user_id = parse_local_user_id(services(), &user_id)?; let user_id = parse_local_user_id(self.services, &user_id)?;
// don't deactivate the server service account // don't deactivate the server service account
if user_id == services().globals.server_user { if user_id == self.services.globals.server_user {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Not allowed to deactivate the server service account.", "Not allowed to deactivate the server service account.",
)); ));
} }
services().users.deactivate_account(&user_id)?; self.services.users.deactivate_account(&user_id)?;
if !no_leave_rooms { if !no_leave_rooms {
services() self.services
.admin .admin
.send_message(RoomMessageEventContent::text_plain(format!( .send_message(RoomMessageEventContent::text_plain(format!(
"Making {user_id} leave all rooms after deactivation..." "Making {user_id} leave all rooms after deactivation..."
))) )))
.await; .await;
let all_joined_rooms: Vec<OwnedRoomId> = services() let all_joined_rooms: Vec<OwnedRoomId> = self
.services
.rooms .rooms
.state_cache .state_cache
.rooms_joined(&user_id) .rooms_joined(&user_id)
.filter_map(Result::ok) .filter_map(Result::ok)
.collect(); .collect();
update_displayname(services(), user_id.clone(), None, all_joined_rooms.clone()).await?; update_displayname(self.services, user_id.clone(), None, all_joined_rooms.clone()).await?;
update_avatar_url(services(), user_id.clone(), None, None, all_joined_rooms).await?; update_avatar_url(self.services, user_id.clone(), None, None, all_joined_rooms).await?;
leave_all_rooms(services(), &user_id).await; leave_all_rooms(self.services, &user_id).await;
} }
Ok(RoomMessageEventContent::text_plain(format!( Ok(RoomMessageEventContent::text_plain(format!(
@ -169,10 +172,11 @@ pub(super) async fn deactivate(
))) )))
} }
pub(super) async fn reset_password(_body: Vec<&str>, username: String) -> Result<RoomMessageEventContent> { #[admin_command]
let user_id = parse_local_user_id(services(), &username)?; pub(super) async fn reset_password(&self, username: String) -> Result<RoomMessageEventContent> {
let user_id = parse_local_user_id(self.services, &username)?;
if user_id == services().globals.server_user { if user_id == self.services.globals.server_user {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Not allowed to set the password for the server account. Please use the emergency password config option.", "Not allowed to set the password for the server account. Please use the emergency password config option.",
)); ));
@ -180,7 +184,8 @@ pub(super) async fn reset_password(_body: Vec<&str>, username: String) -> Result
let new_password = utils::random_string(AUTO_GEN_PASSWORD_LENGTH); let new_password = utils::random_string(AUTO_GEN_PASSWORD_LENGTH);
match services() match self
.services
.users .users
.set_password(&user_id, Some(new_password.as_str())) .set_password(&user_id, Some(new_password.as_str()))
{ {
@ -193,28 +198,29 @@ pub(super) async fn reset_password(_body: Vec<&str>, username: String) -> Result
} }
} }
pub(super) async fn deactivate_all( #[admin_command]
body: Vec<&str>, no_leave_rooms: bool, force: bool, pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) -> Result<RoomMessageEventContent> {
) -> Result<RoomMessageEventContent> { if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { {
return Ok(RoomMessageEventContent::text_plain( return Ok(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.", "Expected code block in command body. Add --help for details.",
)); ));
} }
let usernames = body let usernames = self
.clone() .body
.drain(1..body.len().saturating_sub(1)) .to_vec()
.drain(1..self.body.len().saturating_sub(1))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut user_ids: Vec<OwnedUserId> = Vec::with_capacity(usernames.len()); let mut user_ids: Vec<OwnedUserId> = Vec::with_capacity(usernames.len());
let mut admins = Vec::new(); let mut admins = Vec::new();
for username in usernames { for username in usernames {
match parse_active_local_user_id(services(), username) { match parse_active_local_user_id(self.services, username) {
Ok(user_id) => { Ok(user_id) => {
if services().users.is_admin(&user_id)? && !force { if self.services.users.is_admin(&user_id)? && !force {
services() self.services
.admin .admin
.send_message(RoomMessageEventContent::text_plain(format!( .send_message(RoomMessageEventContent::text_plain(format!(
"{username} is an admin and --force is not set, skipping over" "{username} is an admin and --force is not set, skipping over"
@ -225,8 +231,8 @@ pub(super) async fn deactivate_all(
} }
// don't deactivate the server service account // don't deactivate the server service account
if user_id == services().globals.server_user { if user_id == self.services.globals.server_user {
services() self.services
.admin .admin
.send_message(RoomMessageEventContent::text_plain(format!( .send_message(RoomMessageEventContent::text_plain(format!(
"{username} is the server service account, skipping over" "{username} is the server service account, skipping over"
@ -238,7 +244,7 @@ pub(super) async fn deactivate_all(
user_ids.push(user_id); user_ids.push(user_id);
}, },
Err(e) => { Err(e) => {
services() self.services
.admin .admin
.send_message(RoomMessageEventContent::text_plain(format!( .send_message(RoomMessageEventContent::text_plain(format!(
"{username} is not a valid username, skipping over: {e}" "{username} is not a valid username, skipping over: {e}"
@ -252,24 +258,25 @@ pub(super) async fn deactivate_all(
let mut deactivation_count: usize = 0; let mut deactivation_count: usize = 0;
for user_id in user_ids { for user_id in user_ids {
match services().users.deactivate_account(&user_id) { match self.services.users.deactivate_account(&user_id) {
Ok(()) => { Ok(()) => {
deactivation_count = deactivation_count.saturating_add(1); deactivation_count = deactivation_count.saturating_add(1);
if !no_leave_rooms { if !no_leave_rooms {
info!("Forcing user {user_id} to leave all rooms apart of deactivate-all"); info!("Forcing user {user_id} to leave all rooms apart of deactivate-all");
let all_joined_rooms: Vec<OwnedRoomId> = services() let all_joined_rooms: Vec<OwnedRoomId> = self
.services
.rooms .rooms
.state_cache .state_cache
.rooms_joined(&user_id) .rooms_joined(&user_id)
.filter_map(Result::ok) .filter_map(Result::ok)
.collect(); .collect();
update_displayname(services(), user_id.clone(), None, all_joined_rooms.clone()).await?; update_displayname(self.services, user_id.clone(), None, all_joined_rooms.clone()).await?;
update_avatar_url(services(), user_id.clone(), None, None, all_joined_rooms).await?; update_avatar_url(self.services, user_id.clone(), None, None, all_joined_rooms).await?;
leave_all_rooms(services(), &user_id).await; leave_all_rooms(self.services, &user_id).await;
} }
}, },
Err(e) => { Err(e) => {
services() self.services
.admin .admin
.send_message(RoomMessageEventContent::text_plain(format!("Failed deactivating user: {e}"))) .send_message(RoomMessageEventContent::text_plain(format!("Failed deactivating user: {e}")))
.await; .await;
@ -290,16 +297,18 @@ pub(super) async fn deactivate_all(
} }
} }
pub(super) async fn list_joined_rooms(_body: Vec<&str>, user_id: String) -> Result<RoomMessageEventContent> { #[admin_command]
pub(super) async fn list_joined_rooms(&self, user_id: String) -> Result<RoomMessageEventContent> {
// Validate user id // Validate user id
let user_id = parse_local_user_id(services(), &user_id)?; let user_id = parse_local_user_id(self.services, &user_id)?;
let mut rooms: Vec<(OwnedRoomId, u64, String)> = services() let mut rooms: Vec<(OwnedRoomId, u64, String)> = self
.services
.rooms .rooms
.state_cache .state_cache
.rooms_joined(&user_id) .rooms_joined(&user_id)
.filter_map(Result::ok) .filter_map(Result::ok)
.map(|room_id| get_room_info(services(), &room_id)) .map(|room_id| get_room_info(self.services, &room_id))
.collect(); .collect();
if rooms.is_empty() { if rooms.is_empty() {
@ -341,35 +350,38 @@ pub(super) async fn list_joined_rooms(_body: Vec<&str>, user_id: String) -> Resu
Ok(RoomMessageEventContent::text_html(output_plain, output_html)) Ok(RoomMessageEventContent::text_html(output_plain, output_html))
} }
#[admin_command]
pub(super) async fn force_join_room( pub(super) async fn force_join_room(
_body: Vec<&str>, user_id: String, room_id: OwnedRoomOrAliasId, &self, user_id: String, room_id: OwnedRoomOrAliasId,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
let user_id = parse_local_user_id(services(), &user_id)?; let user_id = parse_local_user_id(self.services, &user_id)?;
let room_id = services().rooms.alias.resolve(&room_id).await?; let room_id = self.services.rooms.alias.resolve(&room_id).await?;
assert!( assert!(
services().globals.user_is_local(&user_id), self.services.globals.user_is_local(&user_id),
"Parsed user_id must be a local user" "Parsed user_id must be a local user"
); );
join_room_by_id_helper(services(), &user_id, &room_id, None, &[], None).await?; join_room_by_id_helper(self.services, &user_id, &room_id, None, &[], None).await?;
Ok(RoomMessageEventContent::notice_markdown(format!( Ok(RoomMessageEventContent::notice_markdown(format!(
"{user_id} has been joined to {room_id}.", "{user_id} has been joined to {room_id}.",
))) )))
} }
pub(super) async fn make_user_admin(_body: Vec<&str>, user_id: String) -> Result<RoomMessageEventContent> { #[admin_command]
let user_id = parse_local_user_id(services(), &user_id)?; pub(super) async fn make_user_admin(&self, user_id: String) -> Result<RoomMessageEventContent> {
let displayname = services() let user_id = parse_local_user_id(self.services, &user_id)?;
let displayname = self
.services
.users .users
.displayname(&user_id)? .displayname(&user_id)?
.unwrap_or_else(|| user_id.to_string()); .unwrap_or_else(|| user_id.to_string());
assert!( assert!(
services().globals.user_is_local(&user_id), self.services.globals.user_is_local(&user_id),
"Parsed user_id must be a local user" "Parsed user_id must be a local user"
); );
services() self.services
.admin .admin
.make_user_admin(&user_id, displayname) .make_user_admin(&user_id, displayname)
.await?; .await?;
@ -379,12 +391,14 @@ pub(super) async fn make_user_admin(_body: Vec<&str>, user_id: String) -> Result
))) )))
} }
#[admin_command]
pub(super) async fn put_room_tag( pub(super) async fn put_room_tag(
_body: Vec<&str>, user_id: String, room_id: Box<RoomId>, tag: String, &self, user_id: String, room_id: Box<RoomId>, tag: String,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
let user_id = parse_active_local_user_id(services(), &user_id)?; let user_id = parse_active_local_user_id(self.services, &user_id)?;
let event = services() let event = self
.services
.account_data .account_data
.get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?; .get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?;
@ -402,7 +416,7 @@ pub(super) async fn put_room_tag(
.tags .tags
.insert(tag.clone().into(), TagInfo::new()); .insert(tag.clone().into(), TagInfo::new());
services().account_data.update( self.services.account_data.update(
Some(&room_id), Some(&room_id),
&user_id, &user_id,
RoomAccountDataEventType::Tag, RoomAccountDataEventType::Tag,
@ -414,12 +428,14 @@ pub(super) async fn put_room_tag(
))) )))
} }
#[admin_command]
pub(super) async fn delete_room_tag( pub(super) async fn delete_room_tag(
_body: Vec<&str>, user_id: String, room_id: Box<RoomId>, tag: String, &self, user_id: String, room_id: Box<RoomId>, tag: String,
) -> Result<RoomMessageEventContent> { ) -> Result<RoomMessageEventContent> {
let user_id = parse_active_local_user_id(services(), &user_id)?; let user_id = parse_active_local_user_id(self.services, &user_id)?;
let event = services() let event = self
.services
.account_data .account_data
.get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?; .get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?;
@ -434,7 +450,7 @@ pub(super) async fn delete_room_tag(
tags_event.content.tags.remove(&tag.clone().into()); tags_event.content.tags.remove(&tag.clone().into());
services().account_data.update( self.services.account_data.update(
Some(&room_id), Some(&room_id),
&user_id, &user_id,
RoomAccountDataEventType::Tag, RoomAccountDataEventType::Tag,
@ -446,12 +462,12 @@ pub(super) async fn delete_room_tag(
))) )))
} }
pub(super) async fn get_room_tags( #[admin_command]
_body: Vec<&str>, user_id: String, room_id: Box<RoomId>, pub(super) async fn get_room_tags(&self, user_id: String, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
) -> Result<RoomMessageEventContent> { let user_id = parse_active_local_user_id(self.services, &user_id)?;
let user_id = parse_active_local_user_id(services(), &user_id)?;
let event = services() let event = self
.services
.account_data .account_data
.get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?; .get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?;

View file

@ -2,14 +2,16 @@ mod commands;
use clap::Subcommand; use clap::Subcommand;
use conduit::Result; use conduit::Result;
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomOrAliasId, RoomId}; use ruma::{OwnedRoomOrAliasId, RoomId};
use self::commands::*; use crate::admin_command_dispatch;
#[admin_command_dispatch]
#[derive(Debug, Subcommand)] #[derive(Debug, Subcommand)]
pub(super) enum UserCommand { pub(super) enum UserCommand {
/// - Create a new user /// - Create a new user
Create { #[clap(alias = "create")]
CreateUser {
/// Username of the new user /// Username of the new user
username: String, username: String,
/// Password of the new user, if unspecified one is generated /// Password of the new user, if unspecified one is generated
@ -56,7 +58,8 @@ pub(super) enum UserCommand {
}, },
/// - List local users in the database /// - List local users in the database
List, #[clap(alias = "list")]
ListUsers,
/// - Lists all the rooms (local and remote) that the specified user is /// - Lists all the rooms (local and remote) that the specified user is
/// joined in /// joined in
@ -101,48 +104,3 @@ pub(super) enum UserCommand {
room_id: Box<RoomId>, room_id: Box<RoomId>,
}, },
} }
pub(super) async fn process(command: UserCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
Ok(match command {
UserCommand::List => list(body).await?,
UserCommand::Create {
username,
password,
} => create(body, username, password).await?,
UserCommand::Deactivate {
no_leave_rooms,
user_id,
} => deactivate(body, no_leave_rooms, user_id).await?,
UserCommand::ResetPassword {
username,
} => reset_password(body, username).await?,
UserCommand::DeactivateAll {
no_leave_rooms,
force,
} => deactivate_all(body, no_leave_rooms, force).await?,
UserCommand::ListJoinedRooms {
user_id,
} => list_joined_rooms(body, user_id).await?,
UserCommand::ForceJoinRoom {
user_id,
room_id,
} => force_join_room(body, user_id, room_id).await?,
UserCommand::MakeUserAdmin {
user_id,
} => make_user_admin(body, user_id).await?,
UserCommand::PutRoomTag {
user_id,
room_id,
tag,
} => put_room_tag(body, user_id, room_id, tag).await?,
UserCommand::DeleteRoomTag {
user_id,
room_id,
tag,
} => delete_room_tag(body, user_id, room_id, tag).await?,
UserCommand::GetRoomTags {
user_id,
room_id,
} => get_room_tags(body, user_id, room_id).await?,
})
}

View file

@ -2,15 +2,27 @@ use itertools::Itertools;
use proc_macro::{Span, TokenStream}; use proc_macro::{Span, TokenStream};
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
use syn::{Error, Fields, Ident, ItemEnum, Meta, Variant}; use syn::{parse_quote, Attribute, Error, Fields, Ident, ItemEnum, ItemFn, Meta, Variant};
use crate::{utils::camel_to_snake_string, Result}; use crate::{utils::camel_to_snake_string, Result};
pub(super) fn command(mut item: ItemFn, _args: &[Meta]) -> Result<TokenStream> {
let attr: Attribute = parse_quote! {
#[conduit_macros::implement(crate::Command, params = "<'_>")]
};
item.attrs.push(attr);
Ok(item.into_token_stream().into())
}
pub(super) fn command_dispatch(item: ItemEnum, _args: &[Meta]) -> Result<TokenStream> { pub(super) fn command_dispatch(item: ItemEnum, _args: &[Meta]) -> Result<TokenStream> {
let name = &item.ident; let name = &item.ident;
let arm: Vec<TokenStream2> = item.variants.iter().map(dispatch_arm).try_collect()?; let arm: Vec<TokenStream2> = item.variants.iter().map(dispatch_arm).try_collect()?;
let switch = quote! { let switch = quote! {
pub(super) async fn process(command: #name, body: Vec<&str>) -> Result<RoomMessageEventContent> { pub(super) async fn process(
command: #name,
context: &crate::Command<'_>
) -> Result<ruma::events::room::message::RoomMessageEventContent> {
use #name::*; use #name::*;
#[allow(non_snake_case)] #[allow(non_snake_case)]
Ok(match command { Ok(match command {
@ -34,7 +46,7 @@ fn dispatch_arm(v: &Variant) -> Result<TokenStream2> {
let field = fields.named.iter().filter_map(|f| f.ident.as_ref()); let field = fields.named.iter().filter_map(|f| f.ident.as_ref());
let arg = field.clone(); let arg = field.clone();
quote! { quote! {
#name { #( #field ),* } => Box::pin(#handler(&body, #( #arg ),*)).await?, #name { #( #field ),* } => Box::pin(context.#handler(#( #arg ),*)).await?,
} }
}, },
Fields::Unnamed(fields) => { Fields::Unnamed(fields) => {
@ -42,12 +54,12 @@ fn dispatch_arm(v: &Variant) -> Result<TokenStream2> {
return Err(Error::new(Span::call_site().into(), "One unnamed field required")); return Err(Error::new(Span::call_site().into(), "One unnamed field required"));
}; };
quote! { quote! {
#name ( #field ) => Box::pin(#handler::process(#field, body)).await?, #name ( #field ) => Box::pin(#handler::process(#field, context)).await?,
} }
}, },
Fields::Unit => { Fields::Unit => {
quote! { quote! {
#name => Box::pin(#handler(&body)).await?, #name => Box::pin(context.#handler()).await?,
} }
}, },
}; };

View file

@ -14,6 +14,11 @@ use syn::{
pub(crate) type Result<T> = std::result::Result<T, Error>; pub(crate) type Result<T> = std::result::Result<T, Error>;
#[proc_macro_attribute]
pub fn admin_command(args: TokenStream, input: TokenStream) -> TokenStream {
attribute_macro::<ItemFn, _>(args, input, admin::command)
}
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn admin_command_dispatch(args: TokenStream, input: TokenStream) -> TokenStream { pub fn admin_command_dispatch(args: TokenStream, input: TokenStream) -> TokenStream {
attribute_macro::<ItemEnum, _>(args, input, admin::command_dispatch) attribute_macro::<ItemEnum, _>(args, input, admin::command_dispatch)