mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
rc_society dashboard display (#131)
### Description Closes #130 ### 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/131
This commit is contained in:
@@ -28,6 +28,16 @@ impl oj_rc_factory::VehicleFactoryAdapter for Factory {
|
||||
}
|
||||
}
|
||||
|
||||
async fn count(&self, query: libfj::robocraft::ListQuery) -> Result<usize, Box<dyn std::error::Error>> {
|
||||
match self {
|
||||
Self::Arc(x) => x.count(query).await,
|
||||
Self::Primary(x) => x.count(query).await,
|
||||
Self::Web(x) => x.count(query).await,
|
||||
Self::Custom(x) => x.count(query).await,
|
||||
Self::None => Ok(0),
|
||||
}
|
||||
}
|
||||
|
||||
async fn upload(&self, vehicle: oj_rc_factory::VehicleUploadInfo) -> Result<oj_rc_factory::VehicleThumbnailInfo, Box<dyn std::error::Error>> {
|
||||
match self {
|
||||
Self::Arc(x) => x.upload(vehicle).await,
|
||||
|
||||
@@ -323,6 +323,7 @@ impl <C: Clone + Send> super::ConfigProvider<C> for CubeConfig {
|
||||
cdn_url: self.settings.server.cdn_url.trim_end_matches('/').to_owned(),
|
||||
auth_url: self.settings.server.auth_url.trim_end_matches('/').to_owned(),
|
||||
intercom_url: self.settings.server.intercom_url.trim_end_matches('/').to_owned(),
|
||||
factory_url: self.settings.server.factory_url.trim_end_matches('/').to_owned(),
|
||||
minimum_version: self.settings.server.min_version as i32,
|
||||
dos_protect: self.settings.server.dos_protection,
|
||||
maintenance_message: self.settings.server.maintenance_message.clone(),
|
||||
|
||||
@@ -100,6 +100,7 @@ pub struct ServerConfig {
|
||||
pub cdn_url: String,
|
||||
pub auth_url: String,
|
||||
pub intercom_url: String,
|
||||
pub factory_url: String,
|
||||
pub minimum_version: i32,
|
||||
pub dos_protect: bool,
|
||||
pub maintenance_message: Option<String>,
|
||||
|
||||
@@ -96,6 +96,8 @@ pub struct ServerSettings {
|
||||
pub auth_url: String,
|
||||
#[serde(default = "default_intercom_root_url")]
|
||||
pub intercom_url: String,
|
||||
#[serde(default = "default_factory_url")]
|
||||
pub factory_url: String,
|
||||
#[serde(default = "default_feedback_url")]
|
||||
pub feedback_url: String,
|
||||
#[serde(default = "default_support_url")]
|
||||
@@ -132,6 +134,7 @@ fn default_server_conf() -> ServerSettings {
|
||||
cdn_url: default_cdn_root_url(),
|
||||
auth_url: default_auth_root_url(),
|
||||
intercom_url: default_intercom_root_url(),
|
||||
factory_url: default_factory_url(),
|
||||
feedback_url: default_feedback_url(),
|
||||
support_url: default_support_url(),
|
||||
wiki_url: default_wiki_url(),
|
||||
@@ -157,6 +160,10 @@ fn default_intercom_root_url() -> String {
|
||||
"ws://127.0.0.1:8001".to_owned()
|
||||
}
|
||||
|
||||
fn default_factory_url() -> String {
|
||||
"http://127.0.0.1:8012".to_owned()
|
||||
}
|
||||
|
||||
fn default_feedback_url() -> String {
|
||||
"https://mstdn.ca/@ngram".to_owned()
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ mod inventory;
|
||||
pub use inventory::{UnlockedParts, UnlockOverride};
|
||||
|
||||
mod traits;
|
||||
pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserAuthInfo, UserLoginInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData, ChatUser, AvatarInfo, GetAvatarInfo, ControlData, ControlType, CustomisationData, GetCustomisationData, SetSanction, SanctionType, LobbyUser, GameDescriptor, PlayerLobbyDescriptor, MultiplayerUser, PlayerScore, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor, GameEventSetter, CurrentGameEvent, AuthError, IntercomUser, FakePlayers, ResolvedVehicle, CommonUser, IntercomListener, UserRole, SocialUser, SocialUserC, CurrencyType, CurrencyOp, MatchRewards, SingleplayerUser, PurchaseResult, FactoryUser, FriendInviteReturn, FriendData, FriendInviteStatus, SocialInfo, ClanData, ClanMember, ClanMemberRank, ClanType, ClanSearchQuery, ClanInviteData, Userless, GameOverrides, WebUser, GarageWebInfo};
|
||||
pub use traits::{UserProvider, User, UserToken, UserSlots, UserSlotData, VehicleData, UserAuthInfo, UserLoginInfo, UserAuthenticator, NewSlotData, UserId, RegistrationInfo, VehicleUploadData, ChatUser, AvatarInfo, GetAvatarInfo, ControlData, ControlType, CustomisationData, GetCustomisationData, SetSanction, SanctionType, LobbyUser, GameDescriptor, PlayerLobbyDescriptor, MultiplayerUser, PlayerScore, MultiplayerError, MultiplayerErrorCode, PlayerDescriptor, GameEventSetter, CurrentGameEvent, AuthError, IntercomUser, FakePlayers, ResolvedVehicle, CommonUser, IntercomListener, UserRole, SocialUser, SocialUserC, CurrencyType, CurrencyOp, MatchRewards, SingleplayerUser, PurchaseResult, FactoryUser, FriendInviteReturn, FriendData, FriendInviteStatus, SocialInfo, ClanData, ClanMember, ClanMemberRank, ClanType, ClanSearchQuery, ClanInviteData, Userless, GameOverrides, WebUser, GarageWebInfo, GarageWebStats, SanctionWebStats, AccountWebStats, SocialWebStats};
|
||||
|
||||
pub mod intercom;
|
||||
pub use intercom::generate_token as generate_intercom_token;
|
||||
|
||||
@@ -468,7 +468,7 @@ pub trait CommonUser: Send + Sync {
|
||||
async fn currency(&self, ty: CurrencyType, op: CurrencyOp) -> Result<u64, polariton_server::operations::SimpleOpError>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
|
||||
pub enum CurrencyType {
|
||||
Free,
|
||||
Paid,
|
||||
@@ -680,6 +680,10 @@ pub trait WebUser: CommonUser {
|
||||
async fn garage_by_id(&self, id: i32) -> Result<Option<VehicleData>, Box<dyn std::error::Error>>;
|
||||
async fn save_garage(&self, vehicle: crate::persist::user::VehicleData, garage_id: Option<i32>, cpu_counter: &crate::cubes::CpuListParser, weapon_orderer: &crate::cubes::WeaponListParser) -> Result<(), Box<dyn std::error::Error>>;
|
||||
async fn garage_id_selected(&self) -> Result<Option<i32>, Box<dyn std::error::Error>>;
|
||||
async fn garage_stats(&self) -> Result<GarageWebStats, Box<dyn std::error::Error>>;
|
||||
async fn account_stats(&self) -> Result<AccountWebStats, Box<dyn std::error::Error>>;
|
||||
async fn sanction_stats(&self) -> Result<SanctionWebStats, Box<dyn std::error::Error>>;
|
||||
async fn social_stats(&self) -> Result<SocialWebStats, Box<dyn std::error::Error>>;
|
||||
}
|
||||
|
||||
pub struct GarageWebInfo {
|
||||
@@ -691,6 +695,41 @@ pub struct GarageWebInfo {
|
||||
pub creation_time: i64,
|
||||
}
|
||||
|
||||
pub struct GarageWebStats {
|
||||
pub vehicle_total: u64,
|
||||
pub regular_vehicle_total: u64,
|
||||
pub mega_vehicle_total: u64,
|
||||
pub empty_vehicle_total: u64,
|
||||
pub factory_vehicle_total: u64,
|
||||
pub storage_bytes_total: Option<u64>,
|
||||
pub selected_garage: i32,
|
||||
}
|
||||
|
||||
pub struct AccountWebStats {
|
||||
pub currencies: std::collections::HashMap<CurrencyType, u64>,
|
||||
pub games_played: u64,
|
||||
pub avatar_id: Option<i32>,
|
||||
pub premium_until: i64,
|
||||
pub rank: u32,
|
||||
}
|
||||
|
||||
pub struct SanctionWebStats {
|
||||
pub total: u64,
|
||||
pub pending_total: u64,
|
||||
pub warn_total: u64,
|
||||
pub mute_total: u64,
|
||||
pub muted_until: Option<i64>,
|
||||
}
|
||||
|
||||
pub struct SocialWebStats {
|
||||
pub clan: Option<ClanData>,
|
||||
/// Friend source is user
|
||||
pub friends_total: u64,
|
||||
/// Friend target is user
|
||||
pub friends_of_total: u64,
|
||||
pub chats: Vec<String>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait Userless: Send + Sync {
|
||||
async fn lobby_state_listener(&self) -> Result<super::IntercomListener<super::intercom::IntercomLobbyStateMessage>, reqwest_websocket::Error>;
|
||||
|
||||
@@ -106,4 +106,100 @@ impl super::WebUser for super::account_json::UserData {
|
||||
Ok(self.db.garage_selected(self.account.id).await?
|
||||
.map(|x| x.id))
|
||||
}
|
||||
|
||||
async fn garage_stats(&self) -> Result<super::GarageWebStats, Box<dyn std::error::Error>> {
|
||||
let vehicle_count = self.db.garage_count_by_user_id(self.account.id).await?;
|
||||
let regular_count = self.db.garage_count_by_user_id_between(self.account.id, 2_000, 1).await?;
|
||||
let mega_count = self.db.garage_count_by_user_id_between(self.account.id, u32::MAX as u64, 2_000).await?;
|
||||
let empty_count = self.db.garage_count_by_user_id_between(self.account.id, 0, 0).await?;
|
||||
let factory_count = self.db.garage_count_by_user_id_factory(self.account.id).await?;
|
||||
let storage_size = self.db.garage_storage_by_user_id(self.account.id).await?;
|
||||
let selected_garage_slot = self.db.garage_selected(self.account.id).await?.map(|g| g.slot).unwrap_or_default();
|
||||
Ok(super::GarageWebStats {
|
||||
vehicle_total: vehicle_count,
|
||||
regular_vehicle_total: regular_count,
|
||||
mega_vehicle_total: mega_count,
|
||||
empty_vehicle_total: empty_count,
|
||||
factory_vehicle_total: factory_count,
|
||||
storage_bytes_total: storage_size,
|
||||
selected_garage: selected_garage_slot,
|
||||
})
|
||||
}
|
||||
|
||||
async fn account_stats(&self) -> Result<super::AccountWebStats, Box<dyn std::error::Error>> {
|
||||
let user_aux_data = self.db.user_aux_by_user_id(self.account.id).await?;
|
||||
let mut currency_map = std::collections::HashMap::with_capacity(8);
|
||||
let mut avatar_id = None;
|
||||
let mut premium_until = 0;
|
||||
let mut user_rank = 0;
|
||||
for row in user_aux_data.iter() {
|
||||
match row.descriptor {
|
||||
oj_rc_database::schema::user_aux::Descriptor::UserXP => {
|
||||
currency_map.insert(super::CurrencyType::Experience, row.data.parse().unwrap_or_default());
|
||||
},
|
||||
oj_rc_database::schema::user_aux::Descriptor::TechPoints => {
|
||||
currency_map.insert(super::CurrencyType::TechPoints, row.data.parse().unwrap_or_default());
|
||||
},
|
||||
oj_rc_database::schema::user_aux::Descriptor::UserFreeCurrency => {
|
||||
currency_map.insert(super::CurrencyType::Free, row.data.parse().unwrap_or_default());
|
||||
},
|
||||
oj_rc_database::schema::user_aux::Descriptor::UserPaidCurrency => {
|
||||
currency_map.insert(super::CurrencyType::Paid, row.data.parse().unwrap_or_default());
|
||||
},
|
||||
oj_rc_database::schema::user_aux::Descriptor::AvatarId => {
|
||||
avatar_id = row.data.parse::<i32>().ok();
|
||||
},
|
||||
oj_rc_database::schema::user_aux::Descriptor::PremiumExpiry => {
|
||||
premium_until = row.data.parse().unwrap_or_default();
|
||||
},
|
||||
oj_rc_database::schema::user_aux::Descriptor::UserRank => {
|
||||
user_rank = row.data.parse().unwrap_or_default();
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
let game_count = self.db.count_games_by_user_id(self.account.id).await?;
|
||||
Ok(super::AccountWebStats {
|
||||
currencies: currency_map,
|
||||
games_played: game_count,
|
||||
avatar_id,
|
||||
premium_until,
|
||||
rank: user_rank,
|
||||
})
|
||||
}
|
||||
|
||||
async fn sanction_stats(&self) -> Result<super::SanctionWebStats, Box<dyn std::error::Error>> {
|
||||
let all_count = self.db.count_sanctions_by_user_id(self.account.id).await?;
|
||||
let pending_count = self.db.count_sanctions_by_user_id_and_ack(self.account.id, false).await?;
|
||||
let warn_count = self.db.count_sanctions_by_user_id_and_descriptor(self.account.id, oj_rc_database::schema::sanction::Descriptor::Warn).await?;
|
||||
let mute_count = self.db.count_sanctions_by_user_id_and_descriptor(self.account.id, oj_rc_database::schema::sanction::Descriptor::Mute).await?;
|
||||
let first_active_mute = self.db.sanction_by_user_id_and_descriptor_and_active(self.account.id, oj_rc_database::schema::sanction::Descriptor::Mute).await?;
|
||||
Ok(super::SanctionWebStats {
|
||||
total: all_count,
|
||||
pending_total: pending_count,
|
||||
warn_total: warn_count,
|
||||
mute_total: mute_count,
|
||||
muted_until: first_active_mute.map(|s| s.creation_time + s.duration.unwrap_or_default()),
|
||||
})
|
||||
}
|
||||
|
||||
async fn social_stats(&self) -> Result<super::SocialWebStats, Box<dyn std::error::Error>> {
|
||||
let clan_opt = self.db.clan_by_user_id(self.account.id).await?
|
||||
.map(|(clan, _myself)| super::ClanData {
|
||||
name: clan.name,
|
||||
description: clan.description,
|
||||
ty: super::ClanType::db_to_core(&clan.variant),
|
||||
size: 0,
|
||||
});
|
||||
let friends_count = self.db.count_friends_by_user_id(self.account.id, oj_rc_database::schema::friend::FriendStatus::Accepted).await?;
|
||||
let friends_of_count = self.db.count_friends_target_by_user_id(self.account.id, oj_rc_database::schema::friend::FriendStatus::Accepted).await?;
|
||||
let subbed_chats_aux = self.db.user_aux_by_user_id_and_descriptor(self.account.id, oj_rc_database::schema::user_aux::Descriptor::SubscribedChannels).await?;
|
||||
let chat_list: Vec<String> = subbed_chats_aux.and_then(|aux| serde_json::from_str(&aux.data).ok()).unwrap_or_default();
|
||||
Ok(super::SocialWebStats {
|
||||
clan: clan_opt,
|
||||
friends_total: friends_count,
|
||||
friends_of_total: friends_of_count,
|
||||
chats: chat_list,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user