diff --git a/rc_chat_room/src/operations/add_modify_sanction.rs b/rc_chat_room/src/operations/add_modify_sanction.rs new file mode 100644 index 0000000..50b74ad --- /dev/null +++ b/rc_chat_room/src/operations/add_modify_sanction.rs @@ -0,0 +1,47 @@ +use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl}; +use polariton::operation::{ParameterTable, Typed}; + +const CODE: u8 = 7; + +const SANCTION_TY_PARAM_KEY: u8 = 9; // int; in +const IS_ADDING_PARAM_KEY: u8 = 10; // bool; in +const DURATION_PARAM_KEY: u8 = 11; // int; in +const REASON_PARAM_KEY: u8 = 2; // str; in +const USERNAME_PARAM_KEY: u8 = 7; // str; in + +pub(super) struct AddSanctionProvider; + +#[async_trait::async_trait] +impl SimpleOperation for AddSanctionProvider { + type User = crate::UserTy; + const CODE: u8 = CODE; + + async fn handle(&self, params: ParameterTable, user: &Self::User) -> Result, SimpleOpError> { + let mut params = params.to_dict(); + if let Some(Typed::Int(sanction_ty)) = params.remove(&SANCTION_TY_PARAM_KEY) { + if let Some(Typed::Bool(is_adding)) = params.remove(&IS_ADDING_PARAM_KEY) { + if let Some(Typed::Int(duration)) = params.remove(&DURATION_PARAM_KEY) { + if let Some(Typed::Str(reason)) = params.remove(&REASON_PARAM_KEY) { + if let Some(Typed::Str(username)) = params.remove(&USERNAME_PARAM_KEY) { + let user_info = user.user()?; + let sanction = rc_core::persist::user::SetSanction { + type_: rc_core::persist::user::SanctionType::from_i32(sanction_ty)?, + is_adding, + duration, + reason: reason.string, + username: username.string, + }; + user_info.set_sanction(sanction).await?; + return Ok(params.into()); + } + } + } + } + } + Err((rc_core::data::error_codes::ChatErrorCodes::UnexpectedError as i16).into()) + } +} + +pub(super) fn add_modify_sanction_provider() -> SimpleOpImpl { + SimpleOpImpl::new(AddSanctionProvider) +} diff --git a/rc_chat_room/src/operations/list_sanctions.rs b/rc_chat_room/src/operations/list_sanctions.rs new file mode 100644 index 0000000..e50af0a --- /dev/null +++ b/rc_chat_room/src/operations/list_sanctions.rs @@ -0,0 +1,30 @@ +use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl}; +use polariton::operation::{ParameterTable, Typed}; + +const CODE: u8 = 16; + +const SANCTIONS_PARAM_KEY: u8 = 31; // arr of str; out +const USERNAME_PARAM_KEY: u8 = 22; // str; in + +pub(super) struct GetSanctionsProvider; + +#[async_trait::async_trait] +impl SimpleOperation<()> for GetSanctionsProvider { + type User = crate::UserTy; + const CODE: u8 = CODE; + + async fn handle(&self, params: ParameterTable, user: &Self::User) -> Result { + let mut params = params.to_dict(); + if let Some(Typed::Str(username)) = params.remove(&USERNAME_PARAM_KEY) { + let user_info = user.user()?; + let sanctions = user_info.get_sanctions(username.string).await?; + params.insert(SANCTIONS_PARAM_KEY, sanctions); + return Ok(params.into()); + } + Err((rc_core::data::error_codes::ChatErrorCodes::UnexpectedError as i16).into()) + } +} + +pub(super) fn list_sanctions_provider() -> SimpleOpImpl<(), crate::UserTy, GetSanctionsProvider> { + SimpleOpImpl::new(GetSanctionsProvider) +} diff --git a/rc_chat_room/src/operations/mod.rs b/rc_chat_room/src/operations/mod.rs index e997475..93e1259 100644 --- a/rc_chat_room/src/operations/mod.rs +++ b/rc_chat_room/src/operations/mod.rs @@ -7,6 +7,8 @@ mod public_channels; mod join_channel; mod user_online; mod subscribed_channels; +mod add_modify_sanction; +mod list_sanctions; use polariton_server::operations::OperationsHandler; @@ -25,5 +27,7 @@ pub fn handler(chat_system: crate::state::chat::ChatImpl, conf: &rc_core::persis .add(send_message::send_private_message_handler(chat_system.clone())) .add(join_channel::leave_channel_provider(chat_system.clone())) .add(subscribed_channels::all_subbed_channels_provider()) + .add(add_modify_sanction::add_modify_sanction_provider()) + .add(list_sanctions::list_sanctions_provider()) //.add(polariton_server::operations::Ack::<00000, _>::default()) } diff --git a/rc_chat_room/src/operations/pending_sanctions.rs b/rc_chat_room/src/operations/pending_sanctions.rs index c8ee98f..1b48da7 100644 --- a/rc_chat_room/src/operations/pending_sanctions.rs +++ b/rc_chat_room/src/operations/pending_sanctions.rs @@ -1,12 +1,26 @@ -use polariton_server::operations::SimpleFunc; +use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl}; use polariton::operation::{ParameterTable, Typed}; +const CODE: u8 = 15; + const PARAM_KEY: u8 = 28; -pub(super) fn pending_sanctions_checker() -> SimpleFunc<15, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> { - SimpleFunc::new(|params, _| { +pub(super) struct PendingSanctionsProvider; + +#[async_trait::async_trait] +impl SimpleOperation for PendingSanctionsProvider { + type User = crate::UserTy; + const CODE: u8 = CODE; + + async fn handle(&self, params: ParameterTable, _user: &Self::User) -> Result, SimpleOpError> { let mut params = params.to_dict(); - params.insert(PARAM_KEY, Typed::Bool(false.into())); + //let user_info = user.user()?; + //params.insert(PARAM_KEY, Typed::Bool(user_info.has_pending_sanctions().await?)); + params.insert(PARAM_KEY, Typed::Bool(false)); Ok(params.into()) - }) + } +} + +pub(super) fn pending_sanctions_checker() -> SimpleOpImpl { + SimpleOpImpl::new(PendingSanctionsProvider) } diff --git a/rc_core/src/data/mod.rs b/rc_core/src/data/mod.rs index 2d1092c..5f951be 100644 --- a/rc_core/src/data/mod.rs +++ b/rc_core/src/data/mod.rs @@ -12,6 +12,7 @@ pub mod weapon_list; pub mod weapon_upgrade; pub mod crf; pub mod channel; +pub mod sanction; pub mod error_codes; diff --git a/rc_core/src/data/sanction.rs b/rc_core/src/data/sanction.rs new file mode 100644 index 0000000..5dbac08 --- /dev/null +++ b/rc_core/src/data/sanction.rs @@ -0,0 +1,72 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct SanctionJson { + #[serde(rename="Type")] + pub type_: SanctionType, + #[serde(rename="Reason")] + pub reason: String, + #[serde(rename="Reporter")] + pub reporter: String, + #[serde(rename="Issued", serialize_with="serde_issued::serialize")] + pub issued: chrono::DateTime, +} + +impl SanctionJson { + pub fn as_json(&self) -> String { + serde_json::to_string(&self).unwrap() + } +} + +mod serde_issued { + use chrono::{DateTime, Utc}; + + /*pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result, D::Error> { + d.deserialize_str(todo!()) + }*/ + + pub fn serialize(dt: &DateTime, serializer: S) -> Result { + serializer.serialize_str(&dt.to_rfc2822()) + } +} + +#[derive(Serialize, Deserialize, Copy, Clone, Debug)] +pub enum SanctionType { + Warning = 0, + Mute = 1, + Suspension = 2, + Note = 3, + Kick = 4, +} + +impl SanctionType { + pub(crate) fn from_db(t: rc_database::schema::sanction::Descriptor) -> Self { + match t { + rc_database::schema::sanction::Descriptor::Warn => Self::Warning, + rc_database::schema::sanction::Descriptor::Mute => Self::Mute, + rc_database::schema::sanction::Descriptor::Ban => Self::Suspension, + rc_database::schema::sanction::Descriptor::Note => Self::Note, + rc_database::schema::sanction::Descriptor::Kick => Self::Kick, + } + } + + pub(crate) fn to_db(self) -> rc_database::schema::sanction::Descriptor { + match self { + Self::Warning => rc_database::schema::sanction::Descriptor::Warn, + Self::Mute => rc_database::schema::sanction::Descriptor::Mute, + Self::Suspension => rc_database::schema::sanction::Descriptor::Ban, + Self::Note => rc_database::schema::sanction::Descriptor::Note, + Self::Kick => rc_database::schema::sanction::Descriptor::Kick, + } + } + + pub(crate) fn from_persist(t: crate::persist::user::SanctionType) -> Self { + match t { + crate::persist::user::SanctionType::Warn => Self::Warning, + crate::persist::user::SanctionType::Mute => Self::Mute, + crate::persist::user::SanctionType::Ban => Self::Suspension, + crate::persist::user::SanctionType::Note => Self::Note, + crate::persist::user::SanctionType::Kick => Self::Kick, + } + } +} diff --git a/rc_core/src/persist/user/account_json.rs b/rc_core/src/persist/user/account_json.rs index 319c2b5..cc36d53 100644 --- a/rc_core/src/persist/user/account_json.rs +++ b/rc_core/src/persist/user/account_json.rs @@ -198,6 +198,33 @@ impl UserData { Ok(()) } } + + fn check_perms_to_exec(&self, ty: &super::SanctionType) -> Result<(), i16> { + match ty { + super::SanctionType::Warn + | super::SanctionType::Mute + | super::SanctionType::Note => if self.has_any_elevated_perms() { + Ok(()) + } else { + Err(crate::data::error_codes::ChatErrorCodes::ModeratorsOnly as i16) + }, + super::SanctionType::Ban + | super::SanctionType::Kick => if self.has_admin_or_better_perms() { + Ok(()) + } else { + Err(crate::data::error_codes::ChatErrorCodes::AdminsOnly as i16) + }, + } + } + + #[inline] + fn has_any_elevated_perms(&self) -> bool { + self.perms.moderator | self.perms.administrator | self.perms.developer + } + + fn has_admin_or_better_perms(&self) -> bool { + self.perms.administrator | self.perms.developer + } } const INVALID_ROBOT_ERR: i16 = crate::data::error_codes::WebServicesError::InvalidRobot as i16; // 140 @@ -716,4 +743,83 @@ impl super::ChatUser for UserData { } Ok(()) } + + /*async fn has_pending_sanctions(&self) -> Result { + let count = self.db.count_sanctions_to_ack_by_user_id_and_descriptor(self.account.id, 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 = rc_database::schema::sanction::ActiveModel { + user_id: rc_database::sea_orm::ActiveValue::Set(user.id), + creation_time: rc_database::sea_orm::ActiveValue::Set(now), + issuer_id: rc_database::sea_orm::ActiveValue::Set(self.account.id), + issuer_name: rc_database::sea_orm::ActiveValue::Set(self.account.display_name.clone()), + descriptor: rc_database::sea_orm::ActiveValue::Set(sanction_ty.clone()), + reason: rc_database::sea_orm::ActiveValue::Set(sanction.reason), + duration: rc_database::sea_orm::ActiveValue::Set(if sanction.duration <= 0 { None } else { Some(sanction.duration as i64) }), + ..Default::default() + }; + if matches!(sanction_ty, rc_database::schema::sanction::Descriptor::Ban) { + self.db.update_perms_by_user_id(rc_database::schema::permissions::ActiveModel { + banned: 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) + } + } } diff --git a/rc_core/src/persist/user/mod.rs b/rc_core/src/persist/user/mod.rs index f768ca9..f13a82a 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}; +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}; 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 332c0ff..5e0c9f8 100644 --- a/rc_core/src/persist/user/traits.rs +++ b/rc_core/src/persist/user/traits.rs @@ -190,5 +190,36 @@ pub trait ChatUser { async fn subscribed_channels_strings(&self) -> Result, i16>; async fn add_subscribed_channel(&self, channel: String, channel_ty: crate::data::channel::ChatChannelType) -> Result, i16>; async fn remove_subscribed_channel(&self, channel: String, channel_ty: crate::data::channel::ChatChannelType) -> Result<(), i16>; + //async fn has_pending_sanctions(&self) -> Result; + async fn get_sanctions(&self, username: String) -> Result, i16>; + async fn set_sanction(&self, sanction: SetSanction) -> Result<(), i16>; } +pub struct SetSanction { + pub is_adding: bool, // if false, it's modifying + pub type_: SanctionType, + pub duration: i32, + pub reason: String, + pub username: String, +} + +pub enum SanctionType { + Warn = 0, + Mute = 1, + Ban = 2, + Note = 3, + Kick = 4, +} + +impl SanctionType { + pub fn from_i32(i: i32) -> Result { + match i { + 0 => Ok(Self::Warn), + 1 => Ok(Self::Mute), + 2 => Ok(Self::Ban), + 3 => Ok(Self::Note), + 4 => Ok(Self::Kick), + _ => Err(crate::data::error_codes::ChatErrorCodes::UnexpectedError as i16), + } + } +} diff --git a/rc_database/src/migration/m20250529_000001_create_sanction_table.rs b/rc_database/src/migration/m20250529_000001_create_sanction_table.rs new file mode 100644 index 0000000..57a689b --- /dev/null +++ b/rc_database/src/migration/m20250529_000001_create_sanction_table.rs @@ -0,0 +1,65 @@ +use sea_orm_migration::prelude::*; + +pub struct Migration; + +impl MigrationName for Migration { + fn name(&self) -> &str { + "m20250529_000001_create_sanction_table" + } +} + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + // Define how to apply this migration: Create the Sanctions table. + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(crate::schema::sanction::Entity) + .col( + ColumnDef::new(crate::schema::sanction::Column::Id) + .unsigned() + .not_null() + .auto_increment() + .primary_key(), + ) + .col(ColumnDef::new(crate::schema::sanction::Column::UserId).unsigned().not_null()) + .foreign_key( + ForeignKey::create() + .name("fk-sanction-user_id") + .from(crate::schema::sanction::Entity, crate::schema::sanction::Column::UserId) + .to(crate::schema::user::Entity, crate::schema::user::Column::Id), + ) + .col(ColumnDef::new(crate::schema::sanction::Column::CreationTime).big_integer().not_null()) + .col(ColumnDef::new(crate::schema::sanction::Column::IssuerId).unsigned().not_null()) + .foreign_key( + ForeignKey::create() + .name("fk-sanction-issuer_id") + .from(crate::schema::sanction::Entity, crate::schema::sanction::Column::IssuerId) + .to(crate::schema::user::Entity, crate::schema::user::Column::Id), + ) + .col(ColumnDef::new(crate::schema::sanction::Column::IssuerName).string().not_null()) + .col(ColumnDef::new(crate::schema::sanction::Column::Descriptor).string().not_null()) + .col(ColumnDef::new(crate::schema::sanction::Column::Reason).string().not_null()) + .col(ColumnDef::new(crate::schema::sanction::Column::Duration).big_integer()) + .col(ColumnDef::new(crate::schema::sanction::Column::Acknowledged).big_integer()) + .col(ColumnDef::new(crate::schema::sanction::Column::AppealerId).unsigned()) + .foreign_key( + ForeignKey::create() + .name("fk-sanction-appealer_id") + .from(crate::schema::sanction::Entity, crate::schema::sanction::Column::AppealerId) + .to(crate::schema::user::Entity, crate::schema::user::Column::Id), + ) + .col(ColumnDef::new(crate::schema::sanction::Column::AppealTime).big_integer()) + .to_owned(), + ) + .await + } + + // Define how to rollback this migration: Drop the Sanctions table. + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(crate::schema::sanction::Entity).to_owned()) + .await + } +} diff --git a/rc_database/src/migration/mod.rs b/rc_database/src/migration/mod.rs index 6dd9348..2026aa8 100644 --- a/rc_database/src/migration/mod.rs +++ b/rc_database/src/migration/mod.rs @@ -6,6 +6,7 @@ mod m20250424_000003_create_garage_table; mod m20250424_000004_create_user_aux_table; mod m20250424_000005_create_campaign_tables; mod m20250526_000001_add_garage_customisation; +mod m20250529_000001_create_sanction_table; pub struct Migrator; @@ -19,6 +20,7 @@ impl MigratorTrait for Migrator { Box::new(m20250424_000004_create_user_aux_table::Migration), Box::new(m20250424_000005_create_campaign_tables::Migration), Box::new(m20250526_000001_add_garage_customisation::Migration), + Box::new(m20250529_000001_create_sanction_table::Migration), ] } } diff --git a/rc_database/src/schema/mod.rs b/rc_database/src/schema/mod.rs index 409f753..44a02fd 100644 --- a/rc_database/src/schema/mod.rs +++ b/rc_database/src/schema/mod.rs @@ -5,6 +5,7 @@ pub mod garage; pub mod campaign; pub mod campaign_difficulty_completion; pub mod common_query; +pub mod sanction; pub fn parse_int_csv(s: &str) -> Vec { s.split(',').filter_map(|i_as_s| { diff --git a/rc_database/src/schema/sanction.rs b/rc_database/src/schema/sanction.rs new file mode 100644 index 0000000..b4fc77f --- /dev/null +++ b/rc_database/src/schema/sanction.rs @@ -0,0 +1,46 @@ +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "sanctions")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: u32, + pub user_id: u32, + pub creation_time: i64, // seconds since unix epoch + pub issuer_id: u32, + pub issuer_name: String, + pub descriptor: Descriptor, + pub reason: String, + pub duration: Option, // seconds after creation_time (null means permanent or irrelevant) + pub acknowledged: Option, // seconds since unix epoch + pub appealer_id: Option, // moderator who approved appeal + pub appeal_time: Option, // seconds since unix epoch +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::UserId", + to = "super::user::Column::Id" + )] + User, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)] +#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)", rename_all = "PascalCase")] +pub enum Descriptor { + Warn, + Mute, + Ban, + Note, + Kick, +} diff --git a/rc_database/src/wrapper.rs b/rc_database/src/wrapper.rs index 22575f3..ed78308 100644 --- a/rc_database/src/wrapper.rs +++ b/rc_database/src/wrapper.rs @@ -1,5 +1,5 @@ use sea_orm_migration::MigratorTrait; -use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect, TransactionTrait}; +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect, TransactionTrait}; pub struct Database { orm: sea_orm::DatabaseConnection, @@ -105,6 +105,24 @@ impl Database { entity.insert(&self.orm).await } + pub async fn update_perms_by_user_id(&self, mut entity: crate::schema::permissions::ActiveModel, user_id: u32) -> Result, sea_orm::DbErr> { + let id_opt = crate::schema::permissions::Entity::find() + .select_only() + .column(crate::schema::permissions::Column::Id) + .filter(crate::schema::permissions::Column::UserId.eq(user_id)) + .into_model::() + .one(&self.orm) + .await?; + if let Some(id) = id_opt { + entity.id = sea_orm::ActiveValue::Set(id.id); + Ok(Some(crate::schema::permissions::Entity::update(entity) + .exec(&self.orm) + .await?)) + } else { + Ok(None) + } + } + pub async fn garage_max_slot_by_user_id(&self, user_id: u32) -> Result { let result = crate::schema::garage::Entity::find() .select_only() @@ -227,4 +245,26 @@ impl Database { })?; Ok(()) } + + pub async fn count_sanctions_to_ack_by_user_id_and_descriptor(&self, user_id: u32, desc: crate::schema::sanction::Descriptor) -> Result { + crate::schema::sanction::Entity::find() + .filter(crate::schema::sanction::Column::UserId.eq(user_id)) + .filter(crate::schema::sanction::Column::Descriptor.eq(desc)) + .filter(crate::schema::sanction::Column::Acknowledged.is_null()) + .count(&self.orm) + .await + } + + pub async fn sanctions_by_user_id(&self, user_id: u32) -> Result, sea_orm::DbErr> { + crate::schema::sanction::Entity::find() + .filter(crate::schema::sanction::Column::UserId.eq(user_id)) + .filter(crate::schema::sanction::Column::Acknowledged.is_null()) + .order_by_asc(crate::schema::sanction::Column::CreationTime) + .all(&self.orm) + .await + } + + pub async fn insert_sanction(&self, entity: crate::schema::sanction::ActiveModel) -> Result { + entity.insert(&self.orm).await + } } diff --git a/rc_services_room/src/operations/mod.rs b/rc_services_room/src/operations/mod.rs index 8b59a3c..6514e5b 100644 --- a/rc_services_room/src/operations/mod.rs +++ b/rc_services_room/src/operations/mod.rs @@ -164,6 +164,7 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler .add(polariton_server::operations::Ack::<152, _>::default()) // custom game player state changed (188 is desired state) .add(custom_games_invite::pending_invite_provider()) .add(chat_settings::chat_settings_provider()) + .add(polariton_server::operations::Ack::<19, _>::default()) // save chat settings .add(prebuilt_robots::garage_robot_data_provider()) .add(prebuilt_colours::garage_colour_combo_provider()) .add(robopass_preview_items::robopass_preview_provider())