From 7e50db419362c6838d5eaeefccee24c7686b57e3 Mon Sep 17 00:00:00 2001 From: Jason Volk <jason@zemos.net> Date: Sat, 27 Jul 2024 00:11:41 +0000 Subject: [PATCH] de-global services from admin Signed-off-by: Jason Volk <jason@zemos.net> --- src/admin/admin.rs | 34 +-- src/admin/appservice/commands.rs | 29 +- src/admin/appservice/mod.rs | 23 +- src/admin/check/commands.rs | 6 +- src/admin/check/mod.rs | 6 +- src/admin/command.rs | 6 + src/admin/debug/commands.rs | 258 +++++++++------- src/admin/debug/mod.rs | 6 +- src/admin/debug/tester.rs | 18 +- src/admin/federation/commands.rs | 39 ++- src/admin/federation/mod.rs | 23 +- src/admin/handler.rs | 23 +- src/admin/media/commands.rs | 37 +-- src/admin/media/mod.rs | 19 +- src/admin/mod.rs | 7 +- src/admin/query/account_data.rs | 46 ++- src/admin/query/appservice.rs | 30 +- src/admin/query/globals.rs | 49 ++- src/admin/query/mod.rs | 281 +----------------- src/admin/query/presence.rs | 36 ++- src/admin/query/resolver.rs | 44 ++- src/admin/query/room_alias.rs | 41 ++- src/admin/query/room_state_cache.rs | 170 +++++++---- src/admin/query/sending.rs | 91 +++++- src/admin/query/users.rs | 19 +- .../room/{room_alias_commands.rs => alias.rs} | 67 ++++- .../room/{room_commands.rs => commands.rs} | 19 +- ...oom_directory_commands.rs => directory.rs} | 38 ++- .../room/{room_info_commands.rs => info.rs} | 51 ++-- src/admin/room/mod.rs | 166 +---------- ...m_moderation_commands.rs => moderation.rs} | 197 +++++++----- src/admin/server/commands.rs | 77 +++-- src/admin/server/mod.rs | 35 +-- src/admin/user/commands.rs | 180 ++++++----- src/admin/user/mod.rs | 56 +--- src/macros/admin.rs | 22 +- src/macros/mod.rs | 5 + 37 files changed, 1129 insertions(+), 1125 deletions(-) create mode 100644 src/admin/command.rs rename src/admin/room/{room_alias_commands.rs => alias.rs} (73%) rename src/admin/room/{room_commands.rs => commands.rs} (82%) rename src/admin/room/{room_directory_commands.rs => directory.rs} (68%) rename src/admin/room/{room_info_commands.rs => info.rs} (54%) rename src/admin/room/{room_moderation_commands.rs => moderation.rs} (72%) diff --git a/src/admin/admin.rs b/src/admin/admin.rs index f5fe5dc22..fa4972056 100644 --- a/src/admin/admin.rs +++ b/src/admin/admin.rs @@ -3,14 +3,14 @@ use ruma::events::room::message::RoomMessageEventContent; use crate::{ - appservice, appservice::AppserviceCommand, check, check::CheckCommand, debug, debug::DebugCommand, federation, - federation::FederationCommand, media, media::MediaCommand, query, query::QueryCommand, room, room::RoomCommand, - server, server::ServerCommand, user, user::UserCommand, + appservice, appservice::AppserviceCommand, check, check::CheckCommand, command::Command, debug, + debug::DebugCommand, federation, federation::FederationCommand, media, media::MediaCommand, query, + query::QueryCommand, room, room::RoomCommand, server, server::ServerCommand, user, user::UserCommand, }; #[derive(Debug, Parser)] #[command(name = "admin", version = env!("CARGO_PKG_VERSION"))] -pub(crate) enum AdminCommand { +pub(super) enum AdminCommand { #[command(subcommand)] /// - Commands for managing appservices Appservices(AppserviceCommand), @@ -49,18 +49,18 @@ pub(crate) enum AdminCommand { } #[tracing::instrument(skip_all, name = "command")] -pub(crate) async fn process(command: AdminCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> { - let reply_message_content = match command { - 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?, - }; +pub(super) async fn process(command: AdminCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> { + use AdminCommand::*; - 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?, + }) } diff --git a/src/admin/appservice/commands.rs b/src/admin/appservice/commands.rs index 5cce83510..7d6378f31 100644 --- a/src/admin/appservice/commands.rs +++ b/src/admin/appservice/commands.rs @@ -1,18 +1,20 @@ 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> { - if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { +#[admin_command] +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( "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); 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!( "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> { - match services() +#[admin_command] +pub(super) async fn unregister(&self, appservice_identifier: String) -> Result<RoomMessageEventContent> { + match self + .services .appservice .unregister_appservice(&appservice_identifier) .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> { - match services() +#[admin_command] +pub(super) async fn show_appservice_config(&self, appservice_identifier: String) -> Result<RoomMessageEventContent> { + match self + .services .appservice .get_registration(&appservice_identifier) .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> { - let appservices = services().appservice.iter_ids().await; +#[admin_command] +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(", ")); Ok(RoomMessageEventContent::text_plain(output)) } diff --git a/src/admin/appservice/mod.rs b/src/admin/appservice/mod.rs index 81e04087c..ca5f46bba 100644 --- a/src/admin/appservice/mod.rs +++ b/src/admin/appservice/mod.rs @@ -2,11 +2,11 @@ use clap::Subcommand; use conduit::Result; -use ruma::events::room::message::RoomMessageEventContent; -use self::commands::*; +use crate::admin_command_dispatch; #[derive(Debug, Subcommand)] +#[admin_command_dispatch] pub(super) enum AppserviceCommand { /// - Register an appservice using its registration YAML /// @@ -28,24 +28,13 @@ pub(super) enum AppserviceCommand { /// - Show an appservice's config using its ID /// /// You can find the ID using the `list-appservices` command. - Show { + #[clap(alias("show"))] + ShowAppserviceConfig { /// The appservice to show appservice_identifier: String, }, /// - List all the currently registered appservices - List, -} - -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?, - }) + #[clap(alias("list"))] + ListRegistered, } diff --git a/src/admin/check/commands.rs b/src/admin/check/commands.rs index 1fbea8f6e..a757d5044 100644 --- a/src/admin/check/commands.rs +++ b/src/admin/check/commands.rs @@ -1,12 +1,14 @@ use conduit::Result; +use conduit_macros::implement; 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 /// every user in our database (remote and local). Reports total count, any /// 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 results = services().users.db.iter(); let query_time = timer.elapsed(); diff --git a/src/admin/check/mod.rs b/src/admin/check/mod.rs index f1cfa2b94..e543e5b54 100644 --- a/src/admin/check/mod.rs +++ b/src/admin/check/mod.rs @@ -4,15 +4,15 @@ use conduit::Result; use ruma::events::room::message::RoomMessageEventContent; -use self::commands::*; +use crate::Command; #[derive(Debug, Subcommand)] pub(super) enum CheckCommand { 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 { - CheckCommand::AllUsers => check_all_users(body).await?, + CheckCommand::AllUsers => context.check_all_users().await?, }) } diff --git a/src/admin/command.rs b/src/admin/command.rs new file mode 100644 index 000000000..fbfdd2ba8 --- /dev/null +++ b/src/admin/command.rs @@ -0,0 +1,6 @@ +use service::Services; + +pub(crate) struct Command<'a> { + pub(crate) services: &'a Services, + pub(crate) body: &'a [&'a str], +} diff --git a/src/admin/debug/commands.rs b/src/admin/debug/commands.rs index 307495d70..cf4ab31d7 100644 --- a/src/admin/debug/commands.rs +++ b/src/admin/debug/commands.rs @@ -16,19 +16,22 @@ events::room::message::RoomMessageEventContent, CanonicalJsonObject, EventId, OwnedRoomOrAliasId, RoomId, RoomVersionId, ServerName, }; -use service::services; use tokio::sync::RwLock; 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(" "); 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); - 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 .get("room_id") .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) .map_err(|_| Error::bad_database("Invalid room id field in event in database"))?; + let start = Instant::now(); - let count = services() + let count = self + .services .rooms .auth_chain .event_ids_iter(room_id, vec![event_id]) .await? .count(); + let elapsed = start.elapsed(); Ok(RoomMessageEventContent::text_plain(format!( "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> { - if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { +#[admin_command] +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( "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) { Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) { 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 pdu_json = services() + let mut pdu_json = self + .services .rooms .timeline .get_non_outlier_pdu_json(&event_id)?; if pdu_json.is_none() { 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 { 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( - body: &[&str], server: Box<ServerName>, force: bool, + &self, server: Box<ServerName>, force: bool, ) -> Result<RoomMessageEventContent> { - if !services().globals.config.allow_federation { + if !self.services.globals.config.allow_federation { return Ok(RoomMessageEventContent::text_plain( "Federation is disabled on this homeserver.", )); } - if server == services().globals.server_name() { + if server == self.services.globals.server_name() { return Ok(RoomMessageEventContent::text_plain( "Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs from \ 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( "Expected code block in command body. Add --help for details.", )); } - let list = body + let list = self + .body .iter() .collect::<Vec<_>>() - .drain(1..body.len().saturating_sub(1)) + .drain(1..self.body.len().saturating_sub(1)) .filter_map(|pdu| EventId::parse(pdu).ok()) .collect::<Vec<_>>(); for pdu in list { if force { - if let Err(e) = get_remote_pdu(&[], Box::from(pdu), server.clone()).await { - services() + if let Err(e) = self.get_remote_pdu(Box::from(pdu), server.clone()).await { + self.services .admin .send_message(RoomMessageEventContent::text_plain(format!( "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"); } } 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.")) } +#[admin_command] 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> { - if !services().globals.config.allow_federation { + if !self.services.globals.config.allow_federation { return Ok(RoomMessageEventContent::text_plain( "Federation is disabled on this homeserver.", )); } - if server == services().globals.server_name() { + if server == self.services.globals.server_name() { return Ok(RoomMessageEventContent::text_plain( "Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs.", )); } - match services() + match self + .services .sending .send_federation_request( &server, @@ -191,7 +206,8 @@ pub(super) async fn get_remote_pdu( debug!("Attempting to parse PDU: {:?}", &response.pdu); let parsed_pdu = { - let parsed_result = services() + let parsed_result = self + .services .rooms .event_handler .parse_incoming_pdu(&response.pdu); @@ -212,7 +228,7 @@ pub(super) async fn get_remote_pdu( let pub_key_map = RwLock::new(BTreeMap::new()); debug!("Attempting to fetch homeserver signing keys for {server}"); - services() + self.services .rooms .event_handler .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"); - services() + self.services .rooms .timeline .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> { - let room_id = services().rooms.alias.resolve(&room).await?; - let room_state = services() +#[admin_command] +pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result<RoomMessageEventContent> { + let room_id = self.services.rooms.alias.resolve(&room).await?; + let room_state = self + .services .rooms .state_accessor .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```"))) } -pub(super) async fn ping(_body: &[&str], server: Box<ServerName>) -> Result<RoomMessageEventContent> { - if server == services().globals.server_name() { +#[admin_command] +pub(super) async fn ping(&self, server: Box<ServerName>) -> Result<RoomMessageEventContent> { + if server == self.services.globals.server_name() { return Ok(RoomMessageEventContent::text_plain( "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(); - match services() + match self + .services .sending .send_federation_request(&server, ruma::api::federation::discovery::get_server_version::v1::Request {}) .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 - for user_id in services().users.iter().filter_map(Result::ok) { - services().users.mark_device_key_update(&user_id)?; + for user_id in self.services.users.iter().filter_map(Result::ok) { + self.services.users.mark_device_key_update(&user_id)?; } Ok(RoomMessageEventContent::text_plain( "Marked all devices for all users as having new keys to update", )) } -pub(super) async fn change_log_level( - _body: &[&str], filter: Option<String>, reset: bool, -) -> Result<RoomMessageEventContent> { +#[admin_command] +pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool) -> Result<RoomMessageEventContent> { let handles = &["console"]; 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, Err(e) => { return Ok(RoomMessageEventContent::text_plain(format!( @@ -331,7 +351,8 @@ pub(super) async fn change_log_level( }, }; - match services() + match self + .services .server .log .reload @@ -340,7 +361,7 @@ pub(super) async fn change_log_level( Ok(()) => { return Ok(RoomMessageEventContent::text_plain(format!( "Successfully changed log level back to config value {}", - services().globals.config.log + self.services.globals.config.log ))); }, Err(e) => { @@ -361,7 +382,8 @@ pub(super) async fn change_log_level( }, }; - match services() + match self + .services .server .log .reload @@ -381,19 +403,21 @@ pub(super) async fn change_log_level( Ok(RoomMessageEventContent::text_plain("No log level was specified.")) } -pub(super) async fn sign_json(body: &[&str]) -> Result<RoomMessageEventContent> { - if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { +#[admin_command] +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( "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) { Ok(mut value) => { ruma::signatures::sign_json( - services().globals.server_name().as_str(), - services().globals.keypair(), + self.services.globals.server_name().as_str(), + self.services.globals.keypair(), &mut value, ) .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> { - if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { +#[admin_command] +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( "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) { Ok(value) => { let pub_key_map = RwLock::new(BTreeMap::new()); - services() + self.services .rooms .event_handler .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))] -pub(super) async fn first_pdu_in_room(_body: &[&str], room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { - if !services() +#[admin_command] +#[tracing::instrument(skip(self))] +pub(super) async fn first_pdu_in_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { + if !self + .services .rooms .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( "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 .timeline .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:?}"))) } -#[tracing::instrument(skip(_body))] -pub(super) async fn latest_pdu_in_room(_body: &[&str], room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { - if !services() +#[admin_command] +#[tracing::instrument(skip(self))] +pub(super) async fn latest_pdu_in_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { + if !self + .services .rooms .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( "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 .timeline .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:?}"))) } -#[tracing::instrument(skip(_body))] +#[admin_command] +#[tracing::instrument(skip(self))] 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> { - if !services() + if !self + .services .rooms .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( "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 .timeline .latest_pdu_in_room(&room_id)? .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 pub_key_map = RwLock::new(BTreeMap::new()); - let remote_state_response = services() + let remote_state_response = self + .services .sending .send_federation_request( &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()); 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, Err(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"); - services() + self.services .rooms .event_handler .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 .pdus .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 { continue; @@ -546,12 +582,13 @@ pub(super) async fn force_set_room_state_from_server( Error::BadServerResponse("Invalid PDU in send_join response.") })?; - services() + self.services .rooms .outlier .add_pdu_outlier(&event_id, &value)?; if let Some(state_key) = &pdu.state_key { - let shortstatekey = services() + let shortstatekey = self + .services .rooms .short .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 .auth_chain .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 { continue; }; - services() + self.services .rooms .outlier .add_pdu_outlier(&event_id, &value)?; } - let new_room_state = services() + let new_room_state = self + .services .rooms .event_handler .resolve_state(room_id.clone().as_ref(), &room_version, state) .await?; info!("Forcing new room state"); - let (short_state_hash, new, removed) = services() + let (short_state_hash, new, removed) = self + .services .rooms .state_compressor .save_state(room_id.clone().as_ref(), new_room_state)?; - let state_lock = services().rooms.state.mutex.lock(&room_id).await; - services() + let state_lock = self.services.rooms.state.mutex.lock(&room_id).await; + self.services .rooms .state .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 \ 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); @@ -607,28 +649,30 @@ pub(super) async fn force_set_room_state_from_server( )) } +#[admin_command] 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> { - let server_name = server_name.unwrap_or_else(|| services().server.config.server_name.clone().into()); - let signing_keys = services().globals.signing_keys_for(&server_name)?; + let server_name = server_name.unwrap_or_else(|| self.services.server.config.server_name.clone().into()); + let signing_keys = self.services.globals.signing_keys_for(&server_name)?; Ok(RoomMessageEventContent::notice_markdown(format!( "```rs\n{signing_keys:#?}\n```" ))) } +#[admin_command] #[allow(dead_code)] 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> { - 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(); if cached { writeln!(out, "| Key ID | VerifyKey |")?; 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:?} |")?; } @@ -636,7 +680,8 @@ pub(super) async fn get_verify_keys( } let signature_ids: Vec<String> = Vec::new(); - let keys = services() + let keys = self + .services .rooms .event_handler .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)) } +#[admin_command] 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> { - if !services().globals.config.allow_federation { + if !self.services.globals.config.allow_federation { return Ok(RoomMessageEventContent::text_plain( "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( "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") }; - let state = &services().server.log.capture; + let state = &self.services.server.log.capture; let logs = Arc::new(Mutex::new(String::new())); let capture = Capture::new(state, Some(filter), capture::fmt_markdown(logs.clone())); let capture_scope = capture.start(); - let actual = services() + let actual = self + .services .resolver .resolve_actual_dest(&server_name, !no_cache) .await?; @@ -692,7 +739,8 @@ pub(super) async fn resolve_true_destination( 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(); if html_body.is_none() { @@ -708,8 +756,9 @@ pub(super) async fn memory_stats(_body: &[&str]) -> Result<RoomMessageEventConte } #[cfg(tokio_unstable)] -pub(super) async fn runtime_metrics(_body: &[&str]) -> Result<RoomMessageEventContent> { - let out = services().server.metrics.runtime_metrics().map_or_else( +#[admin_command] +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(), |metrics| format!("```rs\n{metrics:#?}\n```"), ); @@ -718,15 +767,17 @@ pub(super) async fn runtime_metrics(_body: &[&str]) -> Result<RoomMessageEventCo } #[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( "Runtime metrics require building with `tokio_unstable`.", )) } #[cfg(tokio_unstable)] -pub(super) async fn runtime_interval(_body: &[&str]) -> Result<RoomMessageEventContent> { - let out = services().server.metrics.runtime_interval().map_or_else( +#[admin_command] +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(), |metrics| format!("```rs\n{metrics:#?}\n```"), ); @@ -735,18 +786,21 @@ pub(super) async fn runtime_interval(_body: &[&str]) -> Result<RoomMessageEventC } #[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( "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(); 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 { let out = info::cargo::dependencies_names().join(" "); return Ok(RoomMessageEventContent::notice_markdown(out)); diff --git a/src/admin/debug/mod.rs b/src/admin/debug/mod.rs index babd6047d..fbe6fd264 100644 --- a/src/admin/debug/mod.rs +++ b/src/admin/debug/mod.rs @@ -3,10 +3,10 @@ use clap::Subcommand; use conduit::Result; -use conduit_macros::admin_command_dispatch; -use ruma::{events::room::message::RoomMessageEventContent, EventId, OwnedRoomOrAliasId, RoomId, ServerName}; +use ruma::{EventId, OwnedRoomOrAliasId, RoomId, ServerName}; -use self::{commands::*, tester::TesterCommand}; +use self::tester::TesterCommand; +use crate::admin_command_dispatch; #[admin_command_dispatch] #[derive(Debug, Subcommand)] diff --git a/src/admin/debug/tester.rs b/src/admin/debug/tester.rs index 2765a344d..af4ea2dca 100644 --- a/src/admin/debug/tester.rs +++ b/src/admin/debug/tester.rs @@ -1,33 +1,29 @@ use ruma::events::room::message::RoomMessageEventContent; -use crate::Result; +use crate::{admin_command, admin_command_dispatch, Result}; +#[admin_command_dispatch] #[derive(Debug, clap::Subcommand)] pub(crate) enum TesterCommand { Tester, 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)] #[rustfmt::skip] #[allow(unused_variables)] -async fn tester(body: Vec<&str>) -> Result<RoomMessageEventContent> { +#[admin_command] +async fn tester(&self) -> Result<RoomMessageEventContent> { Ok(RoomMessageEventContent::notice_plain("completed")) } #[inline(never)] #[rustfmt::skip] -async fn timer(body: Vec<&str>) -> Result<RoomMessageEventContent> { +#[admin_command] +async fn timer(&self) -> Result<RoomMessageEventContent> { let started = std::time::Instant::now(); - timed(&body); + timed(self.body); let elapsed = started.elapsed(); Ok(RoomMessageEventContent::notice_plain(format!("completed in {elapsed:#?}"))) diff --git a/src/admin/federation/commands.rs b/src/admin/federation/commands.rs index d6ecd3f7c..8917a46b9 100644 --- a/src/admin/federation/commands.rs +++ b/src/admin/federation/commands.rs @@ -1,21 +1,26 @@ use std::fmt::Write; +use conduit::Result; 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> { - services().rooms.metadata.disable_room(&room_id, true)?; +#[admin_command] +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.")) } -pub(super) async fn enable_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { - services().rooms.metadata.disable_room(&room_id, false)?; +#[admin_command] +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.")) } -pub(super) async fn incoming_federation(_body: Vec<&str>) -> Result<RoomMessageEventContent> { - let map = services() +#[admin_command] +pub(super) async fn incoming_federation(&self) -> Result<RoomMessageEventContent> { + let map = self + .services .rooms .event_handler .federation_handletime @@ -31,10 +36,10 @@ pub(super) async fn incoming_federation(_body: Vec<&str>) -> Result<RoomMessageE Ok(RoomMessageEventContent::text_plain(&msg)) } -pub(super) async fn fetch_support_well_known( - _body: Vec<&str>, server_name: Box<ServerName>, -) -> Result<RoomMessageEventContent> { - let response = services() +#[admin_command] +pub(super) async fn fetch_support_well_known(&self, server_name: Box<ServerName>) -> Result<RoomMessageEventContent> { + let response = self + .services .client .default .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> { - if user_id.server_name() == services().globals.config.server_name { +#[admin_command] +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( "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( "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 .state_cache .rooms_joined(&user_id) .filter_map(Result::ok) - .map(|room_id| get_room_info(services(), &room_id)) + .map(|room_id| get_room_info(self.services, &room_id)) .collect(); if rooms.is_empty() { diff --git a/src/admin/federation/mod.rs b/src/admin/federation/mod.rs index d02b42956..8f5d3fae5 100644 --- a/src/admin/federation/mod.rs +++ b/src/admin/federation/mod.rs @@ -2,10 +2,11 @@ use clap::Subcommand; 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)] pub(super) enum FederationCommand { /// - List all rooms we are currently handling an incoming pdu from @@ -39,21 +40,3 @@ pub(super) enum FederationCommand { 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?, - }) -} diff --git a/src/admin/handler.rs b/src/admin/handler.rs index 26a5ea419..32360c855 100644 --- a/src/admin/handler.rs +++ b/src/admin/handler.rs @@ -15,10 +15,10 @@ Services, }; -use crate::{admin, admin::AdminCommand}; +use crate::{admin, admin::AdminCommand, Command}; -struct Handler<'a> { - services: &'a Services, +struct Handler { + services: &'static Services, } #[must_use] @@ -68,13 +68,12 @@ fn reply(mut content: RoomMessageEventContent, reply_id: Option<OwnedEventId>) - Some(content) } -impl Handler<'_> { +impl Handler { // Parse and process a message from the admin room async fn process(&self, msg: &str) -> CommandOutput { let mut lines = msg.lines().filter(|l| !l.trim().is_empty()); let command = lines.next().expect("each string has at least one line"); - let body = lines.collect::<Vec<_>>(); - let parsed = match self.parse_command(command) { + let (parsed, body) = match self.parse_command(command) { Ok(parsed) => parsed, Err(error) => { let server_name = self.services.globals.server_name(); @@ -84,7 +83,12 @@ async fn process(&self, msg: &str) -> CommandOutput { }; 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(); conduit::debug!(?command, ok = result.is_ok(), "command processed in {elapsed:?}"); match result { @@ -96,9 +100,10 @@ async fn process(&self, msg: &str) -> CommandOutput { } // 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); - 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 { diff --git a/src/admin/media/commands.rs b/src/admin/media/commands.rs index d29d5f47d..7906d951b 100644 --- a/src/admin/media/commands.rs +++ b/src/admin/media/commands.rs @@ -1,11 +1,11 @@ -use conduit::Result; +use conduit::{debug, info, Result}; 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( - _body: Vec<&str>, mxc: Option<Box<MxcUri>>, event_id: Option<Box<EventId>>, + &self, mxc: Option<Box<MxcUri>>, event_id: Option<Box<EventId>>, ) -> Result<RoomMessageEventContent> { if event_id.is_some() && mxc.is_some() { return Ok(RoomMessageEventContent::text_plain( @@ -15,7 +15,7 @@ pub(super) async fn delete( if let Some(mxc) = 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( "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; // 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") { debug!("Event ID has \"content\"."); let content_obj = content_key.as_object(); @@ -123,7 +123,7 @@ pub(super) async fn delete( } 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); } @@ -138,23 +138,26 @@ pub(super) async fn delete( )) } -pub(super) async fn delete_list(body: Vec<&str>) -> Result<RoomMessageEventContent> { - if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { +#[admin_command] +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( "Expected code block in command body. Add --help for details.", )); } - let mxc_list = body - .clone() - .drain(1..body.len().checked_sub(1).unwrap()) + let mxc_list = self + .body + .to_vec() + .drain(1..self.body.len().checked_sub(1).unwrap()) .collect::<Vec<_>>(); let mut mxc_deletion_count: usize = 0; for mxc in mxc_list { debug!("Deleting MXC {mxc} in bulk"); - services().media.delete(mxc).await?; + self.services.media.delete(mxc).await?; mxc_deletion_count = mxc_deletion_count .checked_add(1) .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( - _body: Vec<&str>, duration: String, force: bool, -) -> Result<RoomMessageEventContent> { - let deleted_count = services() +#[admin_command] +pub(super) async fn delete_past_remote_media(&self, duration: String, force: bool) -> Result<RoomMessageEventContent> { + let deleted_count = self + .services .media .delete_all_remote_media_at_after_time(duration, force) .await?; diff --git a/src/admin/media/mod.rs b/src/admin/media/mod.rs index d30c55d0b..31cbf810e 100644 --- a/src/admin/media/mod.rs +++ b/src/admin/media/mod.rs @@ -2,10 +2,11 @@ use clap::Subcommand; 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)] pub(super) enum MediaCommand { /// - Deletes a single media file from our database and on the filesystem @@ -36,17 +37,3 @@ pub(super) enum MediaCommand { 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?, - }) -} diff --git a/src/admin/mod.rs b/src/admin/mod.rs index cd1110ee3..5d4c8f5e4 100644 --- a/src/admin/mod.rs +++ b/src/admin/mod.rs @@ -3,6 +3,7 @@ #![allow(clippy::enum_glob_use)] pub(crate) mod admin; +pub(crate) mod command; pub(crate) mod handler; mod tests; pub(crate) mod utils; @@ -22,9 +23,13 @@ extern crate conduit_service as service; pub(crate) use conduit::Result; +pub(crate) use conduit_macros::{admin_command, admin_command_dispatch}; 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; diff --git a/src/admin/query/account_data.rs b/src/admin/query/account_data.rs index ce9caedb4..e18c298a3 100644 --- a/src/admin/query/account_data.rs +++ b/src/admin/query/account_data.rs @@ -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::{services, Result}; +use crate::Command; +#[derive(Debug, Subcommand)] /// All the getters and iterators from src/database/key_value/account_data.rs -pub(super) async fn account_data(subcommand: AccountData) -> Result<RoomMessageEventContent> { +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 +pub(super) async fn process(subcommand: AccountDataCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> { + let services = context.services; + match subcommand { - AccountData::ChangesSince { + AccountDataCommand::ChangesSince { user_id, since, room_id, } => { let timer = tokio::time::Instant::now(); - let results = services() + let results = services .account_data .changes_since(room_id.as_deref(), &user_id, since)?; 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```" ))) }, - AccountData::Get { + AccountDataCommand::Get { user_id, kind, room_id, } => { let timer = tokio::time::Instant::now(); - let results = services() + let results = services .account_data .get(room_id.as_deref(), &user_id, kind)?; let query_time = timer.elapsed(); diff --git a/src/admin/query/appservice.rs b/src/admin/query/appservice.rs index 77ac96419..683c228f7 100644 --- a/src/admin/query/appservice.rs +++ b/src/admin/query/appservice.rs @@ -1,16 +1,32 @@ +use clap::Subcommand; +use conduit::Result; use ruma::events::room::message::RoomMessageEventContent; -use super::Appservice; -use crate::{services, Result}; +use crate::Command; +#[derive(Debug, Subcommand)] /// All the getters and iterators from src/database/key_value/appservice.rs -pub(super) async fn appservice(subcommand: Appservice) -> Result<RoomMessageEventContent> { +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 +pub(super) async fn process(subcommand: AppserviceCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> { + let services = context.services; + match subcommand { - Appservice::GetRegistration { + AppserviceCommand::GetRegistration { appservice_id, } => { let timer = tokio::time::Instant::now(); - let results = services() + let results = services .appservice .db .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```" ))) }, - Appservice::All => { + AppserviceCommand::All => { let timer = tokio::time::Instant::now(); - let results = services().appservice.all(); + let results = services.appservice.all(); let query_time = timer.elapsed(); Ok(RoomMessageEventContent::notice_markdown(format!( diff --git a/src/admin/query/globals.rs b/src/admin/query/globals.rs index 389860711..5f271c2c4 100644 --- a/src/admin/query/globals.rs +++ b/src/admin/query/globals.rs @@ -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::{services, Result}; +use crate::Command; +#[derive(Debug, Subcommand)] /// All the getters and iterators from src/database/key_value/globals.rs -pub(super) async fn globals(subcommand: Globals) -> Result<RoomMessageEventContent> { +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 +pub(super) async fn process(subcommand: GlobalsCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> { + let services = context.services; + match subcommand { - Globals::DatabaseVersion => { + GlobalsCommand::DatabaseVersion => { 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(); Ok(RoomMessageEventContent::notice_markdown(format!( "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" ))) }, - Globals::CurrentCount => { + GlobalsCommand::CurrentCount => { 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(); Ok(RoomMessageEventContent::notice_markdown(format!( "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" ))) }, - Globals::LastCheckForUpdatesId => { + GlobalsCommand::LastCheckForUpdatesId => { 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(); Ok(RoomMessageEventContent::notice_markdown(format!( "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" ))) }, - Globals::LoadKeypair => { + GlobalsCommand::LoadKeypair => { 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(); Ok(RoomMessageEventContent::notice_markdown(format!( "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" ))) }, - Globals::SigningKeysFor { + GlobalsCommand::SigningKeysFor { origin, } => { 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(); Ok(RoomMessageEventContent::notice_markdown(format!( diff --git a/src/admin/query/mod.rs b/src/admin/query/mod.rs index c86f4f538..1aa28c48b 100644 --- a/src/admin/query/mod.rs +++ b/src/admin/query/mod.rs @@ -10,304 +10,51 @@ use clap::Subcommand; use conduit::Result; -use room_state_cache::room_state_cache; -use ruma::{ - events::{room::message::RoomMessageEventContent, RoomAccountDataEventType}, - OwnedServerName, RoomAliasId, RoomId, ServerName, UserId, -}; use self::{ - account_data::account_data, appservice::appservice, globals::globals, presence::presence, resolver::resolver, - room_alias::room_alias, sending::sending, users::users, + account_data::AccountDataCommand, appservice::AppserviceCommand, globals::GlobalsCommand, + 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)] /// Query tables from database pub(super) enum QueryCommand { /// - account_data.rs iterators and getters #[command(subcommand)] - AccountData(AccountData), + AccountData(AccountDataCommand), /// - appservice.rs iterators and getters #[command(subcommand)] - Appservice(Appservice), + Appservice(AppserviceCommand), /// - presence.rs iterators and getters #[command(subcommand)] - Presence(Presence), + Presence(PresenceCommand), /// - rooms/alias.rs iterators and getters #[command(subcommand)] - RoomAlias(RoomAlias), + RoomAlias(RoomAliasCommand), /// - rooms/state_cache iterators and getters #[command(subcommand)] - RoomStateCache(RoomStateCache), + RoomStateCache(RoomStateCacheCommand), /// - globals.rs iterators and getters #[command(subcommand)] - Globals(Globals), + Globals(GlobalsCommand), /// - sending.rs iterators and getters #[command(subcommand)] - Sending(Sending), + Sending(SendingCommand), /// - users.rs iterators and getters #[command(subcommand)] - Users(Users), + Users(UsersCommand), /// - resolver service #[command(subcommand)] - Resolver(Resolver), -} - -#[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?, - }) + Resolver(ResolverCommand), } diff --git a/src/admin/query/presence.rs b/src/admin/query/presence.rs index c47b7a51d..145ecd9b1 100644 --- a/src/admin/query/presence.rs +++ b/src/admin/query/presence.rs @@ -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::{services, Result}; +use crate::Command; + +#[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 -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 { - Presence::GetPresence { + PresenceCommand::GetPresence { user_id, } => { 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(); Ok(RoomMessageEventContent::notice_markdown(format!( "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" ))) }, - Presence::PresenceSince { + PresenceCommand::PresenceSince { since, } => { 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 query_time = timer.elapsed(); diff --git a/src/admin/query/resolver.rs b/src/admin/query/resolver.rs index 37d179609..e8340dad8 100644 --- a/src/admin/query/resolver.rs +++ b/src/admin/query/resolver.rs @@ -1,24 +1,28 @@ use std::fmt::Write; +use clap::Subcommand; use conduit::{utils::time, Result}; use ruma::{events::room::message::RoomMessageEventContent, OwnedServerName}; -use super::Resolver; -use crate::services; +use crate::{admin_command, admin_command_dispatch}; -/// All the getters and iterators in key_value/users.rs -pub(super) async fn resolver(subcommand: Resolver) -> Result<RoomMessageEventContent> { - match subcommand { - Resolver::DestinationsCache { - server_name, - } => destinations_cache(server_name).await, - Resolver::OverridesCache { - name, - } => overrides_cache(name).await, - } +#[admin_command_dispatch] +#[derive(Debug, Subcommand)] +/// Resolver service and caches +pub(crate) enum ResolverCommand { + /// Query the destinations cache + DestinationsCache { + server_name: Option<OwnedServerName>, + }, + + /// 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; 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"); }; - let map = services() + let map = self + .services .resolver .cache .destinations @@ -52,7 +57,8 @@ async fn destinations_cache(server_name: Option<OwnedServerName>) -> Result<Room 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; 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"); }; - 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() { map.get_key_value(server_name).map(row); diff --git a/src/admin/query/room_alias.rs b/src/admin/query/room_alias.rs index d2c16801d..1809e26a0 100644 --- a/src/admin/query/room_alias.rs +++ b/src/admin/query/room_alias.rs @@ -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::{services, Result}; +use crate::Command; + +#[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 -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 { - RoomAlias::ResolveLocalAlias { + RoomAliasCommand::ResolveLocalAlias { alias, } => { 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(); Ok(RoomMessageEventContent::notice_markdown(format!( "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" ))) }, - RoomAlias::LocalAliasesForRoom { + RoomAliasCommand::LocalAliasesForRoom { room_id, } => { 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 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```" ))) }, - RoomAlias::AllLocalAliases => { + RoomAliasCommand::AllLocalAliases => { 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 query_time = timer.elapsed(); diff --git a/src/admin/query/room_state_cache.rs b/src/admin/query/room_state_cache.rs index aed2b4a26..4215cf8d6 100644 --- a/src/admin/query/room_state_cache.rs +++ b/src/admin/query/room_state_cache.rs @@ -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::{services, Result}; +use crate::Command; + +#[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 { - RoomStateCache::ServerInRoom { + RoomStateCacheCommand::ServerInRoom { server, room_id, } => { let timer = tokio::time::Instant::now(); - let result = services() - .rooms - .state_cache - .server_in_room(&server, &room_id); + let result = services.rooms.state_cache.server_in_room(&server, &room_id); let query_time = timer.elapsed(); Ok(RoomMessageEventContent::notice_markdown(format!( "Query completed in {query_time:?}:\n\n```rs\n{result:#?}\n```" ))) }, - RoomStateCache::RoomServers { + RoomStateCacheCommand::RoomServers { room_id, } => { let timer = tokio::time::Instant::now(); - let results: Result<Vec<_>> = services() - .rooms - .state_cache - .room_servers(&room_id) - .collect(); + let results: Result<Vec<_>> = services.rooms.state_cache.room_servers(&room_id).collect(); let query_time = timer.elapsed(); Ok(RoomMessageEventContent::notice_markdown(format!( "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" ))) }, - RoomStateCache::ServerRooms { + RoomStateCacheCommand::ServerRooms { server, } => { 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(); Ok(RoomMessageEventContent::notice_markdown(format!( "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" ))) }, - RoomStateCache::RoomMembers { + RoomStateCacheCommand::RoomMembers { room_id, } => { let timer = tokio::time::Instant::now(); - let results: Result<Vec<_>> = services() - .rooms - .state_cache - .room_members(&room_id) - .collect(); + let results: Result<Vec<_>> = services.rooms.state_cache.room_members(&room_id).collect(); let query_time = timer.elapsed(); Ok(RoomMessageEventContent::notice_markdown(format!( "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" ))) }, - RoomStateCache::LocalUsersInRoom { + RoomStateCacheCommand::LocalUsersInRoom { room_id, } => { let timer = tokio::time::Instant::now(); - let results: Vec<_> = services() + let results: Vec<_> = services .rooms .state_cache .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```" ))) }, - RoomStateCache::ActiveLocalUsersInRoom { + RoomStateCacheCommand::ActiveLocalUsersInRoom { room_id, } => { let timer = tokio::time::Instant::now(); - let results: Vec<_> = services() + let results: Vec<_> = services .rooms .state_cache .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```" ))) }, - RoomStateCache::RoomJoinedCount { + RoomStateCacheCommand::RoomJoinedCount { room_id, } => { 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(); Ok(RoomMessageEventContent::notice_markdown(format!( "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" ))) }, - RoomStateCache::RoomInvitedCount { + RoomStateCacheCommand::RoomInvitedCount { room_id, } => { 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(); Ok(RoomMessageEventContent::notice_markdown(format!( "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" ))) }, - RoomStateCache::RoomUserOnceJoined { + RoomStateCacheCommand::RoomUserOnceJoined { room_id, } => { let timer = tokio::time::Instant::now(); - let results: Result<Vec<_>> = services() + let results: Result<Vec<_>> = services .rooms .state_cache .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```" ))) }, - RoomStateCache::RoomMembersInvited { + RoomStateCacheCommand::RoomMembersInvited { room_id, } => { let timer = tokio::time::Instant::now(); - let results: Result<Vec<_>> = services() + let results: Result<Vec<_>> = services .rooms .state_cache .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```" ))) }, - RoomStateCache::GetInviteCount { + RoomStateCacheCommand::GetInviteCount { room_id, user_id, } => { let timer = tokio::time::Instant::now(); - let results = services() + let results = services .rooms .state_cache .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```" ))) }, - RoomStateCache::GetLeftCount { + RoomStateCacheCommand::GetLeftCount { room_id, user_id, } => { let timer = tokio::time::Instant::now(); - let results = services() + let results = services .rooms .state_cache .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```" ))) }, - RoomStateCache::RoomsJoined { + RoomStateCacheCommand::RoomsJoined { user_id, } => { let timer = tokio::time::Instant::now(); - let results: Result<Vec<_>> = services() - .rooms - .state_cache - .rooms_joined(&user_id) - .collect(); + let results: Result<Vec<_>> = services.rooms.state_cache.rooms_joined(&user_id).collect(); let query_time = timer.elapsed(); Ok(RoomMessageEventContent::notice_markdown(format!( "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" ))) }, - RoomStateCache::RoomsInvited { + RoomStateCacheCommand::RoomsInvited { user_id, } => { let timer = tokio::time::Instant::now(); - let results: Result<Vec<_>> = services() - .rooms - .state_cache - .rooms_invited(&user_id) - .collect(); + let results: Result<Vec<_>> = services.rooms.state_cache.rooms_invited(&user_id).collect(); let query_time = timer.elapsed(); Ok(RoomMessageEventContent::notice_markdown(format!( "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" ))) }, - RoomStateCache::RoomsLeft { + RoomStateCacheCommand::RoomsLeft { user_id, } => { 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(); Ok(RoomMessageEventContent::notice_markdown(format!( "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```" ))) }, - RoomStateCache::InviteState { + RoomStateCacheCommand::InviteState { user_id, room_id, } => { let timer = tokio::time::Instant::now(); - let results = services() - .rooms - .state_cache - .invite_state(&user_id, &room_id); + let results = services.rooms.state_cache.invite_state(&user_id, &room_id); let query_time = timer.elapsed(); Ok(RoomMessageEventContent::notice_markdown(format!( diff --git a/src/admin/query/sending.rs b/src/admin/query/sending.rs index 4e82695d3..6d54bddfd 100644 --- a/src/admin/query/sending.rs +++ b/src/admin/query/sending.rs @@ -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::{service::sending::Destination, services, Result}; +use crate::Command; + +#[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 -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 { - Sending::ActiveRequests => { + SendingCommand::ActiveRequests => { 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 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```" ))) }, - Sending::QueuedRequests { + SendingCommand::QueuedRequests { appservice_id, server_name, user_id, @@ -38,12 +97,12 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte )); } - services() + services .sending .db .queued_requests(&Destination::Appservice(appservice_id)) }, - (None, Some(server_name), None, None) => services() + (None, Some(server_name), None, None) => services .sending .db .queued_requests(&Destination::Normal(server_name.into())), @@ -55,7 +114,7 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte )); } - services() + services .sending .db .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```" ))) }, - Sending::ActiveRequestsFor { + SendingCommand::ActiveRequestsFor { appservice_id, server_name, user_id, @@ -104,12 +163,12 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte )); } - services() + services .sending .db .active_requests_for(&Destination::Appservice(appservice_id)) }, - (None, Some(server_name), None, None) => services() + (None, Some(server_name), None, None) => services .sending .db .active_requests_for(&Destination::Normal(server_name.into())), @@ -121,7 +180,7 @@ pub(super) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte )); } - services() + services .sending .db .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```" ))) }, - Sending::GetLatestEduCount { + SendingCommand::GetLatestEduCount { server_name, } => { 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(); Ok(RoomMessageEventContent::notice_markdown(format!( diff --git a/src/admin/query/users.rs b/src/admin/query/users.rs index 2e73bff37..fee12fbfc 100644 --- a/src/admin/query/users.rs +++ b/src/admin/query/users.rs @@ -1,14 +1,23 @@ +use clap::Subcommand; +use conduit::Result; use ruma::events::room::message::RoomMessageEventContent; -use super::Users; -use crate::{services, Result}; +use crate::Command; + +#[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 -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 { - Users::Iter => { + UsersCommand::Iter => { let timer = tokio::time::Instant::now(); - let results = services().users.db.iter(); + let results = services.users.db.iter(); let users = results.collect::<Vec<_>>(); let query_time = timer.elapsed(); diff --git a/src/admin/room/room_alias_commands.rs b/src/admin/room/alias.rs similarity index 73% rename from src/admin/room/room_alias_commands.rs rename to src/admin/room/alias.rs index 4f43ac6e8..415b8a083 100644 --- a/src/admin/room/room_alias_commands.rs +++ b/src/admin/room/alias.rs @@ -1,12 +1,49 @@ 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, services, Result}; +use crate::{escape_html, Command}; -pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> { - let server_user = &services().globals.server_user; +#[derive(Debug, Subcommand)] +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 { RoomAliasCommand::Set { @@ -19,7 +56,7 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu | RoomAliasCommand::Which { 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) { Ok(alias) => alias, 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, room_id, .. - } => match (force, services().rooms.alias.resolve_local_alias(&room_alias)) { - (true, Ok(Some(id))) => match services() + } => match (force, services.rooms.alias.resolve_local_alias(&room_alias)) { + (true, Ok(Some(id))) => match services .rooms .alias .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!( "Refusing to overwrite in use alias for {id}, use -f or --force to overwrite" ))), - (_, Ok(None)) => match services() + (_, Ok(None)) => match services .rooms .alias .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 { .. - } => match services().rooms.alias.resolve_local_alias(&room_alias) { - Ok(Some(id)) => match services() + } => match services.rooms.alias.resolve_local_alias(&room_alias) { + Ok(Some(id)) => match services .rooms .alias .remove_alias(&room_alias, server_user) @@ -70,7 +107,7 @@ pub(super) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu }, 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(None) => Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")), 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, } => { if let Some(room_id) = room_id { - let aliases = services() + let aliases = services .rooms .alias .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}"))), } } else { - let aliases = services() + let aliases = services .rooms .alias .all_local_aliases() .collect::<Result<Vec<_>, _>>(); match aliases { Ok(aliases) => { - let server_name = services().globals.server_name(); + let server_name = services.globals.server_name(); let plain_list = aliases .iter() .fold(String::new(), |mut output, (alias, id)| { diff --git a/src/admin/room/room_commands.rs b/src/admin/room/commands.rs similarity index 82% rename from src/admin/room/room_commands.rs rename to src/admin/room/commands.rs index cf0f3ddbd..2e14a8498 100644 --- a/src/admin/room/room_commands.rs +++ b/src/admin/room/commands.rs @@ -1,15 +1,18 @@ use std::fmt::Write; +use conduit::Result; 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( - _body: Vec<&str>, page: Option<usize>, exclude_disabled: bool, exclude_banned: bool, +#[admin_command] +pub(super) async fn list_rooms( + &self, page: Option<usize>, exclude_disabled: bool, exclude_banned: bool, ) -> Result<RoomMessageEventContent> { // 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 mut rooms = services() + let mut rooms = self + .services .rooms .metadata .iter_ids() @@ -18,7 +21,8 @@ pub(super) async fn list( .ok() .filter(|room_id| { if exclude_disabled - && services() + && self + .services .rooms .metadata .is_disabled(room_id) @@ -28,7 +32,8 @@ pub(super) async fn list( } if exclude_banned - && services() + && self + .services .rooms .metadata .is_banned(room_id) @@ -39,7 +44,7 @@ pub(super) async fn list( true }) - .map(|room_id| get_room_info(services(), &room_id)) + .map(|room_id| get_room_info(self.services, &room_id)) }) .collect::<Vec<_>>(); rooms.sort_by_key(|r| r.1); diff --git a/src/admin/room/room_directory_commands.rs b/src/admin/room/directory.rs similarity index 68% rename from src/admin/room/room_directory_commands.rs rename to src/admin/room/directory.rs index 912e970c6..7bba2eb7b 100644 --- a/src/admin/room/room_directory_commands.rs +++ b/src/admin/room/directory.rs @@ -1,21 +1,43 @@ 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, services, Result, PAGE_SIZE}; +use crate::{escape_html, get_room_info, Command, 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 { RoomDirectoryCommand::Publish { 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")), Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to update room: {err}"))), }, RoomDirectoryCommand::Unpublish { 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")), 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 let page = page.unwrap_or(1); - let mut rooms = services() + let mut rooms = services .rooms .directory .public_rooms() .filter_map(Result::ok) - .map(|id: OwnedRoomId| get_room_info(services(), &id)) + .map(|id: OwnedRoomId| get_room_info(services, &id)) .collect::<Vec<_>>(); rooms.sort_by_key(|r| r.1); rooms.reverse(); diff --git a/src/admin/room/room_info_commands.rs b/src/admin/room/info.rs similarity index 54% rename from src/admin/room/room_info_commands.rs rename to src/admin/room/info.rs index 9182616fd..8ba0a7963 100644 --- a/src/admin/room/room_info_commands.rs +++ b/src/admin/room/info.rs @@ -1,22 +1,30 @@ +use clap::Subcommand; +use conduit::Result; use ruma::{events::room::message::RoomMessageEventContent, RoomId}; -use service::services; -use super::RoomInfoCommand; -use crate::Result; +use crate::{admin_command, admin_command_dispatch}; -pub(super) async fn process(command: RoomInfoCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> { - match command { - RoomInfoCommand::ListJoinedMembers { - room_id, - } => list_joined_members(body, room_id).await, - RoomInfoCommand::ViewRoomTopic { - room_id, - } => view_room_topic(body, room_id).await, - } +#[admin_command_dispatch] +#[derive(Debug, Subcommand)] +pub(crate) 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>, + }, } -async fn list_joined_members(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { - let room_name = services() +#[admin_command] +async fn list_joined_members(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { + let room_name = self + .services .rooms .state_accessor .get_name(&room_id) @@ -24,7 +32,8 @@ async fn list_joined_members(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<R .flatten() .unwrap_or_else(|| room_id.to_string()); - let members = services() + let members = self + .services .rooms .state_cache .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| { ( user_id.clone(), - services() + self.services .users .displayname(&user_id) .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)) } -async fn view_room_topic(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { - let Some(room_topic) = services().rooms.state_accessor.get_room_topic(&room_id)? else { +#[admin_command] +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.")); }; diff --git a/src/admin/room/mod.rs b/src/admin/room/mod.rs index 8f125f0a3..da7acb80c 100644 --- a/src/admin/room/mod.rs +++ b/src/admin/room/mod.rs @@ -1,19 +1,23 @@ -mod room_alias_commands; -mod room_commands; -mod room_directory_commands; -mod room_info_commands; -mod room_moderation_commands; +mod alias; +mod commands; +mod directory; +mod info; +mod moderation; use clap::Subcommand; 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)] pub(super) enum RoomCommand { /// - List all rooms the server knows about - List { + #[clap(alias = "list")] + ListRooms { page: Option<usize>, /// Excludes rooms that we have federation disabled with @@ -41,149 +45,3 @@ pub(super) enum RoomCommand { /// - Manage the room directory 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?, - }) -} diff --git a/src/admin/room/room_moderation_commands.rs b/src/admin/room/moderation.rs similarity index 72% rename from src/admin/room/room_moderation_commands.rs rename to src/admin/room/moderation.rs index 8ad8295b0..869103798 100644 --- a/src/admin/room/room_moderation_commands.rs +++ b/src/admin/room/moderation.rs @@ -1,37 +1,77 @@ use api::client::leave_room; +use clap::Subcommand; use conduit::{debug, error, info, warn, Result}; use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomAliasId, RoomId, RoomOrAliasId}; -use super::RoomModerationCommand; -use crate::{get_room_info, services}; - -pub(super) async fn process(command: RoomModerationCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> { - match command { - RoomModerationCommand::BanRoom { - force, - room, - disable_federation, - } => ban_room(body, force, room, disable_federation).await, - RoomModerationCommand::BanListOfRooms { - force, - disable_federation, - } => ban_list_of_rooms(body, force, disable_federation).await, - RoomModerationCommand::UnbanRoom { - room, - enable_federation, - } => unban_room(body, room, enable_federation).await, - RoomModerationCommand::ListBannedRooms => list_banned_rooms(body).await, - } +use crate::{admin_command, admin_command_dispatch, get_room_info}; + +#[admin_command_dispatch] +#[derive(Debug, Subcommand)] +pub(crate) 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, } +#[admin_command] 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> { 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) { 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"); - services().rooms.metadata.ban_room(&room_id, true)?; + self.services.rooms.metadata.ban_room(&room_id, true)?; room_id } else if room.is_room_alias_id() { @@ -69,12 +109,13 @@ async fn ban_room( 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 } else { 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 .alias .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 } else { @@ -104,20 +145,21 @@ async fn ban_room( debug!("Making all users leave the room {}", &room); if force { - for local_user in services() + for local_user in self + .services .rooms .state_cache .room_members(&room_id) .filter_map(|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 // who are in the admin room to the list of local users (would // 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 // if somehow this fails - && services() + && self.services .users .is_admin(local_user) .unwrap_or(true)) @@ -128,30 +170,31 @@ async fn ban_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 { warn!(%e, "Failed to leave room"); } } } else { - for local_user in services() + for local_user in self + .services .rooms .state_cache .room_members(&room_id) .filter_map(|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 // who are in the admin room to the list of local users (would fail auth check) && (local_user.server_name() - == services().globals.server_name() - && !services() + == self.services.globals.server_name() + && !self.services .users .is_admin(local_user) .unwrap_or(false)) }) }) { 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 attempting to make local user {} leave room {} during room banning: {}", &local_user, &room_id, e @@ -166,7 +209,7 @@ async fn ban_room( } 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( "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> { - if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { +#[admin_command] +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( "Expected code block in command body. Add --help for details.", )); } - let rooms_s = body - .clone() - .drain(1..body.len().saturating_sub(1)) + let rooms_s = self + .body + .to_vec() + .drain(1..self.body.len().saturating_sub(1)) .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_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 { match <&RoomOrAliasId>::try_from(room) { 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) { info!("User specified admin room in bulk ban list, ignoring"); 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) { Ok(room_alias) => { 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 } else { debug!( @@ -239,7 +285,8 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo ID over federation" ); - match services() + match self + .services .rooms .alias .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 { - 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"); room_ban_count = room_ban_count.saturating_add(1); } debug!("Making all users leave the room {}", &room_id); if force { - for local_user in services() + for local_user in self + .services .rooms .state_cache .room_members(&room_id) .filter_map(|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 who are in the admin room to the list of local // users (would fail auth check) && (local_user.server_name() - == services().globals.server_name() + == self.services.globals.server_name() // since this is a force operation, assume user is an // admin if somehow this fails - && services() + && self.services .users .is_admin(local_user) .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)", &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"); } } } else { - for local_user in services() + for local_user in self + .services .rooms .state_cache .room_members(&room_id) .filter_map(|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 who are in the admin room to the list of local // users (would fail auth check) && (local_user.server_name() - == services().globals.server_name() - && !services() + == self.services.globals.server_name() + && !self.services .users .is_admin(local_user) .unwrap_or(false)) }) }) { 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 attempting to make local user {} leave room {} during bulk room banning: {}", &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 { - 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( - _body: Vec<&str>, room: Box<RoomOrAliasId>, enable_federation: bool, -) -> Result<RoomMessageEventContent> { +#[admin_command] +async fn unban_room(&self, enable_federation: bool, room: Box<RoomOrAliasId>) -> Result<RoomMessageEventContent> { let room_id = if room.is_room_id() { let room_id = match RoomId::parse(&room) { Ok(room_id) => room_id, @@ -406,7 +460,7 @@ async fn unban_room( 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 } else if room.is_room_alias_id() { @@ -425,12 +479,13 @@ async fn unban_room( 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 } else { 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 .alias .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 } else { @@ -459,7 +514,7 @@ async fn unban_room( }; 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.")); } @@ -469,8 +524,10 @@ async fn unban_room( )) } -async fn list_banned_rooms(_body: Vec<&str>) -> Result<RoomMessageEventContent> { - let rooms = services() +#[admin_command] +async fn list_banned_rooms(&self) -> Result<RoomMessageEventContent> { + let rooms = self + .services .rooms .metadata .list_banned_rooms() @@ -484,7 +541,7 @@ async fn list_banned_rooms(_body: Vec<&str>) -> Result<RoomMessageEventContent> let mut rooms = room_ids .into_iter() - .map(|room_id| get_room_info(services(), &room_id)) + .map(|room_id| get_room_info(self.services, &room_id)) .collect::<Vec<_>>(); rooms.sort_by_key(|r| r.1); rooms.reverse(); diff --git a/src/admin/server/commands.rs b/src/admin/server/commands.rs index c30f8f871..34b0f0a01 100644 --- a/src/admin/server/commands.rs +++ b/src/admin/server/commands.rs @@ -1,12 +1,14 @@ -use std::fmt::Write; +use std::{fmt::Write, sync::Arc}; use conduit::{info, utils::time, warn, Err, Result}; use ruma::events::room::message::RoomMessageEventContent; -use crate::services; +use crate::admin_command; -pub(super) async fn uptime(_body: Vec<&str>) -> Result<RoomMessageEventContent> { - let elapsed = services() +#[admin_command] +pub(super) async fn uptime(&self) -> Result<RoomMessageEventContent> { + let elapsed = self + .services .server .started .elapsed() @@ -16,13 +18,15 @@ pub(super) async fn uptime(_body: Vec<&str>) -> Result<RoomMessageEventContent> 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 - 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( - _body: Vec<&str>, available: bool, enabled: bool, comma: bool, + &self, available: bool, enabled: bool, comma: bool, ) -> Result<RoomMessageEventContent> { let delim = if comma { "," @@ -62,9 +66,10 @@ pub(super) async fn list_features( Ok(RoomMessageEventContent::text_markdown(features)) } -pub(super) async fn memory_usage(_body: Vec<&str>) -> Result<RoomMessageEventContent> { - let services_usage = services().memory_usage().await?; - let database_usage = services().db.db.memory_usage()?; +#[admin_command] +pub(super) async fn memory_usage(&self) -> Result<RoomMessageEventContent> { + 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}")); 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> { - services().clear_cache().await; +#[admin_command] +pub(super) async fn clear_caches(&self) -> Result<RoomMessageEventContent> { + self.services.clear_cache().await; Ok(RoomMessageEventContent::text_plain("Done.")) } -pub(super) async fn list_backups(_body: Vec<&str>) -> Result<RoomMessageEventContent> { - let result = services().globals.db.backup_list()?; +#[admin_command] +pub(super) async fn list_backups(&self) -> Result<RoomMessageEventContent> { + let result = self.services.globals.db.backup_list()?; if result.is_empty() { 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> { - let mut result = services() +#[admin_command] +pub(super) async fn backup_database(&self) -> Result<RoomMessageEventContent> { + let globals = Arc::clone(&self.services.globals); + let mut result = self + .services .server .runtime() - .spawn_blocking(move || match services().globals.db.backup() { + .spawn_blocking(move || match globals.db.backup() { Ok(()) => String::new(), Err(e) => (*e).to_string(), }) - .await - .unwrap(); + .await?; if result.is_empty() { - result = services().globals.db.backup_list()?; + result = self.services.globals.db.backup_list()?; } - Ok(RoomMessageEventContent::text_plain(&result)) + Ok(RoomMessageEventContent::notice_markdown(result)) } -pub(super) async fn list_database_files(_body: Vec<&str>) -> Result<RoomMessageEventContent> { - let result = services().globals.db.file_list()?; +#[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)) } -pub(super) async fn admin_notice(_body: Vec<&str>, message: Vec<String>) -> Result<RoomMessageEventContent> { +#[admin_command] +pub(super) async fn admin_notice(&self, message: Vec<String>) -> Result<RoomMessageEventContent> { 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")) } -#[cfg(conduit_mods)] -pub(super) async fn reload(_body: Vec<&str>) -> Result<RoomMessageEventContent> { - services().server.reload()?; +#[admin_command] +pub(super) async fn reload_mods(&self) -> Result<RoomMessageEventContent> { + self.services.server.reload()?; Ok(RoomMessageEventContent::notice_plain("Reloading server...")) } +#[admin_command] #[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; 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...")) } -pub(super) async fn shutdown(_body: Vec<&str>) -> Result<RoomMessageEventContent> { +#[admin_command] +pub(super) async fn shutdown(&self) -> Result<RoomMessageEventContent> { warn!("shutdown command"); - services().server.shutdown()?; + self.services.server.shutdown()?; Ok(RoomMessageEventContent::notice_plain("Shutting down server...")) } diff --git a/src/admin/server/mod.rs b/src/admin/server/mod.rs index 958cc54ba..222c537a0 100644 --- a/src/admin/server/mod.rs +++ b/src/admin/server/mod.rs @@ -2,10 +2,10 @@ use clap::Subcommand; use conduit::Result; -use ruma::events::room::message::RoomMessageEventContent; -use self::commands::*; +use crate::admin_command_dispatch; +#[admin_command_dispatch] #[derive(Debug, Subcommand)] pub(super) enum ServerCommand { /// - Time elapsed since startup @@ -47,9 +47,9 @@ pub(super) enum ServerCommand { message: Vec<String>, }, - #[cfg(conduit_mods)] /// - Hot-reload the server - Reload, + #[clap(alias = "reload")] + ReloadMods, #[cfg(unix)] /// - Restart the server @@ -61,30 +61,3 @@ pub(super) enum ServerCommand { /// - Shutdown the server 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?, - }) -} diff --git a/src/admin/user/commands.rs b/src/admin/user/commands.rs index 69019d79e..bdd35d59e 100644 --- a/src/admin/user/commands.rs +++ b/src/admin/user/commands.rs @@ -1,7 +1,7 @@ use std::{collections::BTreeMap, fmt::Write as _}; 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::{ events::{ room::message::RoomMessageEventContent, @@ -10,17 +10,17 @@ }, OwnedRoomId, OwnedRoomOrAliasId, OwnedUserId, RoomId, }; -use tracing::{error, info, warn}; 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}, }; const AUTO_GEN_PASSWORD_LENGTH: usize = 25; -pub(super) async fn list(_body: Vec<&str>) -> Result<RoomMessageEventContent> { - match services().users.list_local_users() { +#[admin_command] +pub(super) async fn list_users(&self) -> Result<RoomMessageEventContent> { + match self.services.users.list_local_users() { Ok(users) => { let mut plain_msg = format!("Found {} local user account(s):\n```\n", users.len()); 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( - _body: Vec<&str>, username: String, password: Option<String>, -) -> Result<RoomMessageEventContent> { +#[admin_command] +pub(super) async fn create_user(&self, username: String, password: Option<String>) -> Result<RoomMessageEventContent> { // 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"))); } @@ -51,30 +50,33 @@ pub(super) async fn create( let password = password.unwrap_or_else(|| utils::random_string(AUTO_GEN_PASSWORD_LENGTH)); // 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 let mut displayname = user_id.localpart().to_owned(); // 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 - if !services() + if !self + .services .globals .config .new_user_displayname_suffix .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"); } - services() + self.services .users .set_displayname(&user_id, Some(displayname)) .await?; // Initial account data - services().account_data.update( + self.services.account_data.update( None, &user_id, ruma::events::GlobalAccountDataEventType::PushRules @@ -88,12 +90,13 @@ pub(super) async fn create( .expect("to json value always works"), )?; - if !services().globals.config.auto_join_rooms.is_empty() { - for room in &services().globals.config.auto_join_rooms { - if !services() + if !self.services.globals.config.auto_join_rooms.is_empty() { + for room in &self.services.globals.config.auto_join_rooms { + if !self + .services .rooms .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."); continue; @@ -101,11 +104,11 @@ pub(super) async fn create( if let Some(room_id_server_name) = room.server_name() { match join_room_by_id_helper( - services(), + self.services, &user_id, room, 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, ) .await @@ -130,38 +133,38 @@ pub(super) async fn create( ))) } -pub(super) async fn deactivate( - _body: Vec<&str>, no_leave_rooms: bool, user_id: String, -) -> Result<RoomMessageEventContent> { +#[admin_command] +pub(super) async fn deactivate(&self, no_leave_rooms: bool, user_id: String) -> Result<RoomMessageEventContent> { // 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 - if user_id == services().globals.server_user { + if user_id == self.services.globals.server_user { return Ok(RoomMessageEventContent::text_plain( "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 { - services() + self.services .admin .send_message(RoomMessageEventContent::text_plain(format!( "Making {user_id} leave all rooms after deactivation..." ))) .await; - let all_joined_rooms: Vec<OwnedRoomId> = services() + let all_joined_rooms: Vec<OwnedRoomId> = self + .services .rooms .state_cache .rooms_joined(&user_id) .filter_map(Result::ok) .collect(); - update_displayname(services(), user_id.clone(), None, all_joined_rooms.clone()).await?; - update_avatar_url(services(), user_id.clone(), None, None, all_joined_rooms).await?; - leave_all_rooms(services(), &user_id).await; + update_displayname(self.services, user_id.clone(), None, all_joined_rooms.clone()).await?; + update_avatar_url(self.services, user_id.clone(), None, None, all_joined_rooms).await?; + leave_all_rooms(self.services, &user_id).await; } 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> { - let user_id = parse_local_user_id(services(), &username)?; +#[admin_command] +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( "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); - match services() + match self + .services .users .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( - body: Vec<&str>, no_leave_rooms: bool, force: bool, -) -> Result<RoomMessageEventContent> { - if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" { +#[admin_command] +pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: 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( "Expected code block in command body. Add --help for details.", )); } - let usernames = body - .clone() - .drain(1..body.len().saturating_sub(1)) + let usernames = self + .body + .to_vec() + .drain(1..self.body.len().saturating_sub(1)) .collect::<Vec<_>>(); let mut user_ids: Vec<OwnedUserId> = Vec::with_capacity(usernames.len()); let mut admins = Vec::new(); for username in usernames { - match parse_active_local_user_id(services(), username) { + match parse_active_local_user_id(self.services, username) { Ok(user_id) => { - if services().users.is_admin(&user_id)? && !force { - services() + if self.services.users.is_admin(&user_id)? && !force { + self.services .admin .send_message(RoomMessageEventContent::text_plain(format!( "{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 - if user_id == services().globals.server_user { - services() + if user_id == self.services.globals.server_user { + self.services .admin .send_message(RoomMessageEventContent::text_plain(format!( "{username} is the server service account, skipping over" @@ -238,7 +244,7 @@ pub(super) async fn deactivate_all( user_ids.push(user_id); }, Err(e) => { - services() + self.services .admin .send_message(RoomMessageEventContent::text_plain(format!( "{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; for user_id in user_ids { - match services().users.deactivate_account(&user_id) { + match self.services.users.deactivate_account(&user_id) { Ok(()) => { deactivation_count = deactivation_count.saturating_add(1); if !no_leave_rooms { 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 .state_cache .rooms_joined(&user_id) .filter_map(Result::ok) .collect(); - update_displayname(services(), user_id.clone(), None, all_joined_rooms.clone()).await?; - update_avatar_url(services(), user_id.clone(), None, None, all_joined_rooms).await?; - leave_all_rooms(services(), &user_id).await; + update_displayname(self.services, user_id.clone(), None, all_joined_rooms.clone()).await?; + update_avatar_url(self.services, user_id.clone(), None, None, all_joined_rooms).await?; + leave_all_rooms(self.services, &user_id).await; } }, Err(e) => { - services() + self.services .admin .send_message(RoomMessageEventContent::text_plain(format!("Failed deactivating user: {e}"))) .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 - 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 .state_cache .rooms_joined(&user_id) .filter_map(Result::ok) - .map(|room_id| get_room_info(services(), &room_id)) + .map(|room_id| get_room_info(self.services, &room_id)) .collect(); 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)) } +#[admin_command] 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> { - let user_id = parse_local_user_id(services(), &user_id)?; - let room_id = services().rooms.alias.resolve(&room_id).await?; + let user_id = parse_local_user_id(self.services, &user_id)?; + let room_id = self.services.rooms.alias.resolve(&room_id).await?; assert!( - services().globals.user_is_local(&user_id), + self.services.globals.user_is_local(&user_id), "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!( "{user_id} has been joined to {room_id}.", ))) } -pub(super) async fn make_user_admin(_body: Vec<&str>, user_id: String) -> Result<RoomMessageEventContent> { - let user_id = parse_local_user_id(services(), &user_id)?; - let displayname = services() +#[admin_command] +pub(super) async fn make_user_admin(&self, user_id: String) -> Result<RoomMessageEventContent> { + let user_id = parse_local_user_id(self.services, &user_id)?; + let displayname = self + .services .users .displayname(&user_id)? .unwrap_or_else(|| user_id.to_string()); assert!( - services().globals.user_is_local(&user_id), + self.services.globals.user_is_local(&user_id), "Parsed user_id must be a local user" ); - services() + self.services .admin .make_user_admin(&user_id, displayname) .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( - _body: Vec<&str>, user_id: String, room_id: Box<RoomId>, tag: String, + &self, user_id: String, room_id: Box<RoomId>, tag: String, ) -> 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 .get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?; @@ -402,7 +416,7 @@ pub(super) async fn put_room_tag( .tags .insert(tag.clone().into(), TagInfo::new()); - services().account_data.update( + self.services.account_data.update( Some(&room_id), &user_id, RoomAccountDataEventType::Tag, @@ -414,12 +428,14 @@ pub(super) async fn put_room_tag( ))) } +#[admin_command] 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> { - 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 .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()); - services().account_data.update( + self.services.account_data.update( Some(&room_id), &user_id, RoomAccountDataEventType::Tag, @@ -446,12 +462,12 @@ pub(super) async fn delete_room_tag( ))) } -pub(super) async fn get_room_tags( - _body: Vec<&str>, user_id: String, room_id: Box<RoomId>, -) -> Result<RoomMessageEventContent> { - let user_id = parse_active_local_user_id(services(), &user_id)?; +#[admin_command] +pub(super) async fn get_room_tags(&self, user_id: String, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> { + let user_id = parse_active_local_user_id(self.services, &user_id)?; - let event = services() + let event = self + .services .account_data .get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?; diff --git a/src/admin/user/mod.rs b/src/admin/user/mod.rs index 1b92d668c..b0c0bd1ec 100644 --- a/src/admin/user/mod.rs +++ b/src/admin/user/mod.rs @@ -2,14 +2,16 @@ use clap::Subcommand; 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)] pub(super) enum UserCommand { /// - Create a new user - Create { + #[clap(alias = "create")] + CreateUser { /// Username of the new user username: String, /// 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, + #[clap(alias = "list")] + ListUsers, /// - Lists all the rooms (local and remote) that the specified user is /// joined in @@ -101,48 +104,3 @@ pub(super) enum UserCommand { 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?, - }) -} diff --git a/src/macros/admin.rs b/src/macros/admin.rs index 4189d64f9..d4ce7ad5f 100644 --- a/src/macros/admin.rs +++ b/src/macros/admin.rs @@ -2,15 +2,27 @@ use proc_macro::{Span, TokenStream}; use proc_macro2::TokenStream as TokenStream2; 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}; +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> { let name = &item.ident; let arm: Vec<TokenStream2> = item.variants.iter().map(dispatch_arm).try_collect()?; 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::*; #[allow(non_snake_case)] 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 arg = field.clone(); quote! { - #name { #( #field ),* } => Box::pin(#handler(&body, #( #arg ),*)).await?, + #name { #( #field ),* } => Box::pin(context.#handler(#( #arg ),*)).await?, } }, 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")); }; quote! { - #name ( #field ) => Box::pin(#handler::process(#field, body)).await?, + #name ( #field ) => Box::pin(#handler::process(#field, context)).await?, } }, Fields::Unit => { quote! { - #name => Box::pin(#handler(&body)).await?, + #name => Box::pin(context.#handler()).await?, } }, }; diff --git a/src/macros/mod.rs b/src/macros/mod.rs index 1a5494bb0..d32cda71c 100644 --- a/src/macros/mod.rs +++ b/src/macros/mod.rs @@ -14,6 +14,11 @@ 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] pub fn admin_command_dispatch(args: TokenStream, input: TokenStream) -> TokenStream { attribute_macro::<ItemEnum, _>(args, input, admin::command_dispatch) -- GitLab