mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Add server-wide and user-only federation controls (#133)
### Description Completes #122 ### Please confirm - [x] I am the legal owner or represent the legal owner of all work submitted (including LLM-generated code, if any) - [x] I consent to my changes being added to this FOSS project - [x] I have confirmed that this does not add new errors or warnings with `utils/clippy.sh` - [ ] This PR used LLMs to generate some or all of the code changes Reviewed-on: https://git.ngram.ca/OpenJam/rc-servers/pulls/133
This commit is contained in:
@@ -8,7 +8,7 @@ use polariton::serdes::TypePrefix;
|
||||
|
||||
use crate::persist::config::SelfValidator;
|
||||
|
||||
use super::super::{MovementCategoryData, MovementData, Cube, ItemCategory, ItemTier, BattleConfig, Settings, ChatConfig, FactoryConfig, ItemShopConfig};
|
||||
use super::super::{MovementCategoryData, MovementData, Cube, ItemCategory, ItemTier, BattleConfig, Settings, ChatConfig, FactoryConfig, ItemShopConfig, FederationConfig};
|
||||
|
||||
const CUBE_CONFIG_FILENAME: &str = "config.json";
|
||||
|
||||
@@ -22,6 +22,8 @@ pub struct CubeConfig {
|
||||
factory: FactoryConfig,
|
||||
shop: ItemShopConfig,
|
||||
settings: Settings,
|
||||
#[serde(default = "super::super::default_federation")]
|
||||
federation: FederationConfig,
|
||||
}
|
||||
|
||||
impl CubeConfig {
|
||||
@@ -623,7 +625,15 @@ impl <C: Clone + Send> super::ConfigProvider<C> for CubeConfig {
|
||||
factory: self.factory.redacted_clone(),
|
||||
shop: self.shop.clone(),
|
||||
settings: self.settings.redacted_clone(),
|
||||
federation: self.federation.clone(),
|
||||
};
|
||||
serde_json::to_string(&redacted_self).expect("Failed to re-serialize config.json")
|
||||
}
|
||||
|
||||
fn federation(&self) -> Option<super::Federation> {
|
||||
self.federation.enabled.then(|| super::Federation {
|
||||
aliases: self.federation.aliases.clone(),
|
||||
defederated: self.federation.defederated.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ mod cubes_json;
|
||||
pub use cubes_json::CubeConfig;
|
||||
|
||||
mod traits;
|
||||
pub use traits::{ConfigProvider, DevMessageProvider, ServerConfig, GarageUpgrades, GarageUpgradeIncrement, ChatSystemConfig, GameEventSequence, GameEvents, GameRotationStrategy, GameEvent, GameMap, GameVisibility, GameType, SingleplayerConfig, VehicleInfo, VehicleDescriptor, QueueChangeMode, Point, Sphere, MapConfig, LinksConfig, FakePlayer, ClientEmulator, EnergyConfig, BattleArenaResolver, PitSettings, PitWinCondition, TeamDeathMatchSettings, ShopEntriesResolver, ShopAction, ShopGain, PromoCode, MultiplayerSettings, BattleArenaCrystalParams, VehicleValidators, TeamChoosers, FactoryConfig, PlatformConfig};
|
||||
pub use traits::{ConfigProvider, DevMessageProvider, ServerConfig, GarageUpgrades, GarageUpgradeIncrement, ChatSystemConfig, GameEventSequence, GameEvents, GameRotationStrategy, GameEvent, GameMap, GameVisibility, GameType, SingleplayerConfig, VehicleInfo, VehicleDescriptor, QueueChangeMode, Point, Sphere, MapConfig, LinksConfig, FakePlayer, ClientEmulator, EnergyConfig, BattleArenaResolver, PitSettings, PitWinCondition, TeamDeathMatchSettings, ShopEntriesResolver, ShopAction, ShopGain, PromoCode, MultiplayerSettings, BattleArenaCrystalParams, VehicleValidators, TeamChoosers, FactoryConfig, PlatformConfig, Federation};
|
||||
pub(super) use traits::RedactedClone;
|
||||
|
||||
mod validation;
|
||||
|
||||
@@ -46,6 +46,8 @@ pub trait ConfigProvider<C: Clone> {
|
||||
fn garage_slot_limit(&self) -> i32;
|
||||
fn team_choosers(&self) -> TeamChoosers;
|
||||
fn redacted_json(&self) -> String;
|
||||
/// None when federation is not enabled
|
||||
fn federation(&self) -> Option<Federation>;
|
||||
}
|
||||
|
||||
pub struct DevMessageProvider<C: Clone> {
|
||||
@@ -579,3 +581,8 @@ pub struct PlatformConfig {
|
||||
pub trait RedactedClone {
|
||||
fn redacted_clone(&self) -> Self;
|
||||
}
|
||||
|
||||
pub struct Federation {
|
||||
pub aliases: std::collections::HashMap<String, String>,
|
||||
pub defederated: Vec<String>,
|
||||
}
|
||||
|
||||
33
rc_core/src/persist/federation.rs
Normal file
33
rc_core/src/persist/federation.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct FederationConfig {
|
||||
#[serde(default)]
|
||||
pub enabled: bool,
|
||||
#[serde(default = "default_aliases")]
|
||||
pub aliases: std::collections::HashMap<String, String>,
|
||||
#[serde(default = "default_defederated")]
|
||||
pub defederated: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn default_federation() -> FederationConfig {
|
||||
FederationConfig {
|
||||
enabled: false,
|
||||
aliases: default_aliases(),
|
||||
defederated: default_defederated(),
|
||||
}
|
||||
}
|
||||
|
||||
fn default_aliases() -> std::collections::HashMap<String, String> {
|
||||
let mut alias_map = std::collections::HashMap::with_capacity(3);
|
||||
alias_map.insert("rc.ngram.ca".to_owned(), "society.rc.ngram.ca".to_owned());
|
||||
alias_map.insert("robocraft.online".to_owned(), "society.robocraft.online".to_owned());
|
||||
alias_map.insert("robocraftgame.co.uk".to_owned(), "society.robocraftgame.co.uk".to_owned());
|
||||
alias_map
|
||||
}
|
||||
|
||||
fn default_defederated() -> Vec<String> {
|
||||
vec![
|
||||
"robocraftgame.com".to_owned(),
|
||||
]
|
||||
}
|
||||
@@ -53,6 +53,10 @@ pub use vehicle_validator::VehicleValidator;
|
||||
pub(crate) mod conversion;
|
||||
pub use conversion::{CubeConversionData, FromConversionData, ToConversionData};
|
||||
|
||||
mod federation;
|
||||
pub use federation::FederationConfig;
|
||||
pub(crate) use federation::default_federation;
|
||||
|
||||
const VALID_ROBOT: &[u8] = &[64,
|
||||
0,
|
||||
0,
|
||||
|
||||
@@ -17,6 +17,8 @@ pub struct AccountProvider {
|
||||
pub(super) intercom_http_client: std::sync::Arc<reqwest::Client>,
|
||||
pub(super) secret: std::sync::Arc<Vec<u8>>,
|
||||
db: std::sync::Arc<oj_rc_database::Database>,
|
||||
#[allow(dead_code)]
|
||||
federation: Option<crate::persist::config::Federation>,
|
||||
}
|
||||
|
||||
impl AccountProvider {
|
||||
@@ -29,6 +31,7 @@ impl AccountProvider {
|
||||
log::debug!("Connecting to user database URI: {}", database_uri);
|
||||
let db = oj_rc_database::Database::init(&database_uri).await
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::NotConnected, e))?;
|
||||
let federation_conf = <crate::persist::config::ConfigImpl as ConfigProvider<()>>::federation(conf);
|
||||
Ok(Self {
|
||||
cubes: std::sync::Arc::new(<crate::persist::config::ConfigImpl as ConfigProvider<()>>::ids(conf)),
|
||||
garage_upgrades: std::sync::Arc::new(<crate::persist::config::ConfigImpl as ConfigProvider<()>>::garage_upgrades(conf)),
|
||||
@@ -42,6 +45,7 @@ impl AccountProvider {
|
||||
intercom_http_client: std::sync::Arc::new(reqwest::Client::new()),
|
||||
secret: std::sync::Arc::new(secret),
|
||||
db: std::sync::Arc::new(db),
|
||||
federation: federation_conf,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -65,5 +65,69 @@ impl super::CommonUser for UserData {
|
||||
))
|
||||
}
|
||||
|
||||
async fn fedi_get(&self) -> super::Federation {
|
||||
match self.db.user_aux_by_user_id_and_descriptor(self.account.id, oj_rc_database::schema::user_aux::Descriptor::Federation).await {
|
||||
Ok(Some(fed)) => {
|
||||
match serde_json::from_str(&fed.data) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
log::error!("Failed to deserialize user_aux Federation for user {}: {} (fedi_get)", self.account.id, e);
|
||||
super::Federation::default()
|
||||
}
|
||||
}
|
||||
},
|
||||
Ok(None) => {
|
||||
// DB upgrade path; return default and also insert it into database
|
||||
let default_fedi = super::Federation::default();
|
||||
let data_str = serde_json::to_string_pretty(&default_fedi).expect("Failed to serialize user_aux Federation data (fedi_get)");
|
||||
let model = oj_rc_database::schema::user_aux::ActiveModel {
|
||||
id: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
||||
user_id: oj_rc_database::sea_orm::ActiveValue::Set(self.account.id),
|
||||
creation_time: oj_rc_database::sea_orm::ActiveValue::Set(chrono::Utc::now().timestamp()),
|
||||
descriptor: oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::user_aux::Descriptor::Federation),
|
||||
data: oj_rc_database::sea_orm::ActiveValue::Set(data_str),
|
||||
};
|
||||
if let Err(e) = self.db.insert_user_aux(vec![model]).await {
|
||||
log::error!("Failed to insert user_aux Federation for user {}: {} (fedi_get)", self.account.id, e);
|
||||
}
|
||||
default_fedi
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to retrieve user_aux Federation for user {}: {} (fedi_get)", self.account.id, e);
|
||||
super::Federation::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn fedi_set(&self, fedi: super::Federation) -> bool {
|
||||
let data_str = serde_json::to_string_pretty(&fedi).expect("Failed to serialize user_aux Federation data (fedi_set)");
|
||||
let model = oj_rc_database::schema::user_aux::ActiveModel {
|
||||
id: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
||||
user_id: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
||||
creation_time: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
||||
descriptor: oj_rc_database::sea_orm::ActiveValue::NotSet,
|
||||
data: oj_rc_database::sea_orm::ActiveValue::Set(data_str),
|
||||
};
|
||||
match self.db.update_user_aux_by_user_id_and_descriptor(model.clone(), self.account.id, oj_rc_database::schema::user_aux::Descriptor::Federation).await {
|
||||
Ok(Some(_fed)) => true,
|
||||
Ok(None) => {
|
||||
// DB upgrade path; insert it into database instead
|
||||
let mut model = model;
|
||||
model.user_id = oj_rc_database::sea_orm::ActiveValue::Set(self.account.id);
|
||||
model.creation_time = oj_rc_database::sea_orm::ActiveValue::Set(chrono::Utc::now().timestamp());
|
||||
model.descriptor = oj_rc_database::sea_orm::ActiveValue::Set(oj_rc_database::schema::user_aux::Descriptor::Federation);
|
||||
match self.db.insert_user_aux(vec![model]).await {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
log::error!("Failed to insert user_aux Federation for user {}: {} (fedi_get)", self.account.id, e);
|
||||
false
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Failed to update user_aux Federation for user {}: {} (fedi_get)", self.account.id, e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
16
rc_core/src/persist/user/federation.rs
Normal file
16
rc_core/src/persist/user/federation.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Federation {
|
||||
pub enabled: bool,
|
||||
pub defederated: Vec<String>,
|
||||
}
|
||||
|
||||
impl std::default::Default for Federation {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
defederated: Vec::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,9 @@ mod team;
|
||||
pub use team::{TeamChooser, StandardTeamChooser};
|
||||
mod web;
|
||||
|
||||
mod federation;
|
||||
pub use federation::Federation;
|
||||
|
||||
pub const TOKEN_SECRET_FILENAME: &str = "token_secret.key";
|
||||
|
||||
pub const USERS_DIR: &str = "accounts";
|
||||
|
||||
@@ -467,6 +467,8 @@ pub trait CommonUser: Send + Sync {
|
||||
async fn db_metrics(&self) -> oj_rc_database::DatabaseMetrics;
|
||||
async fn db_counters(&self) -> Vec<(&'static str, i64)>;
|
||||
async fn currency(&self, ty: CurrencyType, op: CurrencyOp) -> Result<u64, polariton_server::operations::SimpleOpError>;
|
||||
async fn fedi_get(&self) -> super::Federation;
|
||||
async fn fedi_set(&self, fedi: super::Federation) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
|
||||
|
||||
Reference in New Issue
Block a user