diff --git a/assets/robocraft/config.json b/assets/robocraft/config.json index 25f4f49..ee1da56 100644 --- a/assets/robocraft/config.json +++ b/assets/robocraft/config.json @@ -14508,6 +14508,24 @@ }, "permission": "Developer" }, + { + "regex": "\\?grant", + "op": { + "type": "BuiltIn", + "built_in": "System", + "system": "Permissions" + }, + "permission": "Developer" + }, + { + "regex": "\\?perms", + "op": { + "type": "BuiltIn", + "built_in": "System", + "system": "CheckPermissions" + }, + "permission": "Player" + }, { "regex": "\\?help", "op": { diff --git a/rc_chat_room/src/state/chat/config.rs b/rc_chat_room/src/state/chat/config.rs index 301778a..c5bdb4f 100644 --- a/rc_chat_room/src/state/chat/config.rs +++ b/rc_chat_room/src/state/chat/config.rs @@ -115,6 +115,7 @@ impl ChatOperation { enum BuiltIn { Intercom(Intercom), + System(System), OnlineUsers, TotalUsers, Stats, @@ -126,6 +127,7 @@ impl BuiltIn { fn from_persist(b_in: oj_rc_core::persist::BuiltInChatOperation) -> Self { match b_in { oj_rc_core::persist::BuiltInChatOperation::Intercom(com) => Self::Intercom(Intercom::from_persist(com)), + oj_rc_core::persist::BuiltInChatOperation::System(sys) => Self::System(System::from_persist(sys)), oj_rc_core::persist::BuiltInChatOperation::OnlineUsers => Self::OnlineUsers, oj_rc_core::persist::BuiltInChatOperation::TotalUsers => Self::TotalUsers, oj_rc_core::persist::BuiltInChatOperation::Stats => Self::Stats, @@ -141,6 +143,7 @@ impl BuiltIn { async fn do_command<'b, 'c>(&self, text: &str, ctx: CommandContext<'b, 'c>) -> String { match self { Self::Intercom(intercom) => intercom.do_command(text, ctx).await, + Self::System(sys) => sys.do_command(text, ctx).await, Self::OnlineUsers => { let online_count = ctx.chat_system.user_count(); if online_count == 1 { @@ -236,6 +239,7 @@ impl BuiltIn { fn do_help(&self) -> String { match self { Self::Intercom(i) => i.do_help(), + Self::System(s) => s.do_help(), Self::OnlineUsers => "Show total users online".to_owned(), Self::TotalUsers => "Show total users registered".to_owned(), Self::Stats => "Show server metrics (db|perms)".to_owned(), @@ -312,6 +316,114 @@ impl Intercom { } } +enum System { + Permissions, + CheckPermissions, +} + +enum SystemPermission { + Mod, + NotMod, + Admin, + NotAdmin, + Dev, + NotDev, +} + +impl SystemPermission { + fn from_str(s: &str) -> Option { + match &s.to_lowercase() as &str { + "mod" | "moderator" => Some(Self::Mod), + "!mod" | "!moderator" | "unmod" | "notmod" => Some(Self::NotMod), + "admin" | "administrator" => Some(Self::Admin), + "!admin" | "!administrator" | "nadmin" | "notadmin" => Some(Self::NotAdmin), + "dev" | "developer" => Some(Self::Dev), + "!dev" | "!developer" | "notdev" => Some(Self::NotDev), + _ => None, + } + } + + fn display(&self) -> &'static str { + match self { + Self::Mod => "Moderator", + Self::NotMod => "!Moderator", + Self::Admin => "Administrator", + Self::NotAdmin => "!Administrator", + Self::Dev => "Developer", + Self::NotDev => "!Developer", + } + } + + fn role(&self) -> oj_rc_core::persist::user::UserRole { + match self { + Self::Mod => oj_rc_core::persist::user::UserRole::Moderator, + Self::NotMod => oj_rc_core::persist::user::UserRole::Moderator, + Self::Admin => oj_rc_core::persist::user::UserRole::Administrator, + Self::NotAdmin => oj_rc_core::persist::user::UserRole::Administrator, + Self::Dev => oj_rc_core::persist::user::UserRole::Developer, + Self::NotDev => oj_rc_core::persist::user::UserRole::Developer, + } + } + + fn value(&self) -> bool { + match self { + Self::Mod | Self::Admin | Self::Dev => true, + Self::NotMod | Self::NotAdmin | Self::NotDev => false, + } + } +} + +impl System { + fn from_persist(sys: oj_rc_core::persist::SystemChatOperation) -> Self { + match sys { + oj_rc_core::persist::SystemChatOperation::Permissions => Self::Permissions, + oj_rc_core::persist::SystemChatOperation::CheckPermissions => Self::CheckPermissions, + } + } + + async fn do_command<'b, 'c>(&self, text: &str, ctx: CommandContext<'b, 'c>) -> String { + match self { + Self::Permissions => { + let params: Vec<_> = text.trim().split(' ').collect(); + if params.len() < 3 { + return "Not enough arguments\nusage: [command] [permission] [username]".to_owned(); + } else if params.len() > 3 { + return "Too many arguments parameters\nusage: [command] [permission] [username]".to_owned(); + } + let perm = SystemPermission::from_str(params[1]); + if perm.is_none() { + return format!("Unrecognised permission \"{}\" (try dev, admin, or mod)", ¶ms[1]); + } + let perm = perm.unwrap(); + if let Err(e) = ctx.user.set_permission(params[2].to_owned(), perm.role(), perm.value()).await { + if let Some(msg) = e.error_msg() { + format!("Failed to grant permission: {}", msg) + } else { + format!("Failed to grant permission (code {})", e.error_code()) + } + } else { + format!("Granted {} to {} (they should re-log)", perm.display(), ¶ms[2]) + } + }, + Self::CheckPermissions => { + let is_royal = ctx.user.is_royal(); + let is_dev = ctx.user.is_dev(); + let is_admin = ctx.user.is_dev(); + let is_mod = ctx.user.is_mod(); + format!("r:{} dev:{} adm:{} mod:{}", is_royal as u8, is_dev as u8, is_admin as u8, is_mod as u8) + } + } + + } + + fn do_help(&self) -> String { + match self { + Self::Permissions => "Grant permissions to an account".to_owned(), + Self::CheckPermissions => "Display permissions for current account".to_owned(), + } + } +} + enum ExecutePermission { Player, Moderator, diff --git a/rc_core/src/persist/chat.rs b/rc_core/src/persist/chat.rs index 7ffa3fd..ded5625 100644 --- a/rc_core/src/persist/chat.rs +++ b/rc_core/src/persist/chat.rs @@ -76,6 +76,7 @@ impl super::config::SelfValidator for ChatCommand { self.op, ChatOperation::BuiltIn(BuiltInChatOperation::Intercom(IntercomChatOperation::DevBroadcast)) | ChatOperation::BuiltIn(BuiltInChatOperation::Intercom(IntercomChatOperation::Maintenance)) + | ChatOperation::BuiltIn(BuiltInChatOperation::System(SystemChatOperation::Permissions)) ) { if !matches!(self.permission, ChatPermission::Administrator | ChatPermission::Developer | ChatPermission::Royal) { info.warn(crate::persist::config::ValidationMessage { @@ -115,6 +116,7 @@ pub enum ChatOperation { #[serde(tag = "built_in")] pub enum BuiltInChatOperation { Intercom(IntercomChatOperation), + System(SystemChatOperation), OnlineUsers, TotalUsers, Stats, @@ -130,6 +132,13 @@ pub enum IntercomChatOperation { Maintenance, } +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(tag = "system")] +pub enum SystemChatOperation { + Permissions, + CheckPermissions, +} + fn default_pub_channs() -> Vec { vec![ diff --git a/rc_core/src/persist/mod.rs b/rc_core/src/persist/mod.rs index 4373162..a031648 100644 --- a/rc_core/src/persist/mod.rs +++ b/rc_core/src/persist/mod.rs @@ -30,7 +30,7 @@ mod settings; pub use settings::{Settings, QueueMode}; mod chat; -pub use chat::{ChatConfig, ChatCommand, ChatOperation, BuiltInChatOperation, IntercomChatOperation, ChatPermission}; +pub use chat::{ChatConfig, ChatCommand, ChatOperation, BuiltInChatOperation, IntercomChatOperation, ChatPermission, SystemChatOperation}; mod vehicle_factory; pub use vehicle_factory::{FactoryConfig, AdapterSettings, ArcFactorySettings}; diff --git a/rc_core/src/persist/user/account_json.rs b/rc_core/src/persist/user/account_json.rs index fe51af3..e6a3c6b 100644 --- a/rc_core/src/persist/user/account_json.rs +++ b/rc_core/src/persist/user/account_json.rs @@ -355,7 +355,7 @@ impl UserData { } } - fn check_perms_to_exec(&self, ty: &super::SanctionType) -> Result<(), i16> { + pub(super) fn check_perms_to_exec(&self, ty: &super::SanctionType) -> Result<(), i16> { match ty { super::SanctionType::Warn | super::SanctionType::Mute @@ -1186,177 +1186,3 @@ impl super::GameEventSetter for GameEventSetterImpl { self.select_event_now(oj_rc_database::schema::game_event::EventVariant::Singleplayer).await } } - -#[async_trait::async_trait] -impl super::ChatUser for UserData { - async fn subscribed_channels(&self) -> Result, i16> { - let channels = self.subscribed_channels_strings().await?; - log::info!("User is subscribed to channels {:?}", channels); - Ok(polariton::operation::Typed::Arr(polariton::operation::Arr { - ty: polariton::serdes::TypePrefix::HashMap, // hashtable - items: channels.into_iter().map(|name| crate::data::channel::ChatChannelInfo { - channel_name: name, - members: vec![ - crate::data::channel::ChatChannelMember { - name: self.account.display_name.clone(), - use_custom_avatar: false, - state: crate::data::channel::ChatPlayerState::Idk0, - custom_avatar: Vec::default(), - avatar_id: 0, - }, - ], - channel_ty: crate::data::channel::ChatChannelType::Public, - }.as_transmissible()).collect() - })) - } - - async fn subscribed_channels_strings(&self) -> Result, i16> { - let channels = self.db.user_aux_by_user_id_and_descriptor(self.account.id, oj_rc_database::schema::user_aux::Descriptor::SubscribedChannels).await.map_err(|e| { - log::error!("Failed to retrieve SubscribedChannels (user_aux) for user_id {}: {}", self.account.id, e); - crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 - })?.ok_or_else(|| { - log::error!("Failed to find SubscribedChannels (user_aux) for user_id {}", self.account.id); - crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 - })?; - let channels = serde_json::from_str::>(&channels.data).map_err(|e| { - log::error!("Failed to parse SubscribedChannels (user_aux) for user_id {}: {}", self.account.id, e); - crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 - })?; - Ok(channels) - } - - async fn add_subscribed_channel(&self, channel: String, channel_ty: crate::data::channel::ChatChannelType) -> Result, i16> { - if let crate::data::channel::ChatChannelType::Public = channel_ty { - let mut sub_channels = self.subscribed_channels_strings().await?; - sub_channels.push(channel.clone()); - let new_data = serde_json::to_string(&sub_channels).map_err(|e| { - log::error!("Failed to convert to JSON SubscribedChannels (user_aux) for user_id {}: {}", self.account.id, e); - crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 - })?; - self.db.update_user_aux_by_user_id_and_descriptor(oj_rc_database::schema::user_aux::ActiveModel { - data: oj_rc_database::sea_orm::ActiveValue::Set(new_data), - ..Default::default() - }, self.account.id, oj_rc_database::schema::user_aux::Descriptor::SubscribedChannels).await.map_err(|e| { - log::error!("Failed to update SubscribedChannels (user_aux) for user_id {}: {}", self.account.id, e); - crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 - })?; - } - - Ok(crate::data::channel::ChatChannelInfo { - channel_name: channel, - members: Vec::default(), - channel_ty, - }.as_transmissible()) - } - - async fn remove_subscribed_channel(&self, channel: String, channel_ty: crate::data::channel::ChatChannelType) -> Result<(), i16> { - if let crate::data::channel::ChatChannelType::Public = channel_ty { - let mut sub_channels = self.subscribed_channels_strings().await?; - if let Some(index) = sub_channels.iter().position(|chann| chann == &channel) { - sub_channels.swap_remove(index); - let new_data = serde_json::to_string(&sub_channels).map_err(|e| { - log::error!("Failed to convert to JSON SubscribedChannels (user_aux) for user_id {}: {}", self.account.id, e); - crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 - })?; - self.db.update_user_aux_by_user_id_and_descriptor(oj_rc_database::schema::user_aux::ActiveModel { - data: oj_rc_database::sea_orm::ActiveValue::Set(new_data), - ..Default::default() - }, self.account.id, oj_rc_database::schema::user_aux::Descriptor::SubscribedChannels).await.map_err(|e| { - log::error!("Failed to update SubscribedChannels (user_aux) for user_id {}: {}", self.account.id, e); - crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 - })?; - } - } - Ok(()) - } - - /*async fn has_pending_sanctions(&self) -> Result { - let count = self.db.count_sanctions_to_ack_by_user_id_and_descriptor(self.account.id, oj_rc_database::schema::sanction::Descriptor::Warn).await.map_err(|e| { - log::error!("Failed to count pending sanctions for user_id {}: {}", self.account.id, e); - crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 - })?; - Ok(count != 0) - }*/ - - async fn get_sanctions(&self, username: String) -> Result, i16> { - let user_opt = self.db.user_by_display_name(username.clone()).await.map_err(|e| { - log::error!("Failed to retrieve user by username {} for user_id {}: {}", username, self.account.id, e); - crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 - })?; - if let Some(user) = user_opt { - let sanctions = self.db.sanctions_by_user_id(user.id).await.map_err(|e| { - log::error!("Failed to retrieve sanctions by username {} for user_id {}: {}", username, self.account.id, e); - crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 - })?; - Ok(polariton::operation::Typed::Arr(polariton::operation::Arr { - ty: polariton::serdes::TypePrefix::Str, - items: sanctions.into_iter().map(|x| { - let data = crate::data::sanction::SanctionJson { - type_: crate::data::sanction::SanctionType::from_db(x.descriptor), - reason: x.reason, - reporter: x.issuer_name, - issued: chrono::DateTime::from_timestamp(x.creation_time, 0).unwrap(), - }; - polariton::operation::Typed::Str(data.as_json().into()) - }).collect(), - })) - } else { - Err(crate::data::error_codes::ChatErrorCodes::DoesNotExist as i16) - } - } - - async fn set_sanction(&self, sanction: super::SetSanction) -> Result<(), i16> { - self.check_perms_to_exec(&sanction.type_)?; - let user_opt = self.db.user_by_display_name(sanction.username.clone()).await.map_err(|e| { - log::error!("Failed to retrieve user by username {} for user_id {}: {}", sanction.username, self.account.id, e); - crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 - })?; - if let Some(user) = user_opt { - if sanction.is_adding { - let now = chrono::Utc::now().timestamp(); - let sanction_ty = crate::data::sanction::SanctionType::from_persist(sanction.type_).to_db(); - let to_add = oj_rc_database::schema::sanction::ActiveModel { - user_id: oj_rc_database::sea_orm::ActiveValue::Set(user.id), - creation_time: oj_rc_database::sea_orm::ActiveValue::Set(now), - issuer_id: oj_rc_database::sea_orm::ActiveValue::Set(self.account.id), - issuer_name: oj_rc_database::sea_orm::ActiveValue::Set(self.account.display_name.clone()), - descriptor: oj_rc_database::sea_orm::ActiveValue::Set(sanction_ty.clone()), - reason: oj_rc_database::sea_orm::ActiveValue::Set(sanction.reason), - duration: oj_rc_database::sea_orm::ActiveValue::Set(if sanction.duration <= 0 { None } else { Some(sanction.duration as i64) }), - ..Default::default() - }; - if matches!(sanction_ty, oj_rc_database::schema::sanction::Descriptor::Ban) { - self.db.update_perms_by_user_id(oj_rc_database::schema::permissions::ActiveModel { - banned: oj_rc_database::sea_orm::ActiveValue::Set(true), - ..Default::default() - }, user.id).await.map_err(|e| { - log::error!("Failed to update permissions (to ban) for user_id {} by user_id {}: {}", user.id, self.account.id, e); - crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 - })?; - } - self.db.insert_sanction(to_add).await.map_err(|e| { - log::error!("Failed to insert sanction for user_id {} by user_id {}: {}", user.id, self.account.id, e); - crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 - })?; - Ok(()) - } else { - // FIXME - log::error!("Modifying sanctions is not currently supported"); - Err(crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16) - } - } else { - Err(crate::data::error_codes::ChatErrorCodes::DoesNotExist as i16) - } - } - - async fn get_total_registered_users(&self) -> Result { - self.db.user_count().await - .map_err(|e| { - log::error!("Failed to retrieve total user count for {}: {}", self.account.id, e); - polariton_server::operations::SimpleOpError::with_message( - crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16, - format!("Failed to retrieve total user count: {}", e), - ) - }) - } -} diff --git a/rc_core/src/persist/user/chat.rs b/rc_core/src/persist/user/chat.rs new file mode 100644 index 0000000..9ba069b --- /dev/null +++ b/rc_core/src/persist/user/chat.rs @@ -0,0 +1,217 @@ +use super::account_json::UserData; + +#[async_trait::async_trait] +impl super::ChatUser for UserData { + async fn subscribed_channels(&self) -> Result, i16> { + let channels = self.subscribed_channels_strings().await?; + log::info!("User is subscribed to channels {:?}", channels); + Ok(polariton::operation::Typed::Arr(polariton::operation::Arr { + ty: polariton::serdes::TypePrefix::HashMap, // hashtable + items: channels.into_iter().map(|name| crate::data::channel::ChatChannelInfo { + channel_name: name, + members: vec![ + crate::data::channel::ChatChannelMember { + name: self.account.display_name.clone(), + use_custom_avatar: false, + state: crate::data::channel::ChatPlayerState::Idk0, + custom_avatar: Vec::default(), + avatar_id: 0, + }, + ], + channel_ty: crate::data::channel::ChatChannelType::Public, + }.as_transmissible()).collect() + })) + } + + async fn subscribed_channels_strings(&self) -> Result, i16> { + let channels = self.db.user_aux_by_user_id_and_descriptor(self.account.id, oj_rc_database::schema::user_aux::Descriptor::SubscribedChannels).await.map_err(|e| { + log::error!("Failed to retrieve SubscribedChannels (user_aux) for user_id {}: {}", self.account.id, e); + crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 + })?.ok_or_else(|| { + log::error!("Failed to find SubscribedChannels (user_aux) for user_id {}", self.account.id); + crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 + })?; + let channels = serde_json::from_str::>(&channels.data).map_err(|e| { + log::error!("Failed to parse SubscribedChannels (user_aux) for user_id {}: {}", self.account.id, e); + crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 + })?; + Ok(channels) + } + + async fn add_subscribed_channel(&self, channel: String, channel_ty: crate::data::channel::ChatChannelType) -> Result, i16> { + if let crate::data::channel::ChatChannelType::Public = channel_ty { + let mut sub_channels = self.subscribed_channels_strings().await?; + sub_channels.push(channel.clone()); + let new_data = serde_json::to_string(&sub_channels).map_err(|e| { + log::error!("Failed to convert to JSON SubscribedChannels (user_aux) for user_id {}: {}", self.account.id, e); + crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 + })?; + self.db.update_user_aux_by_user_id_and_descriptor(oj_rc_database::schema::user_aux::ActiveModel { + data: oj_rc_database::sea_orm::ActiveValue::Set(new_data), + ..Default::default() + }, self.account.id, oj_rc_database::schema::user_aux::Descriptor::SubscribedChannels).await.map_err(|e| { + log::error!("Failed to update SubscribedChannels (user_aux) for user_id {}: {}", self.account.id, e); + crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 + })?; + } + + Ok(crate::data::channel::ChatChannelInfo { + channel_name: channel, + members: Vec::default(), + channel_ty, + }.as_transmissible()) + } + + async fn remove_subscribed_channel(&self, channel: String, channel_ty: crate::data::channel::ChatChannelType) -> Result<(), i16> { + if let crate::data::channel::ChatChannelType::Public = channel_ty { + let mut sub_channels = self.subscribed_channels_strings().await?; + if let Some(index) = sub_channels.iter().position(|chann| chann == &channel) { + sub_channels.swap_remove(index); + let new_data = serde_json::to_string(&sub_channels).map_err(|e| { + log::error!("Failed to convert to JSON SubscribedChannels (user_aux) for user_id {}: {}", self.account.id, e); + crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 + })?; + self.db.update_user_aux_by_user_id_and_descriptor(oj_rc_database::schema::user_aux::ActiveModel { + data: oj_rc_database::sea_orm::ActiveValue::Set(new_data), + ..Default::default() + }, self.account.id, oj_rc_database::schema::user_aux::Descriptor::SubscribedChannels).await.map_err(|e| { + log::error!("Failed to update SubscribedChannels (user_aux) for user_id {}: {}", self.account.id, e); + crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 + })?; + } + } + Ok(()) + } + + /*async fn has_pending_sanctions(&self) -> Result { + let count = self.db.count_sanctions_to_ack_by_user_id_and_descriptor(self.account.id, oj_rc_database::schema::sanction::Descriptor::Warn).await.map_err(|e| { + log::error!("Failed to count pending sanctions for user_id {}: {}", self.account.id, e); + crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 + })?; + Ok(count != 0) + }*/ + + async fn get_sanctions(&self, username: String) -> Result, i16> { + let user_opt = self.db.user_by_display_name(username.clone()).await.map_err(|e| { + log::error!("Failed to retrieve user by username {} for user_id {}: {}", username, self.account.id, e); + crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 + })?; + if let Some(user) = user_opt { + let sanctions = self.db.sanctions_by_user_id(user.id).await.map_err(|e| { + log::error!("Failed to retrieve sanctions by username {} for user_id {}: {}", username, self.account.id, e); + crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 + })?; + Ok(polariton::operation::Typed::Arr(polariton::operation::Arr { + ty: polariton::serdes::TypePrefix::Str, + items: sanctions.into_iter().map(|x| { + let data = crate::data::sanction::SanctionJson { + type_: crate::data::sanction::SanctionType::from_db(x.descriptor), + reason: x.reason, + reporter: x.issuer_name, + issued: chrono::DateTime::from_timestamp(x.creation_time, 0).unwrap(), + }; + polariton::operation::Typed::Str(data.as_json().into()) + }).collect(), + })) + } else { + Err(crate::data::error_codes::ChatErrorCodes::DoesNotExist as i16) + } + } + + async fn set_sanction(&self, sanction: super::SetSanction) -> Result<(), i16> { + self.check_perms_to_exec(&sanction.type_)?; + let user_opt = self.db.user_by_display_name(sanction.username.clone()).await.map_err(|e| { + log::error!("Failed to retrieve user by username {} for user_id {}: {}", sanction.username, self.account.id, e); + crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 + })?; + if let Some(user) = user_opt { + if sanction.is_adding { + let now = chrono::Utc::now().timestamp(); + let sanction_ty = crate::data::sanction::SanctionType::from_persist(sanction.type_).to_db(); + let to_add = oj_rc_database::schema::sanction::ActiveModel { + user_id: oj_rc_database::sea_orm::ActiveValue::Set(user.id), + creation_time: oj_rc_database::sea_orm::ActiveValue::Set(now), + issuer_id: oj_rc_database::sea_orm::ActiveValue::Set(self.account.id), + issuer_name: oj_rc_database::sea_orm::ActiveValue::Set(self.account.display_name.clone()), + descriptor: oj_rc_database::sea_orm::ActiveValue::Set(sanction_ty.clone()), + reason: oj_rc_database::sea_orm::ActiveValue::Set(sanction.reason), + duration: oj_rc_database::sea_orm::ActiveValue::Set(if sanction.duration <= 0 { None } else { Some(sanction.duration as i64) }), + ..Default::default() + }; + if matches!(sanction_ty, oj_rc_database::schema::sanction::Descriptor::Ban) { + self.db.update_perms_by_user_id(oj_rc_database::schema::permissions::ActiveModel { + banned: oj_rc_database::sea_orm::ActiveValue::Set(true), + ..Default::default() + }, user.id).await.map_err(|e| { + log::error!("Failed to update permissions (to ban) for user_id {} by user_id {}: {}", user.id, self.account.id, e); + crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 + })?; + } + self.db.insert_sanction(to_add).await.map_err(|e| { + log::error!("Failed to insert sanction for user_id {} by user_id {}: {}", user.id, self.account.id, e); + crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16 + })?; + Ok(()) + } else { + // FIXME + log::error!("Modifying sanctions is not currently supported"); + Err(crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16) + } + } else { + Err(crate::data::error_codes::ChatErrorCodes::DoesNotExist as i16) + } + } + + async fn get_total_registered_users(&self) -> Result { + self.db.user_count().await + .map_err(|e| { + log::error!("Failed to retrieve total user count for {}: {}", self.account.id, e); + polariton_server::operations::SimpleOpError::with_message( + crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16, + format!("Failed to retrieve total user count: {}", e), + ) + }) + } + + async fn set_permission(&self, username: String, permission: super::UserRole, value: bool) -> Result<(), polariton_server::operations::SimpleOpError> { + if !(self.perms.developer || self.perms.royalty || self.perms.administrator) { + // technically this should already be handled by the chat command permission check + // this is just extra insurance in case of a bad server configuration + return Err(polariton_server::operations::SimpleOpError::with_message( + crate::data::error_codes::ChatErrorCodes::AdminsOnly as i16, + format!("User {} cannot grant permissions for {}", self.account.id, username), + )) + } + let account_opt = self.db.user_by_display_name(username.clone()).await.map_err(|e| { + log::error!("Failed to retrieve user {} to grant permission by user {}: {}", username, self.account.id, e); + polariton_server::operations::SimpleOpError::with_message( + crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16, + format!("Failed to retrieve user for permission grant: {}", e), + ) + })?; + if let Some(account) = account_opt { + self.db.update_perms_by_user_id(oj_rc_database::schema::permissions::ActiveModel { + id: oj_rc_database::sea_orm::ActiveValue::NotSet, + user_id: oj_rc_database::sea_orm::ActiveValue::NotSet, + moderator: if matches!(permission, super::UserRole::Moderator) { oj_rc_database::sea_orm::ActiveValue::Set(value) } else { oj_rc_database::sea_orm::ActiveValue::NotSet }, + administrator: if matches!(permission, super::UserRole::Administrator) { oj_rc_database::sea_orm::ActiveValue::Set(value) } else { oj_rc_database::sea_orm::ActiveValue::NotSet }, + developer: if matches!(permission, super::UserRole::Developer) { oj_rc_database::sea_orm::ActiveValue::Set(value) } else { oj_rc_database::sea_orm::ActiveValue::NotSet }, + royalty: if matches!(permission, super::UserRole::Royalty) { oj_rc_database::sea_orm::ActiveValue::Set(value) } else { oj_rc_database::sea_orm::ActiveValue::NotSet }, + banned: oj_rc_database::sea_orm::ActiveValue::NotSet, + }, account.id).await + .map_err(|e| { + log::error!("Failed to update permissions for user {} by user {}: {}", account.id, self.account.id, e); + polariton_server::operations::SimpleOpError::with_message( + crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16, + format!("Failed to update user permissions: {}", e), + ) + })?; + Ok(()) + } else { + Err(polariton_server::operations::SimpleOpError::with_message( + crate::data::error_codes::ChatErrorCodes::DoesNotExist as i16, + format!("User {} not found", username), + )) + } + } +} diff --git a/rc_core/src/persist/user/mod.rs b/rc_core/src/persist/user/mod.rs index c6555c4..9f907ec 100644 --- a/rc_core/src/persist/user/mod.rs +++ b/rc_core/src/persist/user/mod.rs @@ -11,7 +11,7 @@ mod inventory; pub use inventory::UnlockedParts; mod traits; -pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserInfo, UserLoginInfo, ExtraUserInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData, ChatUser, AvatarInfo, GetAvatarInfo, ControlData, ControlType, CustomisationData, GetCustomisationData, SetSanction, SanctionType, LobbyUser, GameDescriptor, PlayerLobbyDescriptor, MultiplayerUser, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor, GameEventSetter, CurrentGameEvent, AuthError, IntercomUser, FakePlayers, ResolvedVehicle, CommonUser, IntercomListener}; +pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserInfo, UserLoginInfo, ExtraUserInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData, ChatUser, AvatarInfo, GetAvatarInfo, ControlData, ControlType, CustomisationData, GetCustomisationData, SetSanction, SanctionType, LobbyUser, GameDescriptor, PlayerLobbyDescriptor, MultiplayerUser, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor, GameEventSetter, CurrentGameEvent, AuthError, IntercomUser, FakePlayers, ResolvedVehicle, CommonUser, IntercomListener, UserRole}; pub mod intercom; pub use intercom::generate_token as generate_intercom_token; @@ -20,6 +20,7 @@ mod multiplayer; mod lobby; pub use lobby::TeamChooser; mod common; +mod chat; pub const TOKEN_SECRET_FILENAME: &str = "token_secret.key"; diff --git a/rc_core/src/persist/user/traits.rs b/rc_core/src/persist/user/traits.rs index b0b02c3..4142c4e 100644 --- a/rc_core/src/persist/user/traits.rs +++ b/rc_core/src/persist/user/traits.rs @@ -207,6 +207,7 @@ pub trait ChatUser: CommonUser + IntercomUser { async fn get_sanctions(&self, username: String) -> Result, i16>; async fn set_sanction(&self, sanction: SetSanction) -> Result<(), i16>; async fn get_total_registered_users(&self) -> Result; + async fn set_permission(&self, username: String, permission: UserRole, value: bool) -> Result<(), polariton_server::operations::SimpleOpError>; } pub struct SetSanction { @@ -238,6 +239,13 @@ impl SanctionType { } } +pub enum UserRole { + Moderator, + Administrator, + Developer, + Royalty, +} + #[async_trait::async_trait] pub trait LobbyUser { fn user_id(&self) -> i32;