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:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -3326,6 +3326,7 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
"oj_convert",
|
"oj_convert",
|
||||||
"oj_rc_core",
|
"oj_rc_core",
|
||||||
|
"oj_rc_factory",
|
||||||
"oj_rc_plugins",
|
"oj_rc_plugins",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -72,8 +72,20 @@
|
|||||||
<div class="access-box">
|
<div class="access-box">
|
||||||
<h4 class="access-box-title">Garages</h4>
|
<h4 class="access-box-title">Garages</h4>
|
||||||
<div class="box-details">
|
<div class="box-details">
|
||||||
<div class="box-details-key">Total</div> <!-- TODO -->
|
<div class="box-details-key">Total</div>
|
||||||
<div class="box-details-value">{{form.debug.user_id}}/{{form.debug.user_id}}</div>
|
<div class="box-details-value">{{form.garage.total}}</div>
|
||||||
|
<div class="box-details-key">Regulars</div>
|
||||||
|
<div class="box-details-value">{{form.garage.regular_total}}</div>
|
||||||
|
<div class="box-details-key">Megabots</div>
|
||||||
|
<div class="box-details-value">{{form.garage.mega_total}}</div>
|
||||||
|
<div class="box-details-key">Empty</div>
|
||||||
|
<div class="box-details-value">{{form.garage.empty_total}}</div>
|
||||||
|
<div class="box-details-key">Selected Slot</div>
|
||||||
|
<div class="box-details-value">#{{form.garage.selected_slot}}</div>
|
||||||
|
{{#if form.garage.bytes_total }}
|
||||||
|
<div class="box-details-key">Storage</div>
|
||||||
|
<div class="box-details-value">{{form.garage.bytes_total}}B</div>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<a href="/garages/list">
|
<a href="/garages/list">
|
||||||
<div class="access-box-banner">
|
<div class="access-box-banner">
|
||||||
@@ -84,10 +96,12 @@
|
|||||||
<div class="access-box">
|
<div class="access-box">
|
||||||
<h4 class="access-box-title">Factory</h4>
|
<h4 class="access-box-title">Factory</h4>
|
||||||
<div class="box-details">
|
<div class="box-details">
|
||||||
<div class="box-details-key">Total</div> <!-- TODO -->
|
<div class="box-details-key">Factory Garages</div>
|
||||||
<div class="box-details-value">{{form.debug.user_id}}/{{form.debug.user_id}}</div>
|
<div class="box-details-value">{{form.factory.garages_from_factory}}</div>
|
||||||
|
<div class="box-details-key">Uploads</div>
|
||||||
|
<div class="box-details-value">{{form.factory.uploads_total}}</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="#"> <!-- TODO -->
|
<a href="{{form.debug.factory}}">
|
||||||
<div class="access-box-banner">
|
<div class="access-box-banner">
|
||||||
View My Uploads
|
View My Uploads
|
||||||
</div>
|
</div>
|
||||||
@@ -95,6 +109,32 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="access-box">
|
<div class="access-box">
|
||||||
<h4 class="access-box-title">Account</h4>
|
<h4 class="access-box-title">Account</h4>
|
||||||
|
<div class="box-details">
|
||||||
|
{{#if form.account.is_custom_avatar}}
|
||||||
|
<div class="box-details-key">Avatar</div>
|
||||||
|
<div class="box-details-value">
|
||||||
|
<a href="{{form.debug.cdn}}/customavatar/Live/{{form.public_id}}" download="user_{{form.display_name}}.jpg" target="_blank" type="media/jpeg" referrerpolicy="no-referrer">
|
||||||
|
<img src="{{form.debug.cdn}}/customavatar/Live/{{form.public_id}}" alt="Custom avatar picture for {{form.display_name}}" style="margin-top: 5px;"/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="box-details-key">Avatar</div>
|
||||||
|
<div class="box-details-value">{{form.account.avatar_id}}</div>
|
||||||
|
{{/if}}
|
||||||
|
<div class="box-details-key">Rank</div>
|
||||||
|
<div class="box-details-value">{{form.account.rank}}</div>
|
||||||
|
<div class="box-details-key">Currency</div>
|
||||||
|
<div class="box-details-value">
|
||||||
|
Robits: {{form.account.currencies.robits}}<br/>
|
||||||
|
GC: {{form.account.currencies.gc}}<br/>
|
||||||
|
TP: {{form.account.currencies.tp}}<br/>
|
||||||
|
XP: {{form.account.currencies.xp}}<br/>
|
||||||
|
</div>
|
||||||
|
<div class="box-details-key">Games</div>
|
||||||
|
<div class="box-details-value">{{form.account.games}}</div>
|
||||||
|
<div class="box-details-key">Premium Until</div>
|
||||||
|
<div class="box-details-value"><time datetime="{{form.account.premium_until_iso}}">{{form.account.premium_until_iso}}</time></div>
|
||||||
|
</div>
|
||||||
<a href="#"> <!-- TODO -->
|
<a href="#"> <!-- TODO -->
|
||||||
<div class="access-box-banner">
|
<div class="access-box-banner">
|
||||||
Manage My Account
|
Manage My Account
|
||||||
@@ -104,8 +144,20 @@
|
|||||||
<div class="access-box">
|
<div class="access-box">
|
||||||
<h4 class="access-box-title">Sanctions</h4>
|
<h4 class="access-box-title">Sanctions</h4>
|
||||||
<div class="box-details">
|
<div class="box-details">
|
||||||
<div class="box-details-key">Total</div> <!-- TODO -->
|
<div class="box-details-key">Total</div>
|
||||||
<div class="box-details-value">{{form.debug.user_id}}</div>
|
<div class="box-details-value">{{form.sanction.total}}</div>
|
||||||
|
{{#if (ne form.sanction.pending 0)}}
|
||||||
|
<div class="box-details-key">Pending</div>
|
||||||
|
<div class="box-details-value">{{form.sanction.pending}}</div>
|
||||||
|
{{/if}}
|
||||||
|
<div class="box-details-key">Warnings</div>
|
||||||
|
<div class="box-details-value">{{form.sanction.warns}}</div>
|
||||||
|
<div class="box-details-key">Mutes</div>
|
||||||
|
<div class="box-details-value">{{form.sanction.mutes}}</div>
|
||||||
|
{{#if form.sanction.muted_until_iso}}
|
||||||
|
<div class="box-details-key">Muted Until</div>
|
||||||
|
<div class="box-details-value"><time datetime="{{form.sanction.muted_until_iso}}">{{form.sanction.muted_until_iso}}</time></div>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<a href="#"> <!-- TODO -->
|
<a href="#"> <!-- TODO -->
|
||||||
<div class="access-box-banner">
|
<div class="access-box-banner">
|
||||||
@@ -116,17 +168,40 @@
|
|||||||
<div class="access-box">
|
<div class="access-box">
|
||||||
<h4 class="access-box-title">Social</h4>
|
<h4 class="access-box-title">Social</h4>
|
||||||
<div class="box-details">
|
<div class="box-details">
|
||||||
<div class="box-details-key">Clan</div> <!-- TODO -->
|
{{#if form.social.clan}}
|
||||||
<div class="box-details-value">{{form.display_name}}</div>
|
<div class="box-details-key">Clan</div>
|
||||||
<div class="box-details-key">Friends</div> <!-- TODO -->
|
<div class="box-details-value">
|
||||||
<div class="box-details-value">{{form.debug.user_id}}</div>
|
<span style="display: inline-block; text-align: center; width: 100%;">{{form.social.clan.name}}</span>
|
||||||
|
<a href="{{form.debug.cdn}}/clanavatar/Live/{{form.social.clan.name}}" download="clan_{{form.social.clan.name}}.jpg" target="_blank" type="media/jpeg" referrerpolicy="no-referrer">
|
||||||
|
<img src="{{form.debug.cdn}}/clanavatar/Live/{{form.social.clan.name}}" alt="Clan avatar picture for {{form.social.clan.name}}" style="margin-top: 5px;"/>
|
||||||
|
</a>
|
||||||
|
<span style="display: inline-block; font-size: 10px; text-align: center; margin: 5px auto; width: 100%;">{{form.social.clan.ty}}</span>
|
||||||
|
<span>{{form.social.clan.description}}</span>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="box-details-key">Clan</div>
|
||||||
|
<div class="box-details-value">[no clan]</div>
|
||||||
|
{{/if}}
|
||||||
|
<div class="box-details-key">Friends</div>
|
||||||
|
<div class="box-details-value">
|
||||||
|
{{form.social.friends}}
|
||||||
|
{{#if (ne form.social.friends form.social.friends_of)}}
|
||||||
|
= {{form.social.friends_of}}[!]
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<div class="box-details-key">Chats</div>
|
||||||
|
<div class="box-details-value">
|
||||||
|
{{#each form.social.chats}}
|
||||||
|
{{this}}<br/>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="access-box">
|
<div class="access-box">
|
||||||
<h4 class="access-box-title">Federation</h4>
|
<h4 class="access-box-title">Federation</h4>
|
||||||
<div class="box-details">
|
<div class="box-details">
|
||||||
<div class="box-details-key">Enabled</div> <!-- TODO -->
|
<div class="box-details-key">TODO</div> <!-- TODO -->
|
||||||
<div class="box-details-value">{{form.perms.royal}}</div>
|
<div class="box-details-value">TODO</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="#"> <!-- TODO -->
|
<a href="#"> <!-- TODO -->
|
||||||
<div class="access-box-banner">
|
<div class="access-box-banner">
|
||||||
|
|||||||
@@ -61,9 +61,11 @@
|
|||||||
<div class="box-details-key">Domain</div>
|
<div class="box-details-key">Domain</div>
|
||||||
<div class="box-details-value">{{form.server.domain}}</div>
|
<div class="box-details-value">{{form.server.domain}}</div>
|
||||||
<div class="box-details-key">CDN</div>
|
<div class="box-details-key">CDN</div>
|
||||||
<div class="box-details-value">{{form.server.cdn}}</div>
|
<div class="box-details-value"><a class="box-details-link" href="{{form.server.cdn}}">{{form.server.cdn}}</a></div>
|
||||||
<div class="box-details-key">Auth</div>
|
<div class="box-details-key">Auth</div>
|
||||||
<div class="box-details-value">{{form.server.auth}}</div>
|
<div class="box-details-value"><a class="box-details-link" href="{{form.server.auth}}">{{form.server.auth}}</a></div>
|
||||||
|
<div class="box-details-key">Factory</div>
|
||||||
|
<div class="box-details-value"><a class="box-details-link" href="{{form.server.factory}}">{{form.server.factory}}</a></div>
|
||||||
<div class="box-details-key">Client Version</div>
|
<div class="box-details-key">Client Version</div>
|
||||||
<div class="box-details-value">>={{form.server.min_version}}</div>
|
<div class="box-details-value">>={{form.server.min_version}}</div>
|
||||||
<div class="box-details-key">Server Version</div>
|
<div class="box-details-key">Server Version</div>
|
||||||
@@ -73,9 +75,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="access-box">
|
<div class="access-box">
|
||||||
Grid item 2
|
<h4 class="access-box-title">Server Links</h4>
|
||||||
|
<div class="box-details">
|
||||||
|
<div class="box-details-key">Feedback</div>
|
||||||
|
<div class="box-details-value"><a class="box-details-link" href="{{form.links.feedback}}">{{form.links.feedback}}</a></div>
|
||||||
|
<div class="box-details-key">Support</div>
|
||||||
|
<div class="box-details-value"><a class="box-details-link" href="{{form.links.support}}">{{form.links.support}}</a></div>
|
||||||
|
<div class="box-details-key">Wiki</div>
|
||||||
|
<div class="box-details-value"><a class="box-details-link" href="{{form.links.wiki}}">{{form.links.wiki}}</a></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="access-box">
|
<!--<div class="access-box">
|
||||||
Grid item 3
|
Grid item 3
|
||||||
</div>
|
</div>
|
||||||
<div class="access-box">
|
<div class="access-box">
|
||||||
@@ -92,7 +102,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="access-box">
|
<div class="access-box">
|
||||||
Grid item 8
|
Grid item 8
|
||||||
</div>
|
</div>-->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ a:link { color: darkgrey; }
|
|||||||
a:hover { color: white; }
|
a:hover { color: white; }
|
||||||
a:visited { color: lightgrey; }
|
a:visited { color: lightgrey; }
|
||||||
a:active { color: white; }
|
a:active { color: white; }
|
||||||
|
a.box-details-link { text-decoration-line: underline; }
|
||||||
|
a.box-details-link:link { color: black; }
|
||||||
|
a.box-details-link:hover { color: white; }
|
||||||
|
a.box-details-link:visited { color: black; }
|
||||||
|
a.box-details-link:active { color: white; }
|
||||||
form {
|
form {
|
||||||
padding-top: 25px;
|
padding-top: 25px;
|
||||||
padding-left: 25px;
|
padding-left: 25px;
|
||||||
|
|||||||
@@ -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>> {
|
async fn upload(&self, vehicle: oj_rc_factory::VehicleUploadInfo) -> Result<oj_rc_factory::VehicleThumbnailInfo, Box<dyn std::error::Error>> {
|
||||||
match self {
|
match self {
|
||||||
Self::Arc(x) => x.upload(vehicle).await,
|
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(),
|
cdn_url: self.settings.server.cdn_url.trim_end_matches('/').to_owned(),
|
||||||
auth_url: self.settings.server.auth_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(),
|
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,
|
minimum_version: self.settings.server.min_version as i32,
|
||||||
dos_protect: self.settings.server.dos_protection,
|
dos_protect: self.settings.server.dos_protection,
|
||||||
maintenance_message: self.settings.server.maintenance_message.clone(),
|
maintenance_message: self.settings.server.maintenance_message.clone(),
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ pub struct ServerConfig {
|
|||||||
pub cdn_url: String,
|
pub cdn_url: String,
|
||||||
pub auth_url: String,
|
pub auth_url: String,
|
||||||
pub intercom_url: String,
|
pub intercom_url: String,
|
||||||
|
pub factory_url: String,
|
||||||
pub minimum_version: i32,
|
pub minimum_version: i32,
|
||||||
pub dos_protect: bool,
|
pub dos_protect: bool,
|
||||||
pub maintenance_message: Option<String>,
|
pub maintenance_message: Option<String>,
|
||||||
|
|||||||
@@ -96,6 +96,8 @@ pub struct ServerSettings {
|
|||||||
pub auth_url: String,
|
pub auth_url: String,
|
||||||
#[serde(default = "default_intercom_root_url")]
|
#[serde(default = "default_intercom_root_url")]
|
||||||
pub intercom_url: String,
|
pub intercom_url: String,
|
||||||
|
#[serde(default = "default_factory_url")]
|
||||||
|
pub factory_url: String,
|
||||||
#[serde(default = "default_feedback_url")]
|
#[serde(default = "default_feedback_url")]
|
||||||
pub feedback_url: String,
|
pub feedback_url: String,
|
||||||
#[serde(default = "default_support_url")]
|
#[serde(default = "default_support_url")]
|
||||||
@@ -132,6 +134,7 @@ fn default_server_conf() -> ServerSettings {
|
|||||||
cdn_url: default_cdn_root_url(),
|
cdn_url: default_cdn_root_url(),
|
||||||
auth_url: default_auth_root_url(),
|
auth_url: default_auth_root_url(),
|
||||||
intercom_url: default_intercom_root_url(),
|
intercom_url: default_intercom_root_url(),
|
||||||
|
factory_url: default_factory_url(),
|
||||||
feedback_url: default_feedback_url(),
|
feedback_url: default_feedback_url(),
|
||||||
support_url: default_support_url(),
|
support_url: default_support_url(),
|
||||||
wiki_url: default_wiki_url(),
|
wiki_url: default_wiki_url(),
|
||||||
@@ -157,6 +160,10 @@ fn default_intercom_root_url() -> String {
|
|||||||
"ws://127.0.0.1:8001".to_owned()
|
"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 {
|
fn default_feedback_url() -> String {
|
||||||
"https://mstdn.ca/@ngram".to_owned()
|
"https://mstdn.ca/@ngram".to_owned()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ mod inventory;
|
|||||||
pub use inventory::{UnlockedParts, UnlockOverride};
|
pub use inventory::{UnlockedParts, UnlockOverride};
|
||||||
|
|
||||||
mod traits;
|
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 mod intercom;
|
||||||
pub use intercom::generate_token as generate_intercom_token;
|
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>;
|
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 {
|
pub enum CurrencyType {
|
||||||
Free,
|
Free,
|
||||||
Paid,
|
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 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 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_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 {
|
pub struct GarageWebInfo {
|
||||||
@@ -691,6 +695,41 @@ pub struct GarageWebInfo {
|
|||||||
pub creation_time: i64,
|
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]
|
#[async_trait::async_trait]
|
||||||
pub trait Userless: Send + Sync {
|
pub trait Userless: Send + Sync {
|
||||||
async fn lobby_state_listener(&self) -> Result<super::IntercomListener<super::intercom::IntercomLobbyStateMessage>, reqwest_websocket::Error>;
|
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?
|
Ok(self.db.garage_selected(self.account.id).await?
|
||||||
.map(|x| x.id))
|
.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,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ impl FactoryDatabase {
|
|||||||
.and_then(|(vehicle, user_opt)| user_opt.map(|user| (vehicle, user))))
|
.and_then(|(vehicle, user_opt)| user_opt.map(|user| (vehicle, user))))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_query(&self, query: libfj::robocraft::ListQuery) -> Result<Vec<(crate::schema::factory::vehicle_query::VehicleInfo, crate::schema::factory::vehicle_query::VehicleOwnerInfo)>, sea_orm::DbErr> {
|
fn build_list_query(&self, query: &libfj::robocraft::ListQuery) -> sea_orm::Selector<sea_orm::SelectTwoModel<crate::schema::factory::vehicle_query::VehicleInfo, crate::schema::factory::vehicle_query::VehicleOwnerInfo>> {
|
||||||
let mut query_builder = crate::schema::factory::vehicle::Entity::find()
|
let mut query_builder = crate::schema::factory::vehicle::Entity::find()
|
||||||
.filter(crate::schema::factory::vehicle::Column::RemovedDate.is_null())
|
.filter(crate::schema::factory::vehicle::Column::RemovedDate.is_null())
|
||||||
.filter(crate::schema::factory::vehicle::Column::BanDate.is_null())
|
.filter(crate::schema::factory::vehicle::Column::BanDate.is_null())
|
||||||
@@ -91,7 +91,7 @@ impl FactoryDatabase {
|
|||||||
query_builder = query_builder.filter(crate::schema::factory::vehicle::Column::Cpu.lte(query.maximum_cpu as u32));
|
query_builder = query_builder.filter(crate::schema::factory::vehicle::Column::Cpu.lte(query.maximum_cpu as u32));
|
||||||
}
|
}
|
||||||
|
|
||||||
let query_builder = query_builder
|
query_builder
|
||||||
.find_also_related(crate::schema::user::Entity)
|
.find_also_related(crate::schema::user::Entity)
|
||||||
.select_only()
|
.select_only()
|
||||||
// WARNING: jank sea_orm query generation (fix)
|
// WARNING: jank sea_orm query generation (fix)
|
||||||
@@ -115,8 +115,11 @@ impl FactoryDatabase {
|
|||||||
.column_as(crate::schema::factory::vehicle::Column::CubeAmounts, "A_cube_amounts")
|
.column_as(crate::schema::factory::vehicle::Column::CubeAmounts, "A_cube_amounts")
|
||||||
.column_as(crate::schema::user::Column::PublicId, "B_public_id")
|
.column_as(crate::schema::user::Column::PublicId, "B_public_id")
|
||||||
.column_as(crate::schema::user::Column::DisplayName, "B_display_name")
|
.column_as(crate::schema::user::Column::DisplayName, "B_display_name")
|
||||||
.into_model::<crate::schema::factory::vehicle_query::VehicleInfo, crate::schema::factory::vehicle_query::VehicleOwnerInfo>();
|
.into_model::<crate::schema::factory::vehicle_query::VehicleInfo, crate::schema::factory::vehicle_query::VehicleOwnerInfo>()
|
||||||
//.into_partial_model::<crate::schema::factory::vehicle_query::VehicleInfo, crate::schema::factory::vehicle_query::VehicleOwnerInfo>();
|
}
|
||||||
|
|
||||||
|
async fn list_query(&self, query: libfj::robocraft::ListQuery) -> Result<Vec<(crate::schema::factory::vehicle_query::VehicleInfo, crate::schema::factory::vehicle_query::VehicleOwnerInfo)>, sea_orm::DbErr> {
|
||||||
|
let query_builder = self.build_list_query(&query);
|
||||||
let pagination = query_builder.paginate(self.orm.as_ref(), query.page_size as u64);
|
let pagination = query_builder.paginate(self.orm.as_ref(), query.page_size as u64);
|
||||||
Ok(pagination.fetch_page((query.page - 1) as u64).await?
|
Ok(pagination.fetch_page((query.page - 1) as u64).await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -124,6 +127,11 @@ impl FactoryDatabase {
|
|||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn count_list_query(&self, query: libfj::robocraft::ListQuery) -> Result<u64, sea_orm::DbErr> {
|
||||||
|
let query_builder = self.build_list_query(&query);
|
||||||
|
query_builder.count(self.orm.as_ref()).await
|
||||||
|
}
|
||||||
|
|
||||||
fn thumbnail_url(&self, id: i32) -> String {
|
fn thumbnail_url(&self, id: i32) -> String {
|
||||||
format!("{}/roboshop/Live/{}", self.cdn, id)
|
format!("{}/roboshop/Live/{}", self.cdn, id)
|
||||||
}
|
}
|
||||||
@@ -207,6 +215,11 @@ impl VehicleFactoryAdapter for FactoryDatabase {
|
|||||||
}).collect())
|
}).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn count(&self, query: libfj::robocraft::ListQuery) -> Result<usize, Box<dyn std::error::Error>> {
|
||||||
|
let count = self.count_list_query(query).await?;
|
||||||
|
Ok(count as usize)
|
||||||
|
}
|
||||||
|
|
||||||
async fn upload(&self, vehicle: VehicleUploadInfo) -> Result<VehicleThumbnailInfo, Box<dyn std::error::Error>> {
|
async fn upload(&self, vehicle: VehicleUploadInfo) -> Result<VehicleThumbnailInfo, Box<dyn std::error::Error>> {
|
||||||
let now = chrono::Utc::now();
|
let now = chrono::Utc::now();
|
||||||
let entity = crate::schema::factory::vehicle::ActiveModel {
|
let entity = crate::schema::factory::vehicle::ActiveModel {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use sea_orm_migration::MigratorTrait;
|
use sea_orm_migration::MigratorTrait;
|
||||||
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect, TransactionTrait, sea_query::ExprTrait};
|
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect, TransactionTrait, sea_query::ExprTrait, sqlx::types::chrono};
|
||||||
|
|
||||||
pub struct Database {
|
pub struct Database {
|
||||||
orm: std::sync::Arc<sea_orm::DatabaseConnection>,
|
orm: std::sync::Arc<sea_orm::DatabaseConnection>,
|
||||||
@@ -224,6 +224,32 @@ impl Database {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn garage_count_by_user_id_between(&self, user_id: i32, max_cpu: u64, min_cpu: u64) -> Result<u64, sea_orm::DbErr> {
|
||||||
|
crate::schema::garage::Entity::find()
|
||||||
|
.filter(crate::schema::garage::Column::UserId.eq(user_id))
|
||||||
|
.filter(crate::schema::garage::Column::TotalRobotCpu.lte(max_cpu))
|
||||||
|
.filter(crate::schema::garage::Column::TotalRobotCpu.gte(min_cpu))
|
||||||
|
.count(self.orm.as_ref())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn garage_count_by_user_id_factory(&self, user_id: i32) -> Result<u64, sea_orm::DbErr> {
|
||||||
|
crate::schema::garage::Entity::find()
|
||||||
|
.filter(crate::schema::garage::Column::UserId.eq(user_id))
|
||||||
|
.filter(crate::schema::garage::Column::CrfId.is_not_null())
|
||||||
|
.count(self.orm.as_ref())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn garage_storage_by_user_id(&self, _user_id: i32) -> Result<Option<u64>, sea_orm::DbErr> {
|
||||||
|
let size = match self.orm.as_ref() {
|
||||||
|
// TODO implement support in other databases
|
||||||
|
//sea_orm::DatabaseConnection::SqlxPostgresPoolConnection(_) => None,
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
Ok(size)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn garage_max_slot_by_user_id(&self, user_id: i32) -> Result<i32, sea_orm::DbErr> {
|
pub async fn garage_max_slot_by_user_id(&self, user_id: i32) -> Result<i32, sea_orm::DbErr> {
|
||||||
let result = crate::schema::garage::Entity::find()
|
let result = crate::schema::garage::Entity::find()
|
||||||
.select_only()
|
.select_only()
|
||||||
@@ -373,6 +399,32 @@ impl Database {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn count_sanctions_by_user_id(&self, user_id: i32) -> Result<u64, sea_orm::DbErr> {
|
||||||
|
crate::schema::sanction::Entity::find()
|
||||||
|
.filter(crate::schema::sanction::Column::UserId.eq(user_id))
|
||||||
|
//.order_by_asc(crate::schema::sanction::Column::CreationTime)
|
||||||
|
.count(self.orm.as_ref())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn count_sanctions_by_user_id_and_ack(&self, user_id: i32, is_acked: bool) -> Result<u64, sea_orm::DbErr> {
|
||||||
|
crate::schema::sanction::Entity::find()
|
||||||
|
.filter(crate::schema::sanction::Column::UserId.eq(user_id))
|
||||||
|
.filter(crate::schema::sanction::Column::Acknowledged.eq(is_acked))
|
||||||
|
//.order_by_asc(crate::schema::sanction::Column::CreationTime)
|
||||||
|
.count(self.orm.as_ref())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn count_sanctions_by_user_id_and_descriptor(&self, user_id: i32, desc: crate::schema::sanction::Descriptor) -> Result<u64, sea_orm::DbErr> {
|
||||||
|
crate::schema::sanction::Entity::find()
|
||||||
|
.filter(crate::schema::sanction::Column::UserId.eq(user_id))
|
||||||
|
.filter(crate::schema::sanction::Column::Descriptor.eq(desc))
|
||||||
|
//.order_by_asc(crate::schema::sanction::Column::CreationTime)
|
||||||
|
.count(self.orm.as_ref())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn sanctions_by_user_id(&self, user_id: i32) -> Result<Vec<crate::schema::sanction::Model>, sea_orm::DbErr> {
|
pub async fn sanctions_by_user_id(&self, user_id: i32) -> Result<Vec<crate::schema::sanction::Model>, sea_orm::DbErr> {
|
||||||
crate::schema::sanction::Entity::find()
|
crate::schema::sanction::Entity::find()
|
||||||
.filter(crate::schema::sanction::Column::UserId.eq(user_id))
|
.filter(crate::schema::sanction::Column::UserId.eq(user_id))
|
||||||
@@ -382,6 +434,19 @@ impl Database {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn sanction_by_user_id_and_descriptor_and_active(&self, user_id: i32, desc: crate::schema::sanction::Descriptor) -> Result<Option<crate::schema::sanction::Model>, sea_orm::DbErr> {
|
||||||
|
let now = chrono::Utc::now().timestamp();
|
||||||
|
let expiry_expr = sea_orm::sea_query::Expr::col(crate::schema::sanction::Column::CreationTime).add(sea_orm::sea_query::Expr::col(crate::schema::sanction::Column::Duration));
|
||||||
|
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::Duration.is_not_null())
|
||||||
|
.filter(expiry_expr.clone().gte(now))
|
||||||
|
.order_by_asc(crate::schema::sanction::Column::CreationTime)
|
||||||
|
.one(self.orm.as_ref())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn insert_sanction(&self, entity: crate::schema::sanction::ActiveModel) -> Result<crate::schema::sanction::Model, sea_orm::DbErr> {
|
pub async fn insert_sanction(&self, entity: crate::schema::sanction::ActiveModel) -> Result<crate::schema::sanction::Model, sea_orm::DbErr> {
|
||||||
entity.insert(self.orm.as_ref()).await
|
entity.insert(self.orm.as_ref()).await
|
||||||
}
|
}
|
||||||
@@ -400,6 +465,13 @@ impl Database {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn count_games_by_user_id(&self, user_id: i32) -> Result<u64, sea_orm::DbErr> {
|
||||||
|
crate::schema::multiplayer_game_player::Entity::find()
|
||||||
|
.filter(crate::schema::multiplayer_game_player::Column::UserId.eq(user_id))
|
||||||
|
.count(self.orm.as_ref())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn game_by_user_id_and_completion(&self, user_id: i32, is_complete: bool) -> Result<Option<crate::schema::multiplayer_game::Model>, sea_orm::DbErr> {
|
pub async fn game_by_user_id_and_completion(&self, user_id: i32, is_complete: bool) -> Result<Option<crate::schema::multiplayer_game::Model>, sea_orm::DbErr> {
|
||||||
Ok(crate::schema::multiplayer_game::Entity::find()
|
Ok(crate::schema::multiplayer_game::Entity::find()
|
||||||
.find_also_related(crate::schema::multiplayer_game_player::Entity)
|
.find_also_related(crate::schema::multiplayer_game_player::Entity)
|
||||||
@@ -610,6 +682,24 @@ impl Database {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn count_friends_by_user_id(&self, user_id: i32, status_in: crate::schema::friend::FriendStatus) -> Result<u64, sea_orm::DbErr> {
|
||||||
|
crate::schema::friend::Entity::find()
|
||||||
|
.filter(crate::schema::friend::Column::FriendSource.eq(user_id))
|
||||||
|
.filter(crate::schema::friend::Column::State.eq(status_in))
|
||||||
|
//.order_by_asc(crate::schema::friend::Column::CreationTime)
|
||||||
|
.count(self.orm.as_ref())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn count_friends_target_by_user_id(&self, user_id: i32, status_in: crate::schema::friend::FriendStatus) -> Result<u64, sea_orm::DbErr> {
|
||||||
|
crate::schema::friend::Entity::find()
|
||||||
|
.filter(crate::schema::friend::Column::FriendTarget.eq(user_id))
|
||||||
|
.filter(crate::schema::friend::Column::State.eq(status_in))
|
||||||
|
//.order_by_asc(crate::schema::friend::Column::CreationTime)
|
||||||
|
.count(self.orm.as_ref())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn friends_by_user_id(&self, user_id: i32, status_not_in: impl IntoIterator<Item=crate::schema::friend::FriendStatus>) -> Result<Vec<(crate::schema::friend::Model, crate::schema::user::Model)>, sea_orm::DbErr> {
|
pub async fn friends_by_user_id(&self, user_id: i32, status_not_in: impl IntoIterator<Item=crate::schema::friend::FriendStatus>) -> Result<Vec<(crate::schema::friend::Model, crate::schema::user::Model)>, sea_orm::DbErr> {
|
||||||
Ok(crate::schema::friend::Entity::find()
|
Ok(crate::schema::friend::Entity::find()
|
||||||
.find_also_related(crate::schema::user::Entity)
|
.find_also_related(crate::schema::user::Entity)
|
||||||
|
|||||||
@@ -66,6 +66,79 @@ impl ArcAdapter {
|
|||||||
|
|
||||||
serde_json::to_string(&str_map).unwrap_or_else(|_| "{}".to_string())
|
serde_json::to_string(&str_map).unwrap_or_else(|_| "{}".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn listquery_to_db(&self, query: &libfj::robocraft::ListQuery) -> sea_orm::Select<super::entities::robot_metadata::Entity> {
|
||||||
|
let mut query_builder = super::entities::robot_metadata::Entity::find();
|
||||||
|
match query.order {
|
||||||
|
libfj::robocraft::FactoryOrderType::Suggested => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::RentCount); },
|
||||||
|
libfj::robocraft::FactoryOrderType::CombatRating => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::CombatRating); },
|
||||||
|
libfj::robocraft::FactoryOrderType::CosmeticRating => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::CosmeticRating); },
|
||||||
|
libfj::robocraft::FactoryOrderType::Added => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::AddedDate); },
|
||||||
|
libfj::robocraft::FactoryOrderType::CPU => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::Cpu); },
|
||||||
|
libfj::robocraft::FactoryOrderType::MostBought => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::BuyCount); },
|
||||||
|
}
|
||||||
|
if !query.text_filter.is_empty() {
|
||||||
|
let query_text = format!("%{}%", query.text_filter.replace('%', ""));
|
||||||
|
if query.player_filter {
|
||||||
|
query_builder = query_builder.filter(
|
||||||
|
sea_orm::sea_query::Condition::any()
|
||||||
|
.add(super::entities::robot_metadata::Column::AddedBy.like(query_text.clone()))
|
||||||
|
.add(super::entities::robot_metadata::Column::AddedByDisplayName.like(query_text))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
match query.text_search_field {
|
||||||
|
libfj::robocraft::FactoryTextSearchField::All => {
|
||||||
|
query_builder = query_builder.filter(
|
||||||
|
sea_orm::sea_query::Condition::any()
|
||||||
|
.add(super::entities::robot_metadata::Column::AddedBy.like(query_text.clone()))
|
||||||
|
.add(super::entities::robot_metadata::Column::AddedByDisplayName.like(query_text.clone()))
|
||||||
|
.add(super::entities::robot_metadata::Column::Name.like(query_text.clone()))
|
||||||
|
.add(super::entities::robot_metadata::Column::Description.like(query_text))
|
||||||
|
);
|
||||||
|
},
|
||||||
|
libfj::robocraft::FactoryTextSearchField::Name => {
|
||||||
|
query_builder = query_builder.filter(
|
||||||
|
sea_orm::sea_query::Condition::any()
|
||||||
|
.add(super::entities::robot_metadata::Column::Name.like(query_text.clone()))
|
||||||
|
);
|
||||||
|
},
|
||||||
|
libfj::robocraft::FactoryTextSearchField::Player => {
|
||||||
|
query_builder = query_builder.filter(
|
||||||
|
sea_orm::sea_query::Condition::any()
|
||||||
|
.add(super::entities::robot_metadata::Column::AddedBy.like(query_text.clone()))
|
||||||
|
.add(super::entities::robot_metadata::Column::AddedByDisplayName.like(query_text))
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.buyable {
|
||||||
|
query_builder = query_builder.filter(
|
||||||
|
sea_orm::sea_query::Condition::any()
|
||||||
|
.add(super::entities::robot_metadata::Column::AddedDate.lt("2025-01-23"))
|
||||||
|
.add(super::entities::robot_metadata::Column::Buyable.eq(1))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if query.featured_only {
|
||||||
|
query_builder = query_builder.filter(super::entities::robot_metadata::Column::Featured.eq(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// movement filters not supported
|
||||||
|
// weapon filters not supported
|
||||||
|
if query.minimum_cpu > 0 {
|
||||||
|
query_builder = query_builder.filter(super::entities::robot_metadata::Column::Cpu.gte(query.minimum_cpu as u32));
|
||||||
|
}
|
||||||
|
if query.maximum_cpu < usize::MAX {
|
||||||
|
query_builder = query_builder.filter(super::entities::robot_metadata::Column::Cpu.lte(query.maximum_cpu as u32));
|
||||||
|
}
|
||||||
|
if !self.ignore_expiry {
|
||||||
|
let now_str = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true).trim_end_matches('Z').to_owned();
|
||||||
|
log::debug!("Expiry must exceed `{}`", now_str);
|
||||||
|
query_builder = query_builder.filter(super::entities::robot_metadata::Column::ExpiryDate.gte(now_str))
|
||||||
|
}
|
||||||
|
query_builder
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
@@ -115,76 +188,7 @@ impl crate::VehicleFactoryAdapter for ArcAdapter {
|
|||||||
|
|
||||||
async fn list(&self, query: libfj::robocraft::ListQuery) -> Result<Vec<crate::VehicleQueryInfo>, Box<dyn std::error::Error>> {
|
async fn list(&self, query: libfj::robocraft::ListQuery) -> Result<Vec<crate::VehicleQueryInfo>, Box<dyn std::error::Error>> {
|
||||||
log::debug!("Search vehicles with query {:?}", query);
|
log::debug!("Search vehicles with query {:?}", query);
|
||||||
let mut query_builder = super::entities::robot_metadata::Entity::find();
|
let query_params = self.listquery_to_db(&query);
|
||||||
match query.order {
|
|
||||||
libfj::robocraft::FactoryOrderType::Suggested => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::RentCount); },
|
|
||||||
libfj::robocraft::FactoryOrderType::CombatRating => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::CombatRating); },
|
|
||||||
libfj::robocraft::FactoryOrderType::CosmeticRating => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::CosmeticRating); },
|
|
||||||
libfj::robocraft::FactoryOrderType::Added => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::AddedDate); },
|
|
||||||
libfj::robocraft::FactoryOrderType::CPU => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::Cpu); },
|
|
||||||
libfj::robocraft::FactoryOrderType::MostBought => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::BuyCount); },
|
|
||||||
}
|
|
||||||
if !query.text_filter.is_empty() {
|
|
||||||
let query_text = format!("%{}%", query.text_filter.replace('%', ""));
|
|
||||||
if query.player_filter {
|
|
||||||
query_builder = query_builder.filter(
|
|
||||||
sea_orm::sea_query::Condition::any()
|
|
||||||
.add(super::entities::robot_metadata::Column::AddedBy.like(query_text.clone()))
|
|
||||||
.add(super::entities::robot_metadata::Column::AddedByDisplayName.like(query_text))
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
match query.text_search_field {
|
|
||||||
libfj::robocraft::FactoryTextSearchField::All => {
|
|
||||||
query_builder = query_builder.filter(
|
|
||||||
sea_orm::sea_query::Condition::any()
|
|
||||||
.add(super::entities::robot_metadata::Column::AddedBy.like(query_text.clone()))
|
|
||||||
.add(super::entities::robot_metadata::Column::AddedByDisplayName.like(query_text.clone()))
|
|
||||||
.add(super::entities::robot_metadata::Column::Name.like(query_text.clone()))
|
|
||||||
.add(super::entities::robot_metadata::Column::Description.like(query_text))
|
|
||||||
);
|
|
||||||
},
|
|
||||||
libfj::robocraft::FactoryTextSearchField::Name => {
|
|
||||||
query_builder = query_builder.filter(
|
|
||||||
sea_orm::sea_query::Condition::any()
|
|
||||||
.add(super::entities::robot_metadata::Column::Name.like(query_text.clone()))
|
|
||||||
);
|
|
||||||
},
|
|
||||||
libfj::robocraft::FactoryTextSearchField::Player => {
|
|
||||||
query_builder = query_builder.filter(
|
|
||||||
sea_orm::sea_query::Condition::any()
|
|
||||||
.add(super::entities::robot_metadata::Column::AddedBy.like(query_text.clone()))
|
|
||||||
.add(super::entities::robot_metadata::Column::AddedByDisplayName.like(query_text))
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if query.buyable {
|
|
||||||
query_builder = query_builder.filter(
|
|
||||||
sea_orm::sea_query::Condition::any()
|
|
||||||
.add(super::entities::robot_metadata::Column::AddedDate.lt("2025-01-23"))
|
|
||||||
.add(super::entities::robot_metadata::Column::Buyable.eq(1))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if query.featured_only {
|
|
||||||
query_builder = query_builder.filter(super::entities::robot_metadata::Column::Featured.eq(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
// movement filters not supported
|
|
||||||
// weapon filters not supported
|
|
||||||
if query.minimum_cpu > 0 {
|
|
||||||
query_builder = query_builder.filter(super::entities::robot_metadata::Column::Cpu.gte(query.minimum_cpu as u32));
|
|
||||||
}
|
|
||||||
if query.maximum_cpu < usize::MAX {
|
|
||||||
query_builder = query_builder.filter(super::entities::robot_metadata::Column::Cpu.lte(query.maximum_cpu as u32));
|
|
||||||
}
|
|
||||||
if !self.ignore_expiry {
|
|
||||||
let now_str = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true).trim_end_matches('Z').to_owned();
|
|
||||||
log::debug!("Expiry must exceed `{}`", now_str);
|
|
||||||
query_builder = query_builder.filter(super::entities::robot_metadata::Column::ExpiryDate.gte(now_str))
|
|
||||||
}
|
|
||||||
let query_params = query_builder;
|
|
||||||
|
|
||||||
// FIXME add support for query.prepend_featured_bot
|
// FIXME add support for query.prepend_featured_bot
|
||||||
let metadata_pages = query_params.paginate(&self.orm, query.page_size as u64);
|
let metadata_pages = query_params.paginate(&self.orm, query.page_size as u64);
|
||||||
@@ -230,6 +234,14 @@ impl crate::VehicleFactoryAdapter for ArcAdapter {
|
|||||||
Ok(infos)
|
Ok(infos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn count(&self, query: libfj::robocraft::ListQuery) -> Result<usize, Box<dyn std::error::Error>> {
|
||||||
|
log::debug!("Count vehicles with query {:?}", query);
|
||||||
|
let query_params = self.listquery_to_db(&query);
|
||||||
|
query_params.count(&self.orm).await
|
||||||
|
.map(|len| len as usize)
|
||||||
|
.map_err(|e| Box::new(e) as _)
|
||||||
|
}
|
||||||
|
|
||||||
async fn upload(&self, vehicle: crate::VehicleUploadInfo) -> Result<crate::VehicleThumbnailInfo, Box<dyn std::error::Error>> {
|
async fn upload(&self, vehicle: crate::VehicleUploadInfo) -> Result<crate::VehicleThumbnailInfo, Box<dyn std::error::Error>> {
|
||||||
if self.is_readonly {
|
if self.is_readonly {
|
||||||
return Ok(crate::VehicleThumbnailInfo {
|
return Ok(crate::VehicleThumbnailInfo {
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
pub trait VehicleFactoryAdapter: Send + Sync + 'static {
|
pub trait VehicleFactoryAdapter: Send + Sync + 'static {
|
||||||
async fn vehicle(&self, id: i32) -> Result<Option<(VehicleInfo, VehicleQueryInfo)>, Box<dyn std::error::Error>>;
|
async fn vehicle(&self, id: i32) -> Result<Option<(VehicleInfo, VehicleQueryInfo)>, Box<dyn std::error::Error>>;
|
||||||
async fn list(&self, query: libfj::robocraft::ListQuery) -> Result<Vec<VehicleQueryInfo>, Box<dyn std::error::Error>>;
|
async fn list(&self, query: libfj::robocraft::ListQuery) -> Result<Vec<VehicleQueryInfo>, Box<dyn std::error::Error>>;
|
||||||
|
// used by society web dashboard
|
||||||
|
async fn count(&self, query: libfj::robocraft::ListQuery) -> Result<usize, Box<dyn std::error::Error>> {
|
||||||
|
Ok(self.list(query).await?.len())
|
||||||
|
}
|
||||||
|
|
||||||
async fn upload(&self, vehicle: VehicleUploadInfo) -> Result<VehicleThumbnailInfo, Box<dyn std::error::Error>>;
|
async fn upload(&self, vehicle: VehicleUploadInfo) -> Result<VehicleThumbnailInfo, Box<dyn std::error::Error>>;
|
||||||
async fn rate_vehicle(&self, id: i32, combat: i32, cosmetic: i32) -> Result<(), Box<dyn std::error::Error>>;
|
async fn rate_vehicle(&self, id: i32, combat: i32, cosmetic: i32) -> Result<(), Box<dyn std::error::Error>>;
|
||||||
/// Just update any purchase trackers
|
/// Just update any purchase trackers
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ clap.workspace = true
|
|||||||
handlebars = { version = "6", features = ["dir_source"] }
|
handlebars = { version = "6", features = ["dir_source"] }
|
||||||
oj_rc_core = { version = "*", path = "../rc_core" }
|
oj_rc_core = { version = "*", path = "../rc_core" }
|
||||||
oj_rc_plugins = { version = "*", path = "../rc_plugins" }
|
oj_rc_plugins = { version = "*", path = "../rc_plugins" }
|
||||||
|
oj_rc_factory = { version = "*", path = "../rc_factory" }
|
||||||
oj_convert.workspace = true
|
oj_convert.workspace = true
|
||||||
libfj.workspace = true
|
libfj.workspace = true
|
||||||
git-version.workspace = true
|
git-version.workspace = true
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
let config = oj_rc_core::ConfigImpl::load(&cli_args.assets_robocraft)?;
|
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_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);
|
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 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 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 auth_ref = actix_web::web::Data::new(Box::new(users));
|
||||||
|
|
||||||
let cli_args2 = actix_web::web::Data::new(cli_args.clone());
|
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(loadeds_args.clone())
|
||||||
.app_data(handlebars_ref.clone())
|
.app_data(handlebars_ref.clone())
|
||||||
.app_data(server_settings.clone())
|
.app_data(server_settings.clone())
|
||||||
|
.app_data(server_links.clone())
|
||||||
.app_data(auth_ref.clone())
|
.app_data(auth_ref.clone())
|
||||||
.app_data(importers_ref.clone())
|
.app_data(importers_ref.clone())
|
||||||
.app_data(parsers_ref.clone())
|
.app_data(parsers_ref.clone())
|
||||||
|
.app_data(factory_ref.clone())
|
||||||
.service(version_info)
|
.service(version_info)
|
||||||
.service(web::login::form_submit)
|
.service(web::login::form_submit)
|
||||||
.service(web::login::form_load)
|
.service(web::login::form_load)
|
||||||
|
|||||||
@@ -6,11 +6,15 @@ const FORM_NAME: &str = "dashboard";
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct RenderData {
|
struct RenderData {
|
||||||
// TODO
|
|
||||||
display_name: String,
|
display_name: String,
|
||||||
public_id: String,
|
public_id: String,
|
||||||
debug: DebugData,
|
debug: DebugData,
|
||||||
perms: PermissionData,
|
perms: PermissionData,
|
||||||
|
garage: GarageData,
|
||||||
|
factory: FactoryData,
|
||||||
|
account: AccountData,
|
||||||
|
sanction: SanctionData,
|
||||||
|
social: SocialData,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -18,6 +22,8 @@ struct DebugData {
|
|||||||
user_id: i32,
|
user_id: i32,
|
||||||
creation_time_unix: i64,
|
creation_time_unix: i64,
|
||||||
creation_time_iso: String,
|
creation_time_iso: String,
|
||||||
|
cdn: String,
|
||||||
|
factory: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -29,7 +35,59 @@ struct PermissionData {
|
|||||||
banned: bool,
|
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? {
|
match super::try_auth_user(user_opt, auth.as_ref(), &req).await? {
|
||||||
super::LoginReturn::AuthFail(resp) => Ok(resp),
|
super::LoginReturn::AuthFail(resp) => Ok(resp),
|
||||||
super::LoginReturn::Success(user) => {
|
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");
|
log::debug!("Rendering user's dashboard");
|
||||||
let creation_time = user.creation();
|
let creation_time = user.creation();
|
||||||
let creation_time_chrono = chrono::DateTime::<chrono::Utc>::from_timestamp_secs(creation_time).unwrap_or_default();
|
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(
|
Ok(super::render_ok(
|
||||||
RenderData {
|
RenderData {
|
||||||
display_name: user.display_name().to_owned(),
|
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(),
|
user_id: user.account_id(),
|
||||||
creation_time_unix: creation_time,
|
creation_time_unix: creation_time,
|
||||||
creation_time_iso: creation_time_chrono.to_rfc3339(),
|
creation_time_iso: creation_time_chrono.to_rfc3339(),
|
||||||
|
cdn: server_config.cdn_url.clone(),
|
||||||
|
factory: server_config.factory_url.clone(),
|
||||||
},
|
},
|
||||||
perms: PermissionData {
|
perms: build_perm_data(user.as_ref()),
|
||||||
r#mod: user.is_mod(),
|
garage: garage_stats,
|
||||||
admin: user.is_admin(),
|
factory: factory_stats,
|
||||||
dev: user.is_dev(),
|
account: account_stats,
|
||||||
royal: user.is_royal(),
|
sanction: sanction_stats,
|
||||||
banned: user.is_banned(),
|
social: social_stats,
|
||||||
}
|
|
||||||
},
|
},
|
||||||
handlebars_ref.as_ref(),
|
handlebars_ref.as_ref(),
|
||||||
FORM_NAME,
|
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")]
|
#[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> {
|
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, user_opt, req).await
|
dashboard_impl(handlebars_ref, auth, factory, server_config, user_opt, req).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/dashboard")]
|
#[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> {
|
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, user_opt, req).await
|
dashboard_impl(handlebars_ref, auth, factory, server_config, user_opt, req).await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ struct RenderData {
|
|||||||
is_logged_in: bool,
|
is_logged_in: bool,
|
||||||
display_name: Option<String>,
|
display_name: Option<String>,
|
||||||
server: ServerDetails,
|
server: ServerDetails,
|
||||||
|
links: LinkDetails,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -16,6 +17,7 @@ struct ServerDetails {
|
|||||||
domain: String,
|
domain: String,
|
||||||
cdn: String,
|
cdn: String,
|
||||||
auth: String,
|
auth: String,
|
||||||
|
factory: String,
|
||||||
min_version: i32,
|
min_version: i32,
|
||||||
server_version: String,
|
server_version: String,
|
||||||
start_time_iso: String,
|
start_time_iso: String,
|
||||||
@@ -32,6 +34,7 @@ fn server_details(conf: &oj_rc_core::persist::config::ServerConfig) -> ServerDet
|
|||||||
domain: conf.domain.clone(),
|
domain: conf.domain.clone(),
|
||||||
cdn: conf.cdn_url.clone(),
|
cdn: conf.cdn_url.clone(),
|
||||||
auth: conf.auth_url.clone(),
|
auth: conf.auth_url.clone(),
|
||||||
|
factory: conf.factory_url.clone(),
|
||||||
min_version: conf.minimum_version,
|
min_version: conf.minimum_version,
|
||||||
server_version,
|
server_version,
|
||||||
start_time_unix,
|
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("/")]
|
#[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 server_info = server_details(server_config.as_ref());
|
||||||
|
let links_info = links_details(server_links.as_ref());
|
||||||
if let Some(user) = user_opt {
|
if let Some(user) = user_opt {
|
||||||
match super::try_auth_user(Some(user), auth.as_ref(), &req).await? {
|
match super::try_auth_user(Some(user), auth.as_ref(), &req).await? {
|
||||||
super::LoginReturn::AuthFail(resp) => Ok(resp),
|
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,
|
is_logged_in: true,
|
||||||
display_name: Some(user.display_name().to_owned()),
|
display_name: Some(user.display_name().to_owned()),
|
||||||
server: server_info,
|
server: server_info,
|
||||||
|
links: links_info,
|
||||||
},
|
},
|
||||||
handlebars_ref.as_ref(),
|
handlebars_ref.as_ref(),
|
||||||
FORM_NAME,
|
FORM_NAME,
|
||||||
@@ -66,6 +86,7 @@ pub async fn get(handlebars_ref: Data<handlebars::Handlebars<'_>>, auth: Data<Bo
|
|||||||
is_logged_in: false,
|
is_logged_in: false,
|
||||||
display_name: None,
|
display_name: None,
|
||||||
server: server_info,
|
server: server_info,
|
||||||
|
links: links_info,
|
||||||
},
|
},
|
||||||
handlebars_ref.as_ref(),
|
handlebars_ref.as_ref(),
|
||||||
FORM_NAME,
|
FORM_NAME,
|
||||||
|
|||||||
Reference in New Issue
Block a user