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:
@@ -26,6 +26,7 @@ async fn main() -> std::io::Result<()> {
|
||||
let config = oj_rc_core::ConfigImpl::load(&cli_args.assets_robocraft)?;
|
||||
|
||||
let server_settings = actix_web::web::Data::new(<oj_rc_core::ConfigImpl as oj_rc_core::ConfigProvider<()>>::server_config(&config));
|
||||
let server_links = actix_web::web::Data::new(<oj_rc_core::ConfigImpl as oj_rc_core::ConfigProvider<()>>::url_links(&config));
|
||||
|
||||
let parsers = oj_rc_core::cubes::CubeParsers::new(&config);
|
||||
|
||||
@@ -36,6 +37,8 @@ async fn main() -> std::io::Result<()> {
|
||||
let parsers_ref = actix_web::web::Data::new(parsers);
|
||||
|
||||
let users = oj_rc_core::UserImpl::load(&cli_args.data_robocraft, &config).await.expect("Bad user data");
|
||||
let factory = <oj_rc_core::persist::config::ConfigImpl as oj_rc_core::ConfigProvider<()>>::factory(&config, &|| users.factory_impl()).await.expect("Bad vehicle factory (CRF) config");
|
||||
let factory_ref = actix_web::web::Data::new(factory);
|
||||
let auth_ref = actix_web::web::Data::new(Box::new(users));
|
||||
|
||||
let cli_args2 = actix_web::web::Data::new(cli_args.clone());
|
||||
@@ -72,9 +75,11 @@ async fn main() -> std::io::Result<()> {
|
||||
.app_data(loadeds_args.clone())
|
||||
.app_data(handlebars_ref.clone())
|
||||
.app_data(server_settings.clone())
|
||||
.app_data(server_links.clone())
|
||||
.app_data(auth_ref.clone())
|
||||
.app_data(importers_ref.clone())
|
||||
.app_data(parsers_ref.clone())
|
||||
.app_data(factory_ref.clone())
|
||||
.service(version_info)
|
||||
.service(web::login::form_submit)
|
||||
.service(web::login::form_load)
|
||||
|
||||
@@ -6,11 +6,15 @@ const FORM_NAME: &str = "dashboard";
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RenderData {
|
||||
// TODO
|
||||
display_name: String,
|
||||
public_id: String,
|
||||
debug: DebugData,
|
||||
perms: PermissionData,
|
||||
garage: GarageData,
|
||||
factory: FactoryData,
|
||||
account: AccountData,
|
||||
sanction: SanctionData,
|
||||
social: SocialData,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -18,6 +22,8 @@ struct DebugData {
|
||||
user_id: i32,
|
||||
creation_time_unix: i64,
|
||||
creation_time_iso: String,
|
||||
cdn: String,
|
||||
factory: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -29,7 +35,59 @@ struct PermissionData {
|
||||
banned: bool,
|
||||
}
|
||||
|
||||
pub async fn dashboard_impl(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct GarageData {
|
||||
total: u64,
|
||||
regular_total: u64,
|
||||
mega_total: u64,
|
||||
empty_total: u64,
|
||||
bytes_total: Option<u64>, // not always supported
|
||||
selected_slot: i32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct FactoryData {
|
||||
garages_from_factory: u64,
|
||||
uploads_total: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct AccountData {
|
||||
currencies: std::collections::HashMap<String, u64>,
|
||||
games: u64,
|
||||
avatar_id: String,
|
||||
is_custom_avatar: bool,
|
||||
premium_until_unix: i64,
|
||||
premium_until_iso: String,
|
||||
rank: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SanctionData {
|
||||
total: u64,
|
||||
pending: u64,
|
||||
warns: u64,
|
||||
mutes: u64,
|
||||
muted_until_unix: Option<i64>,
|
||||
muted_until_iso: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct ClanData {
|
||||
name: String,
|
||||
description: String,
|
||||
ty: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SocialData {
|
||||
clan: Option<ClanData>,
|
||||
friends: u64,
|
||||
friends_of: u64,
|
||||
chats: Vec<String>,
|
||||
}
|
||||
|
||||
pub async fn dashboard_impl(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, factory: Data<oj_rc_core::factory::Factory>, server_config: Data<oj_rc_core::persist::config::ServerConfig>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
match super::try_auth_user(user_opt, auth.as_ref(), &req).await? {
|
||||
super::LoginReturn::AuthFail(resp) => Ok(resp),
|
||||
super::LoginReturn::Success(user) => {
|
||||
@@ -37,6 +95,22 @@ pub async fn dashboard_impl(handlebars_ref: Data<handlebars::Handlebars<'_>>, au
|
||||
log::debug!("Rendering user's dashboard");
|
||||
let creation_time = user.creation();
|
||||
let creation_time_chrono = chrono::DateTime::<chrono::Utc>::from_timestamp_secs(creation_time).unwrap_or_default();
|
||||
let (garage_stats, factory_stats) = match build_garage_data(user.as_ref(), factory.as_ref()).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => return Ok(fallback_render(e, user.as_ref(), handlebars_ref.as_ref(), &req)),
|
||||
};
|
||||
let account_stats = match build_account_data(user.as_ref()).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => return Ok(fallback_render(e, user.as_ref(), handlebars_ref.as_ref(), &req)),
|
||||
};
|
||||
let sanction_stats = match build_sanction_data(user.as_ref()).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => return Ok(fallback_render(e, user.as_ref(), handlebars_ref.as_ref(), &req)),
|
||||
};
|
||||
let social_stats = match build_social_data(user.as_ref()).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => return Ok(fallback_render(e, user.as_ref(), handlebars_ref.as_ref(), &req)),
|
||||
};
|
||||
Ok(super::render_ok(
|
||||
RenderData {
|
||||
display_name: user.display_name().to_owned(),
|
||||
@@ -45,14 +119,15 @@ pub async fn dashboard_impl(handlebars_ref: Data<handlebars::Handlebars<'_>>, au
|
||||
user_id: user.account_id(),
|
||||
creation_time_unix: creation_time,
|
||||
creation_time_iso: creation_time_chrono.to_rfc3339(),
|
||||
cdn: server_config.cdn_url.clone(),
|
||||
factory: server_config.factory_url.clone(),
|
||||
},
|
||||
perms: PermissionData {
|
||||
r#mod: user.is_mod(),
|
||||
admin: user.is_admin(),
|
||||
dev: user.is_dev(),
|
||||
royal: user.is_royal(),
|
||||
banned: user.is_banned(),
|
||||
}
|
||||
perms: build_perm_data(user.as_ref()),
|
||||
garage: garage_stats,
|
||||
factory: factory_stats,
|
||||
account: account_stats,
|
||||
sanction: sanction_stats,
|
||||
social: social_stats,
|
||||
},
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
@@ -64,12 +139,177 @@ pub async fn dashboard_impl(handlebars_ref: Data<handlebars::Handlebars<'_>>, au
|
||||
}
|
||||
}
|
||||
|
||||
fn fallback_render(error: Box<dyn std::error::Error>, user: &dyn oj_rc_core::persist::user::WebUser, handlebars_ref: &handlebars::Handlebars<'_>, req: &HttpRequest) -> actix_web::HttpResponse {
|
||||
let creation_time = user.creation();
|
||||
let creation_time_chrono = chrono::DateTime::<chrono::Utc>::from_timestamp_secs(creation_time).unwrap_or_default();
|
||||
super::render_err(
|
||||
RenderData {
|
||||
display_name: user.display_name().to_owned(),
|
||||
public_id: user.public_id().to_owned(),
|
||||
debug: DebugData {
|
||||
user_id: user.account_id(),
|
||||
creation_time_unix: creation_time,
|
||||
creation_time_iso: creation_time_chrono.to_rfc3339(),
|
||||
cdn: String::default(),
|
||||
factory: String::default(),
|
||||
},
|
||||
perms: PermissionData {
|
||||
r#mod: false,
|
||||
admin: false,
|
||||
dev: false,
|
||||
royal: false,
|
||||
banned: false,
|
||||
},
|
||||
garage: GarageData {
|
||||
total: 0,
|
||||
regular_total: 0,
|
||||
mega_total: 0,
|
||||
empty_total: 0,
|
||||
bytes_total: None,
|
||||
selected_slot: 0,
|
||||
},
|
||||
factory: FactoryData {
|
||||
garages_from_factory: 0,
|
||||
uploads_total: 0,
|
||||
},
|
||||
account: AccountData {
|
||||
currencies: std::collections::HashMap::default(),
|
||||
games: 0,
|
||||
avatar_id: "???".to_owned(),
|
||||
is_custom_avatar: false,
|
||||
premium_until_unix: 0,
|
||||
premium_until_iso: String::default(),
|
||||
rank: 0,
|
||||
},
|
||||
sanction: SanctionData {
|
||||
total: 0,
|
||||
pending: 0,
|
||||
warns: 0,
|
||||
mutes: 0,
|
||||
muted_until_unix: None,
|
||||
muted_until_iso: None,
|
||||
},
|
||||
social: SocialData {
|
||||
clan: None,
|
||||
friends: 0,
|
||||
friends_of: 0,
|
||||
chats: Vec::default()
|
||||
}
|
||||
},
|
||||
format!("Dashboard loading failed: {}", error),
|
||||
handlebars_ref,
|
||||
FORM_NAME,
|
||||
)
|
||||
.respond_to(req)
|
||||
.map_into_boxed_body()
|
||||
}
|
||||
|
||||
fn build_perm_data(user: &dyn oj_rc_core::persist::user::WebUser) -> PermissionData {
|
||||
PermissionData {
|
||||
r#mod: user.is_mod(),
|
||||
admin: user.is_admin(),
|
||||
dev: user.is_dev(),
|
||||
royal: user.is_royal(),
|
||||
banned: user.is_banned(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn build_garage_data(user: &dyn oj_rc_core::persist::user::WebUser, factory: &dyn oj_rc_factory::VehicleFactoryAdapter) -> Result<(GarageData, FactoryData), Box<dyn std::error::Error>> {
|
||||
let stats = user.garage_stats().await?;
|
||||
let uploads_count = factory.count(libfj::robocraft::ListQuery {
|
||||
page: 0,
|
||||
page_size: 1_000,
|
||||
order: libfj::robocraft::FactoryOrderType::Added,
|
||||
player_filter: true,
|
||||
movement_filter: Vec::default(),
|
||||
movement_category_filter: Vec::default(),
|
||||
weapon_filter: Vec::default(),
|
||||
weapon_category_filter: Vec::default(),
|
||||
minimum_cpu: 0,
|
||||
maximum_cpu: i32::MAX as _,
|
||||
text_filter: user.public_id().to_owned(),
|
||||
text_search_field: libfj::robocraft::FactoryTextSearchField::Player,
|
||||
buyable: false,
|
||||
prepend_featured_robot: false,
|
||||
featured_only: false,
|
||||
default_page: false,
|
||||
}).await?;
|
||||
Ok((GarageData {
|
||||
total: stats.vehicle_total,
|
||||
regular_total: stats.regular_vehicle_total,
|
||||
mega_total: stats.mega_vehicle_total,
|
||||
empty_total: stats.empty_vehicle_total,
|
||||
bytes_total: stats.storage_bytes_total,
|
||||
selected_slot: stats.selected_garage,
|
||||
},
|
||||
FactoryData {
|
||||
garages_from_factory: stats.factory_vehicle_total,
|
||||
uploads_total: uploads_count,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn build_account_data(user: &dyn oj_rc_core::persist::user::WebUser) -> Result<AccountData, Box<dyn std::error::Error>> {
|
||||
let stats = user.account_stats().await?;
|
||||
Ok(AccountData {
|
||||
currencies: stats.currencies.into_iter()
|
||||
.map(|(key, val)| (currency_to_str(key).to_owned(), val))
|
||||
.collect(),
|
||||
games: stats.games_played,
|
||||
avatar_id: stats.avatar_id.map(|id| format!("#{}", id)).unwrap_or_else(|| "Custom".to_owned()),
|
||||
is_custom_avatar: stats.avatar_id.is_none(),
|
||||
premium_until_unix: stats.premium_until,
|
||||
premium_until_iso: chrono::DateTime::<chrono::Utc>::from_timestamp_secs(stats.premium_until)
|
||||
.unwrap_or_default()
|
||||
.to_rfc3339(),
|
||||
rank: stats.rank,
|
||||
})
|
||||
}
|
||||
|
||||
fn currency_to_str(currency: oj_rc_core::persist::user::CurrencyType) -> &'static str {
|
||||
match currency {
|
||||
oj_rc_core::persist::user::CurrencyType::Free => "robits",
|
||||
oj_rc_core::persist::user::CurrencyType::Paid => "gc",
|
||||
oj_rc_core::persist::user::CurrencyType::TechPoints => "tp",
|
||||
oj_rc_core::persist::user::CurrencyType::Experience => "xp"
|
||||
}
|
||||
}
|
||||
|
||||
async fn build_sanction_data(user: &dyn oj_rc_core::persist::user::WebUser) -> Result<SanctionData, Box<dyn std::error::Error>> {
|
||||
let stats = user.sanction_stats().await?;
|
||||
Ok(SanctionData {
|
||||
total: stats.total,
|
||||
pending: stats.pending_total,
|
||||
warns: stats.warn_total,
|
||||
mutes: stats.mute_total,
|
||||
muted_until_unix: stats.muted_until,
|
||||
muted_until_iso: stats.muted_until
|
||||
.map(|t| chrono::DateTime::<chrono::Utc>::from_timestamp_secs(t)
|
||||
.unwrap_or_default()
|
||||
.to_rfc3339()
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
async fn build_social_data(user: &dyn oj_rc_core::persist::user::WebUser) -> Result<SocialData, Box<dyn std::error::Error>> {
|
||||
let stats = user.social_stats().await?;
|
||||
Ok(SocialData {
|
||||
clan: stats.clan.map(|clan| ClanData {
|
||||
name: clan.name,
|
||||
description: clan.description,
|
||||
ty: format!("{:?}", clan.ty),
|
||||
}),
|
||||
friends: stats.friends_total,
|
||||
friends_of: stats.friends_of_total,
|
||||
chats: stats.chats,
|
||||
})
|
||||
}
|
||||
|
||||
#[get("/dashboard")]
|
||||
pub async fn get(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
dashboard_impl(handlebars_ref, auth, user_opt, req).await
|
||||
pub async fn get(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, factory: Data<oj_rc_core::factory::Factory>, server_config: Data<oj_rc_core::persist::config::ServerConfig>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
dashboard_impl(handlebars_ref, auth, factory, server_config, user_opt, req).await
|
||||
}
|
||||
|
||||
#[post("/dashboard")]
|
||||
pub async fn post(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
dashboard_impl(handlebars_ref, auth, user_opt, req).await
|
||||
pub async fn post(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, factory: Data<oj_rc_core::factory::Factory>, server_config: Data<oj_rc_core::persist::config::ServerConfig>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
dashboard_impl(handlebars_ref, auth, factory, server_config, user_opt, req).await
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ struct RenderData {
|
||||
is_logged_in: bool,
|
||||
display_name: Option<String>,
|
||||
server: ServerDetails,
|
||||
links: LinkDetails,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -16,6 +17,7 @@ struct ServerDetails {
|
||||
domain: String,
|
||||
cdn: String,
|
||||
auth: String,
|
||||
factory: String,
|
||||
min_version: i32,
|
||||
server_version: String,
|
||||
start_time_iso: String,
|
||||
@@ -32,6 +34,7 @@ fn server_details(conf: &oj_rc_core::persist::config::ServerConfig) -> ServerDet
|
||||
domain: conf.domain.clone(),
|
||||
cdn: conf.cdn_url.clone(),
|
||||
auth: conf.auth_url.clone(),
|
||||
factory: conf.factory_url.clone(),
|
||||
min_version: conf.minimum_version,
|
||||
server_version,
|
||||
start_time_unix,
|
||||
@@ -39,9 +42,25 @@ fn server_details(conf: &oj_rc_core::persist::config::ServerConfig) -> ServerDet
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct LinkDetails {
|
||||
feedback: String,
|
||||
support: String,
|
||||
wiki: String,
|
||||
}
|
||||
|
||||
fn links_details(links: &oj_rc_core::persist::config::LinksConfig) -> LinkDetails {
|
||||
LinkDetails {
|
||||
feedback: links.feedback_url.clone(),
|
||||
support: links.support_url.clone(),
|
||||
wiki: links.wiki_url.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
pub async fn get(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, server_config: Data<oj_rc_core::persist::config::ServerConfig>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
pub async fn get(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Box<oj_rc_core::UserImpl>>, server_config: Data<oj_rc_core::persist::config::ServerConfig>, server_links: Data<oj_rc_core::persist::config::LinksConfig>, user_opt: Option<Identity>, req: HttpRequest) -> Result<impl Responder, actix_web::error::Error> {
|
||||
let server_info = server_details(server_config.as_ref());
|
||||
let links_info = links_details(server_links.as_ref());
|
||||
if let Some(user) = user_opt {
|
||||
match super::try_auth_user(Some(user), auth.as_ref(), &req).await? {
|
||||
super::LoginReturn::AuthFail(resp) => Ok(resp),
|
||||
@@ -51,6 +70,7 @@ pub async fn get(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Bo
|
||||
is_logged_in: true,
|
||||
display_name: Some(user.display_name().to_owned()),
|
||||
server: server_info,
|
||||
links: links_info,
|
||||
},
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
@@ -66,6 +86,7 @@ pub async fn get(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Bo
|
||||
is_logged_in: false,
|
||||
display_name: None,
|
||||
server: server_info,
|
||||
links: links_info,
|
||||
},
|
||||
handlebars_ref.as_ref(),
|
||||
FORM_NAME,
|
||||
|
||||
Reference in New Issue
Block a user