mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Create RC database layer
This commit is contained in:
1180
Cargo.lock
generated
1180
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,8 @@ members = [
|
|||||||
"rc_static_data", "rc_microtransactions",
|
"rc_static_data", "rc_microtransactions",
|
||||||
"rc_social", "rc_social_room",
|
"rc_social", "rc_social_room",
|
||||||
"rc_chat", "rc_chat_room",
|
"rc_chat", "rc_chat_room",
|
||||||
"rc_singleplayer", "rc_singleplayer_room", "rc_core",
|
"rc_singleplayer", "rc_singleplayer_room",
|
||||||
|
"rc_core", "rc_database",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
@@ -32,3 +33,4 @@ polariton = { version = "0.2", path = "../polariton", features = [ "tokio-async"
|
|||||||
polariton_server = { version = "0.2", path = "../polariton/server", features = [ "tokio-async" ] }
|
polariton_server = { version = "0.2", path = "../polariton/server", features = [ "tokio-async" ] }
|
||||||
serde = { version = "1.0", features = [ "derive" ] }
|
serde = { version = "1.0", features = [ "derive" ] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
async-trait = "0.1"
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ pub struct CliArgs {
|
|||||||
/// Robocraft user data root
|
/// Robocraft user data root
|
||||||
#[arg(long, default_value_t = {"../data/robocraft".to_string()})]
|
#[arg(long, default_value_t = {"../data/robocraft".to_string()})]
|
||||||
pub data_robocraft: String,
|
pub data_robocraft: String,
|
||||||
|
/// Robocraft asset data root
|
||||||
|
#[arg(long, default_value_t = {"../assets/robocraft".to_string()})]
|
||||||
|
pub assets_robocraft: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CliArgs {
|
impl CliArgs {
|
||||||
@@ -14,10 +17,10 @@ impl CliArgs {
|
|||||||
Self::parse()
|
Self::parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn preloaded(self) -> Config {
|
pub async fn preloaded(self) -> Config {
|
||||||
Config {
|
Config {
|
||||||
#[cfg(feature = "robocraft")]
|
#[cfg(feature = "robocraft")]
|
||||||
robocraft: crate::robocraft::RcConfig::from_args(&self),
|
robocraft: crate::robocraft::RcConfig::from_args(&self).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ fn index() -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[rocket::launch]
|
#[rocket::launch]
|
||||||
fn rocket() -> _ {
|
async fn rocket() -> _ {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
let args = common::cli::CliArgs::get();
|
let args = common::cli::CliArgs::get();
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut builder = rocket::build().mount("/", rocket::routes![index])
|
let mut builder = rocket::build().mount("/", rocket::routes![index])
|
||||||
.manage(args.preloaded());
|
.manage(args.preloaded().await);
|
||||||
|
|
||||||
#[cfg(feature = "cardlife")]
|
#[cfg(feature = "cardlife")]
|
||||||
{builder = builder.attach(cardlife::stage());}
|
{builder = builder.attach(cardlife::stage());}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use rc_core::UserAuthenticator;
|
|||||||
use rocket::{post, routes, serde::json::Json, http::Status, State};
|
use rocket::{post, routes, serde::json::Json, http::Status, State};
|
||||||
|
|
||||||
#[post("/authenticate/robocraft/game", data = "<body>")]
|
#[post("/authenticate/robocraft/game", data = "<body>")]
|
||||||
pub fn email_password_auth(body: Json<libfj::robocraft::EmailUserAuthenticationPayload>, config: &State<crate::common::cli::Config>) -> Result<Json<libfj::robocraft::AuthenticationResponseInfo>, Status> {
|
pub async fn email_password_auth(body: Json<libfj::robocraft::EmailUserAuthenticationPayload>, config: &State<crate::common::cli::Config>) -> Result<Json<libfj::robocraft::AuthenticationResponseInfo>, Status> {
|
||||||
log::info!("Authenticating {} user {}", body.target, body.display_name);
|
log::info!("Authenticating {} user {}", body.target, body.display_name);
|
||||||
let payload = libfj::robocraft::TokenPayload {
|
let payload = libfj::robocraft::TokenPayload {
|
||||||
public_id: body.display_name.clone(),
|
public_id: body.display_name.clone(),
|
||||||
@@ -16,7 +16,7 @@ pub fn email_password_auth(body: Json<libfj::robocraft::EmailUserAuthenticationP
|
|||||||
payload,
|
payload,
|
||||||
extra: rc_core::persist::user::ExtraUserInfo::Standalone { password: body.password.clone() },
|
extra: rc_core::persist::user::ExtraUserInfo::Standalone { password: body.password.clone() },
|
||||||
};
|
};
|
||||||
let response = config.robocraft.account_provider.login(user_info)
|
let response = config.robocraft.account_provider.login(user_info).await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("Failed to authenticate {} user {}: {}", body.target, body.display_name, e);
|
log::error!("Failed to authenticate {} user {}: {}", body.target, body.display_name, e);
|
||||||
Status { code: 401 }
|
Status { code: 401 }
|
||||||
|
|||||||
@@ -18,9 +18,10 @@ pub struct RcConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RcConfig {
|
impl RcConfig {
|
||||||
pub fn from_args(args: &crate::common::cli::CliArgs) -> Self {
|
pub async fn from_args(args: &crate::common::cli::CliArgs) -> Self {
|
||||||
|
let conf = rc_core::persist::config::ConfigImpl::load(&args.assets_robocraft).expect("Bad config data");
|
||||||
Self {
|
Self {
|
||||||
account_provider: rc_core::UserImpl::load_for_auth(&args.data_robocraft).expect("Invalid Robocraft user data"),
|
account_provider: rc_core::UserImpl::load(&args.data_robocraft, &conf).await.expect("Invalid Robocraft user data"),
|
||||||
root: args.data_robocraft.clone().into(),
|
root: args.data_robocraft.clone().into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use rc_core::UserAuthenticator;
|
|||||||
use rocket::{http::Status, post, routes, serde::json::Json, State};
|
use rocket::{http::Status, post, routes, serde::json::Json, State};
|
||||||
|
|
||||||
#[post("/authenticate/steam/game", data = "<body>")]
|
#[post("/authenticate/steam/game", data = "<body>")]
|
||||||
pub fn steam_auth(body: Json<libfj::robocraft::SteamAuthenticationPayload>, config: &State<crate::common::cli::Config>) -> Result<Json<libfj::robocraft::AuthenticationResponseInfo>, Status> {
|
pub async fn steam_auth(body: Json<libfj::robocraft::SteamAuthenticationPayload>, config: &State<crate::common::cli::Config>) -> Result<Json<libfj::robocraft::AuthenticationResponseInfo>, Status> {
|
||||||
let steam_id = crate::common::steam_utils::authenticate_steam_ticket(&body.steam_ticket)
|
let steam_id = crate::common::steam_utils::authenticate_steam_ticket(&body.steam_ticket)
|
||||||
.map_err(|_| Status { code: 401 })?;
|
.map_err(|_| Status { code: 401 })?;
|
||||||
log::info!("Authenticating {} steam user {}", body.target, steam_id);
|
log::info!("Authenticating {} steam user {}", body.target, steam_id);
|
||||||
@@ -18,7 +18,7 @@ pub fn steam_auth(body: Json<libfj::robocraft::SteamAuthenticationPayload>, conf
|
|||||||
payload,
|
payload,
|
||||||
extra: rc_core::persist::user::ExtraUserInfo::Steam { id: steam_id },
|
extra: rc_core::persist::user::ExtraUserInfo::Steam { id: steam_id },
|
||||||
};
|
};
|
||||||
let response = config.robocraft.account_provider.login(user_info)
|
let response = config.robocraft.account_provider.login(user_info).await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("Failed to authenticate {} steam user {}: {}", body.target, steam_id, e);
|
log::error!("Failed to authenticate {} steam user {}: {}", body.target, steam_id, e);
|
||||||
Status { code: 401 }
|
Status { code: 401 }
|
||||||
|
|||||||
@@ -19,3 +19,4 @@ rc_core = { version = "*", path = "../rc_core" }
|
|||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
async-trait.workspace = true
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
log::debug!("Got cli args {:?}", args);
|
log::debug!("Got cli args {:?}", args);
|
||||||
|
|
||||||
let cubes = rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data");
|
let cubes = rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data");
|
||||||
let users = std::sync::Arc::new(rc_core::persist::user::UserImpl::load(&args.data, &cubes).expect("Bad user data"));
|
let users = std::sync::Arc::new(rc_core::persist::user::UserImpl::load(&args.data, &cubes).await.expect("Bad user data"));
|
||||||
|
|
||||||
let chat_system = state::chat::ChatImpl::new(&args.assets, &args.data).expect("Bad chat config data");
|
let chat_system = state::chat::ChatImpl::new(&args.assets, &args.data).expect("Bad chat config data");
|
||||||
|
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ impl MoreLobbyAuth {
|
|||||||
Some(map)
|
Some(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_auth<C>(&self, params: std::collections::HashMap<u8, Typed<C>>, user: &crate::UserTy) -> Result<polariton::operation::ParameterTable<C>, i16> {
|
async fn do_auth<C>(&self, params: std::collections::HashMap<u8, Typed<C>>, user: &crate::UserTy) -> Result<polariton::operation::ParameterTable<C>, i16> {
|
||||||
if let Some(Typed::Str(auth_payload)) = params.get(&Self::AUTH_PAYLOAD_KEY) {
|
if let Some(Typed::Str(auth_payload)) = params.get(&Self::AUTH_PAYLOAD_KEY) {
|
||||||
if user.update_with_auth_ext(&auth_payload.string, |t| self.build_ext_map(t)) {
|
if user.update_with_auth_ext(&auth_payload.string, |t| self.build_ext_map(t)).await {
|
||||||
let user_impl = user.user()?;
|
let user_impl = user.user()?;
|
||||||
let name = user_impl.token().uuid.clone();
|
let name = user_impl.token().uuid.clone();
|
||||||
let chat_user = super::get_chat_user(user_impl.as_ref().as_ref());
|
let chat_user = super::get_chat_user(user_impl.as_ref().as_ref());
|
||||||
@@ -49,12 +49,13 @@ impl MoreLobbyAuth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
impl <C: Send + 'static> Operation<C> for MoreLobbyAuth {
|
impl <C: Send + 'static> Operation<C> for MoreLobbyAuth {
|
||||||
type User = crate::UserTy;
|
type User = crate::UserTy;
|
||||||
|
|
||||||
fn handle(&self, params: polariton::operation::ParameterTable<C>, user: &Self::User) -> polariton::operation::OperationResponse<C> {
|
async fn handle_async(&self, params: polariton::operation::ParameterTable<C>, user: &Self::User) -> polariton::operation::OperationResponse<C> {
|
||||||
let params_dict = params.to_dict();
|
let params_dict = params.to_dict();
|
||||||
match self.do_auth(params_dict, user) {
|
match self.do_auth(params_dict, user).await {
|
||||||
Ok(params) => {
|
Ok(params) => {
|
||||||
polariton::operation::OperationResponse {
|
polariton::operation::OperationResponse {
|
||||||
code: Self::op_code(),
|
code: Self::op_code(),
|
||||||
|
|||||||
@@ -16,8 +16,11 @@ serde_json.workspace = true
|
|||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
polariton_server.workspace = true
|
polariton_server.workspace = true
|
||||||
tokio = { version = "1.43", features = [ "net", "macros", "rt-multi-thread", "io-util", "time" ] }
|
tokio = { version = "1.43", features = [ "net", "macros", "rt-multi-thread", "io-util", "time" ] }
|
||||||
|
async-trait.workspace = true
|
||||||
|
|
||||||
# auth
|
# auth
|
||||||
libfj.workspace = true
|
libfj.workspace = true
|
||||||
jsonwebtoken = "9"
|
jsonwebtoken = "9"
|
||||||
argon2 = { version = "0.5", features = [ "std" ] }
|
argon2 = { version = "0.5", features = [ "std" ] }
|
||||||
|
|
||||||
|
rc_database = { version = "0.2", path = "../rc_database" }
|
||||||
|
|||||||
@@ -217,4 +217,43 @@ impl ItemCategory {
|
|||||||
pub fn but_bigger(&self) -> i32 {
|
pub fn but_bigger(&self) -> i32 {
|
||||||
(*self as i32) * 100_000
|
(*self as i32) * 100_000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_bigger(num: i32) -> Option<Self> {
|
||||||
|
Self::from_smaller(num / 100_000)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_smaller(num: i32) -> Option<Self> {
|
||||||
|
match num {
|
||||||
|
0 => Some(Self::NoFunction),
|
||||||
|
1 => Some(Self::Wheel),
|
||||||
|
2 => Some(Self::Hover),
|
||||||
|
3 => Some(Self::Wing),
|
||||||
|
4 => Some(Self::Rudder),
|
||||||
|
5 => Some(Self::Thruster),
|
||||||
|
6 => Some(Self::InsectLeg),
|
||||||
|
7 => Some(Self::MechLeg),
|
||||||
|
8 => Some(Self::Ski),
|
||||||
|
9 => Some(Self::TankTrack),
|
||||||
|
10 => Some(Self::Rotor),
|
||||||
|
11 => Some(Self::SprinterLeg),
|
||||||
|
12 => Some(Self::Propeller),
|
||||||
|
100 => Some(Self::Laser),
|
||||||
|
200 => Some(Self::Plasma),
|
||||||
|
250 => Some(Self::Mortar),
|
||||||
|
300 => Some(Self::Rail),
|
||||||
|
400 => Some(Self::Nano),
|
||||||
|
500 => Some(Self::Tesla),
|
||||||
|
600 => Some(Self::Aeroflak),
|
||||||
|
650 => Some(Self::Ion),
|
||||||
|
701 => Some(Self::Seeker),
|
||||||
|
750 => Some(Self::Chaingun),
|
||||||
|
800 => Some(Self::ShieldModule),
|
||||||
|
801 => Some(Self::GhostModule),
|
||||||
|
802 => Some(Self::BlinkModule),
|
||||||
|
803 => Some(Self::EmpModule),
|
||||||
|
804 => Some(Self::WindowmakerModule),
|
||||||
|
900 => Some(Self::EnergyModule),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -255,4 +255,10 @@ impl <C: Clone> super::ConfigProvider<C> for CubeConfig {
|
|||||||
items: self.chat.public_channels.iter().map(|s| Typed::Str(s.into())).collect(),
|
items: self.chat.public_channels.iter().map(|s| Typed::Str(s.into())).collect(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn server_config(&self) -> super::ServerConfig {
|
||||||
|
super::ServerConfig {
|
||||||
|
database: self.settings.server.database.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ mod cubes_json;
|
|||||||
pub use cubes_json::CubeConfig;
|
pub use cubes_json::CubeConfig;
|
||||||
|
|
||||||
mod traits;
|
mod traits;
|
||||||
pub use traits::{ConfigProvider, CompleteCampaignProvider, DevMessageProvider};
|
pub use traits::{ConfigProvider, CompleteCampaignProvider, DevMessageProvider, ServerConfig};
|
||||||
|
|
||||||
pub type ConfigImpl = CubeConfig;
|
pub type ConfigImpl = CubeConfig;
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ pub trait ConfigProvider<C: Clone> {
|
|||||||
fn client_config(&self) -> Typed<C>;
|
fn client_config(&self) -> Typed<C>;
|
||||||
fn login_messages(&self) -> DevMessageProvider<C>;
|
fn login_messages(&self) -> DevMessageProvider<C>;
|
||||||
fn public_channels(&self) -> Typed<C>;
|
fn public_channels(&self) -> Typed<C>;
|
||||||
|
fn server_config(&self) -> ServerConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CompleteCampaignProvider {
|
pub struct CompleteCampaignProvider {
|
||||||
@@ -83,3 +84,7 @@ pub struct TypedDevMessage<C> {
|
|||||||
pub message: Typed<C>,
|
pub message: Typed<C>,
|
||||||
pub display_time: Typed<C>,
|
pub display_time: Typed<C>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ServerConfig {
|
||||||
|
pub database: String,
|
||||||
|
}
|
||||||
|
|||||||
@@ -99,6 +99,65 @@ impl std::convert::Into<crate::data::garage_bay::GarageSlotInfo> for GarageSlot
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn db_into_data(garage: rc_database::schema::garage::Model) -> crate::data::garage_bay::GarageSlotInfo {
|
||||||
|
crate::data::garage_bay::GarageSlotInfo {
|
||||||
|
name: garage.name,
|
||||||
|
cubes: 0, // TODO garage.cubes,
|
||||||
|
crf_id: garage.crf_id.unwrap_or(0),
|
||||||
|
was_rated: garage.was_rated,
|
||||||
|
movement_categories: movement_category_into_data(&garage.movement_categories),
|
||||||
|
uuid: i64_split(garage.uuid),
|
||||||
|
thumbnail_version: garage.thumbnail_version,
|
||||||
|
total_robot_cpu: garage.total_robot_cpu,
|
||||||
|
total_cosmetic_cpu: garage.total_cosmetic_cpu,
|
||||||
|
total_robot_ranking: garage.total_robot_ranking,
|
||||||
|
bay_cpu: garage.bay_cpu,
|
||||||
|
tutorial_robot: garage.tutorial_robot,
|
||||||
|
starter_robot_index: garage.starter_robot_index.map(|x| x as i32).unwrap_or(-1),
|
||||||
|
control_type: control_ty_into_data(garage.control_type),
|
||||||
|
control_options: crate::data::garage_bay::ControlOptions {
|
||||||
|
vertical_strafing: garage.vertical_strafing,
|
||||||
|
sideways_driving: garage.sideways_driving,
|
||||||
|
tracks_turn_on_spot: garage.tracks_turn_on_spot,
|
||||||
|
},
|
||||||
|
mastery_level: garage.mastery_level as i32,
|
||||||
|
bay_skin_id: garage.bay_skin_id,
|
||||||
|
weapon_order: rc_database::schema::parse_int_csv(&garage.weapon_order).into_iter().map(|x| x as i32).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn movement_category_into_data(mov_cat: &str) -> Vec<crate::data::weapon_list::ItemCategory> {
|
||||||
|
rc_database::schema::parse_int_csv(mov_cat)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|num| crate::data::weapon_list::ItemCategory::from_bigger(num as _))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn i64_split(num: i64) -> (u32, u32) {
|
||||||
|
let bytes = (num as u64).to_le_bytes();
|
||||||
|
(
|
||||||
|
u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
|
||||||
|
u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn u64_join(uuid: (u32, u32)) -> u64 {
|
||||||
|
let bytes = (uuid.0.to_le_bytes(), uuid.1.to_le_bytes());
|
||||||
|
u64::from_le_bytes(
|
||||||
|
[bytes.0[0], bytes.0[1], bytes.0[2], bytes.0[3],
|
||||||
|
bytes.1[0], bytes.1[1], bytes.1[2], bytes.1[3]]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn control_ty_into_data(control_ty: rc_database::schema::garage::ControlType) -> crate::data::garage_bay::ControlType {
|
||||||
|
match control_ty {
|
||||||
|
rc_database::schema::garage::ControlType::Camera => crate::data::garage_bay::ControlType::Camera,
|
||||||
|
rc_database::schema::garage::ControlType::Keyboard => crate::data::garage_bay::ControlType::Keyboard,
|
||||||
|
rc_database::schema::garage::ControlType::Count => crate::data::garage_bay::ControlType::Count,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Default)]
|
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Default)]
|
||||||
pub enum ControlType {
|
pub enum ControlType {
|
||||||
#[default]
|
#[default]
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ pub struct Settings {
|
|||||||
pub gameplay: super::GameplaySettings,
|
pub gameplay: super::GameplaySettings,
|
||||||
#[serde(default = "default_dev_messages")]
|
#[serde(default = "default_dev_messages")]
|
||||||
pub banners: Vec<BannerMessage>,
|
pub banners: Vec<BannerMessage>,
|
||||||
|
#[serde(default = "default_server_conf")]
|
||||||
|
pub server: ServerSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_gameplay_settings() -> super::GameplaySettings {
|
fn default_gameplay_settings() -> super::GameplaySettings {
|
||||||
@@ -32,3 +34,19 @@ pub struct BannerMessage {
|
|||||||
fn default_dev_messages() -> Vec<BannerMessage> {
|
fn default_dev_messages() -> Vec<BannerMessage> {
|
||||||
Vec::default()
|
Vec::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub struct ServerSettings {
|
||||||
|
#[serde(default = "default_db_conn")]
|
||||||
|
pub database: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_db_conn() -> String {
|
||||||
|
"sqlite:../data/robocraft/accounts.sqlite.db?mode=rwc".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_server_conf() -> ServerSettings {
|
||||||
|
ServerSettings {
|
||||||
|
database: default_db_conn(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,74 +1,81 @@
|
|||||||
use argon2::PasswordVerifier;
|
use argon2::PasswordVerifier;
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
|
|
||||||
use crate::persist::config::ConfigProvider;
|
use crate::persist::config::ConfigProvider;
|
||||||
|
|
||||||
pub struct AccountProvider {
|
pub struct AccountProvider {
|
||||||
root: std::path::PathBuf,
|
|
||||||
cubes: std::sync::Arc<Vec<u32>>,
|
cubes: std::sync::Arc<Vec<u32>>,
|
||||||
secret: Vec<u8>,
|
secret: Vec<u8>,
|
||||||
|
db: std::sync::Arc<rc_database::Database>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccountProvider {
|
impl AccountProvider {
|
||||||
pub fn load(root: impl AsRef<std::path::Path>, cubes: &crate::persist::config::ConfigImpl) -> std::io::Result<Self> {
|
pub async fn load(root: impl AsRef<std::path::Path>, conf: &crate::persist::config::ConfigImpl) -> std::io::Result<Self> {
|
||||||
let token_path = root.as_ref().join(super::TOKEN_SECRET_FILENAME);
|
let token_path = root.as_ref().join(super::TOKEN_SECRET_FILENAME);
|
||||||
|
let database_uri = <crate::persist::config::ConfigImpl as ConfigProvider<()>>::server_config(conf).database;
|
||||||
|
log::debug!("Connecting to user database URI: {}", database_uri);
|
||||||
|
let db = rc_database::Database::init(&database_uri).await
|
||||||
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::NotConnected, e))?;
|
||||||
let root = root.as_ref().join(super::USERS_DIR);
|
let root = root.as_ref().join(super::USERS_DIR);
|
||||||
std::fs::create_dir_all(&root)?;
|
std::fs::create_dir_all(&root)?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
root,
|
cubes: std::sync::Arc::new(<crate::persist::config::ConfigImpl as ConfigProvider<()>>::ids(conf)),
|
||||||
cubes: std::sync::Arc::new(<crate::persist::config::ConfigImpl as ConfigProvider<()>>::ids(cubes)),
|
|
||||||
secret: std::fs::read(&token_path)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_for_auth(root: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
|
|
||||||
let token_path = root.as_ref().join(super::TOKEN_SECRET_FILENAME);
|
|
||||||
let root = root.as_ref().join(super::USERS_DIR);
|
|
||||||
std::fs::create_dir_all(&root)?;
|
|
||||||
Ok(Self {
|
|
||||||
root,
|
|
||||||
cubes: std::sync::Arc::new(Vec::default()),
|
|
||||||
secret: std::fs::read(&token_path)?,
|
secret: std::fs::read(&token_path)?,
|
||||||
|
db: std::sync::Arc::new(db),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
impl <C: Clone> super::UserProvider<C> for AccountProvider {
|
impl <C: Clone> super::UserProvider<C> for AccountProvider {
|
||||||
fn authenticate(&self, token: super::UserToken, ext: std::collections::HashMap<std::any::TypeId, Box<dyn std::any::Any + Send + Sync + 'static>>) -> Result<Box<dyn super::User<C> + Send + Sync>, String> {
|
async fn authenticate(&self, token: super::UserToken, ext: std::collections::HashMap<std::any::TypeId, Box<dyn std::any::Any + Send + Sync + 'static>>) -> Result<Box<dyn super::User<C> + Send + Sync>, String> {
|
||||||
let new_root = self.root.join(&token.uuid);
|
//let new_root = self.root.join(&token.uuid);
|
||||||
let secret = jsonwebtoken::DecodingKey::from_secret(&self.secret);
|
let secret = jsonwebtoken::DecodingKey::from_secret(&self.secret);
|
||||||
let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256);
|
let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256);
|
||||||
validation.set_required_spec_claims::<&str>(&[]);
|
validation.set_required_spec_claims::<&str>(&[]);
|
||||||
jsonwebtoken::decode::<libfj::robocraft::TokenPayload>(&token.token, &secret, &validation).map_err(|e| e.to_string())?;
|
jsonwebtoken::decode::<libfj::robocraft::TokenPayload>(&token.token, &secret, &validation).map_err(|e| e.to_string())?;
|
||||||
let account_info = AccountInfo::load(&new_root).map_err(|e| e.to_string())?;
|
let user_info = if let Some(user_info) = self.db.user_by_public_id(token.uuid.clone()).await.map_err(|e| e.to_string())? {
|
||||||
|
user_info
|
||||||
|
} else {
|
||||||
|
return Err("User not found".to_owned());
|
||||||
|
};
|
||||||
|
let user_perms = if let Some(user_perms) = self.db.perms_by_user_id(user_info.id).await.map_err(|e| e.to_string())? {
|
||||||
|
user_perms
|
||||||
|
} else {
|
||||||
|
return Err("User permissions not found".to_owned());
|
||||||
|
};
|
||||||
|
//let account_info = AccountInfo::load(&new_root).map_err(|e| e.to_string())?;
|
||||||
Ok(Box::new(UserData {
|
Ok(Box::new(UserData {
|
||||||
root: new_root,
|
|
||||||
token,
|
token,
|
||||||
account: account_info,
|
account: user_info,
|
||||||
|
perms: user_perms,
|
||||||
cubes: self.cubes.clone(),
|
cubes: self.cubes.clone(),
|
||||||
extensions: ext,
|
extensions: ext,
|
||||||
|
db: self.db.clone(),
|
||||||
}))
|
}))
|
||||||
//Err("Unable to authenticate".to_string())
|
//Err("Unable to authenticate".to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
impl super::UserAuthenticator for AccountProvider {
|
impl super::UserAuthenticator for AccountProvider {
|
||||||
fn login(&self, info: super::UserInfo) -> Result<super::UserLoginInfo, String> {
|
async fn login(&self, info: super::UserInfo) -> Result<super::UserLoginInfo, String> {
|
||||||
let new_root = self.root.join(&info.payload.public_id);
|
//let new_root = self.root.join(&info.payload.public_id);
|
||||||
let is_new_user = !new_root.exists();
|
let is_new_user;
|
||||||
if is_new_user {
|
let mut user_info = if let Some(user_info) = self.db.user_by_public_id(info.payload.public_id.clone()).await.map_err(|e| e.to_string())? {
|
||||||
std::fs::create_dir(&new_root).map_err(|e| e.to_string())?;
|
is_new_user = false;
|
||||||
|
user_info
|
||||||
|
} else {
|
||||||
|
is_new_user = true;
|
||||||
log::info!("New user {}", info.payload.public_id);
|
log::info!("New user {}", info.payload.public_id);
|
||||||
super::setup_directory(&new_root).map_err(|e| e.to_string())?;
|
super::setup_new_user(&info, &self.db).await.map_err(|e| e.to_string())?;
|
||||||
}
|
self.db.user_by_public_id(info.payload.public_id.clone()).await.map_err(|e| e.to_string())?.unwrap()
|
||||||
let mut account_info = AccountInfo::load(&new_root).map_err(|e| e.to_string())?;
|
};
|
||||||
let is_new_user = is_new_user || (account_info.password.is_none() && account_info.steam_id.is_none()); // migration
|
let override_password = user_info.password.is_empty() && user_info.steam_id.is_none();
|
||||||
match info.extra {
|
match info.extra {
|
||||||
super::ExtraUserInfo::Steam { id } => {
|
super::ExtraUserInfo::Steam { id } => {
|
||||||
if is_new_user {
|
let id_str = id.to_string();
|
||||||
account_info.steam_id = Some(id);
|
if let Some(expected_steam_id) = user_info.steam_id {
|
||||||
}
|
if expected_steam_id != id_str {
|
||||||
if let Some(expected_steam_id) = account_info.steam_id {
|
|
||||||
if expected_steam_id != id {
|
|
||||||
return Err("SteamID does not match".to_owned())
|
return Err("SteamID does not match".to_owned())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -78,13 +85,13 @@ impl super::UserAuthenticator for AccountProvider {
|
|||||||
super::ExtraUserInfo::Standalone { password } => {
|
super::ExtraUserInfo::Standalone { password } => {
|
||||||
use argon2::password_hash::PasswordHasher;
|
use argon2::password_hash::PasswordHasher;
|
||||||
let argon2_algo = argon2::Argon2::default();
|
let argon2_algo = argon2::Argon2::default();
|
||||||
if is_new_user {
|
if override_password {
|
||||||
let salt = argon2::password_hash::SaltString::generate(&mut argon2::password_hash::rand_core::OsRng);
|
let salt = argon2::password_hash::SaltString::generate(&mut argon2::password_hash::rand_core::OsRng);
|
||||||
let password_hash = argon2_algo.hash_password(password.as_bytes(), &salt).map_err(|e| e.to_string())?.to_string();
|
let password_hash = argon2_algo.hash_password(password.as_bytes(), &salt).map_err(|e| e.to_string())?.to_string();
|
||||||
account_info.password = Some(password_hash);
|
user_info.password = password_hash;
|
||||||
}
|
}
|
||||||
if let Some(expected_password) = &account_info.password {
|
if !user_info.password.is_empty() {
|
||||||
let expected = argon2::password_hash::PasswordHash::new(expected_password).map_err(|e| e.to_string())?;
|
let expected = argon2::password_hash::PasswordHash::new(&user_info.password).map_err(|e| e.to_string())?;
|
||||||
argon2_algo.verify_password(password.as_bytes(), &expected).map_err(|e| e.to_string())?;
|
argon2_algo.verify_password(password.as_bytes(), &expected).map_err(|e| e.to_string())?;
|
||||||
} else {
|
} else {
|
||||||
return Err("Password not supported for this user".to_owned())
|
return Err("Password not supported for this user".to_owned())
|
||||||
@@ -92,9 +99,6 @@ impl super::UserAuthenticator for AccountProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// authentication has now definitely succeeded
|
// authentication has now definitely succeeded
|
||||||
if is_new_user {
|
|
||||||
account_info.save(new_root).map_err(|e| e.to_string())?;
|
|
||||||
}
|
|
||||||
// build token
|
// build token
|
||||||
let header = jsonwebtoken::Header {
|
let header = jsonwebtoken::Header {
|
||||||
typ: Some("JWT".to_string()),
|
typ: Some("JWT".to_string()),
|
||||||
@@ -121,45 +125,35 @@ impl super::UserAuthenticator for AccountProvider {
|
|||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
struct UserData {
|
struct UserData {
|
||||||
root: std::path::PathBuf,
|
|
||||||
token: super::UserToken,
|
token: super::UserToken,
|
||||||
account: AccountInfo,
|
account: rc_database::schema::user::Model,
|
||||||
|
perms: rc_database::schema::permissions::Model,
|
||||||
cubes: std::sync::Arc<Vec<u32>>,
|
cubes: std::sync::Arc<Vec<u32>>,
|
||||||
extensions: std::collections::HashMap<std::any::TypeId, Box<dyn std::any::Any + Send + Sync + 'static>>,
|
extensions: std::collections::HashMap<std::any::TypeId, Box<dyn std::any::Any + Send + Sync + 'static>>,
|
||||||
|
db: std::sync::Arc<rc_database::Database>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserData {
|
impl UserData {
|
||||||
fn load_garage_by_id(&self, id: u32) -> std::io::Result<crate::persist::GarageSlot> {
|
async fn load_garage_by_slot(&self, slot: u32) -> Result<Option<rc_database::schema::garage::Model>, rc_database::sea_orm::DbErr> {
|
||||||
let path = self.root.join(super::GARAGE_DIR).join(format!("{}.json", id));
|
//let path = self.root.join(super::GARAGE_DIR).join(format!("{}.json", id));
|
||||||
crate::persist::GarageSlot::load(&path)
|
//crate::persist::GarageSlot::load(&path)
|
||||||
|
self.db.garage_by_user_id_and_slot(self.account.id, slot).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_garage(&self, slot: &crate::persist::GarageSlot) -> std::io::Result<()> {
|
async fn save_garage_by_slot(&self, data: rc_database::schema::garage::ActiveModel, slot: u32) -> Result<(), rc_database::sea_orm::DbErr> {
|
||||||
let path = self.root.join(super::GARAGE_DIR).join(format!("{}.json", slot.slot));
|
self.db.update_garage_by_user_id_and_slot(data, self.account.id, slot).await?;
|
||||||
slot.save(path)
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn all_vehicles(&self) -> std::io::Result<Vec<crate::persist::GarageSlot>> {
|
async fn all_vehicles(&self) -> Result<Vec<rc_database::schema::garage::Model>, rc_database::sea_orm::DbErr> {
|
||||||
let path = self.root.join(super::GARAGE_DIR);
|
self.db.garages_by_user_id(self.account.id).await
|
||||||
let mut slots = Vec::new();
|
|
||||||
for entry in std::fs::read_dir(path)? {
|
|
||||||
let entry = entry?;
|
|
||||||
let filepath = entry.path();
|
|
||||||
if filepath.is_file() {
|
|
||||||
let slot = crate::persist::GarageSlot::load(&filepath)?;
|
|
||||||
slots.push(slot);
|
|
||||||
} else {
|
|
||||||
log::warn!("Ignoring non-file {} in {} dir", filepath.display(), super::GARAGE_DIR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
slots.sort_by_key(|slot| slot.slot);
|
|
||||||
Ok(slots)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const INVALID_ROBOT_ERR: i16 = 140;
|
const INVALID_ROBOT_ERR: i16 = crate::data::error_codes::WebServicesError::InvalidRobot as i16; // 140
|
||||||
const DATABASE_ERR: i16 = 8;
|
const DATABASE_ERR: i16 = crate::data::error_codes::WebServicesError::DatabaseError as i16; // 8
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
impl <C: Clone> super::User<C> for UserData {
|
impl <C: Clone> super::User<C> for UserData {
|
||||||
fn ext(&self, ty: std::any::TypeId) -> Option<&'_ (dyn std::any::Any + Send + Sync + 'static)> {
|
fn ext(&self, ty: std::any::TypeId) -> Option<&'_ (dyn std::any::Any + Send + Sync + 'static)> {
|
||||||
self.extensions.get(&ty).map(|x| x.as_ref())
|
self.extensions.get(&ty).map(|x| x.as_ref())
|
||||||
@@ -170,35 +164,58 @@ impl <C: Clone> super::User<C> for UserData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_mod(&self) -> bool {
|
fn is_mod(&self) -> bool {
|
||||||
self.account.is_mod
|
self.perms.moderator
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_admin(&self) -> bool {
|
fn is_admin(&self) -> bool {
|
||||||
self.account.is_admin
|
self.perms.administrator
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_dev(&self) -> bool {
|
fn is_dev(&self) -> bool {
|
||||||
self.account.is_dev
|
self.perms.developer
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unlocked_parts(&self) -> Vec<u32> {
|
async fn unlocked_parts(&self) -> Vec<u32> {
|
||||||
match self.account.inventory.override_ {
|
match self.db.user_aux_by_user_id_and_descriptor(self.account.id, rc_database::schema::user_aux::Descriptor::UnlockedParts).await {
|
||||||
super::inventory::UnlockOverride::Normal => self.account.inventory.unlocked.clone(),
|
Ok(Some(parts)) => {
|
||||||
super::inventory::UnlockOverride::UnlockNone => Vec::default(),
|
match serde_json::from_str::<super::inventory::UnlockedParts>(&parts.data) {
|
||||||
super::inventory::UnlockOverride::UnlockAll => self.cubes.as_ref().to_owned(),
|
Ok(json) => {
|
||||||
|
match json.override_ {
|
||||||
|
super::inventory::UnlockOverride::Normal => json.unlocked.clone(),
|
||||||
|
super::inventory::UnlockOverride::UnlockNone => Vec::default(),
|
||||||
|
super::inventory::UnlockOverride::UnlockAll => self.cubes.as_ref().to_owned(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to deserialize Descriptor::UnlockedParts for user_id {}: {}", self.account.id, e);
|
||||||
|
Vec::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Ok(None) => Vec::default(),
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to retrieve Descriptor::UnlockedParts for user_id {}: {}", self.account.id, e);
|
||||||
|
Vec::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selected_garage_uuid(&self) -> String {
|
async fn selected_garage(&self) -> (String, u32) {
|
||||||
self.account.garage.uuid_str()
|
match self.db.garage_selected(self.account.id).await {
|
||||||
|
Ok(Some(selected)) => (super::i64_as_uuid_str(selected.uuid), selected.slot),
|
||||||
|
Ok(None) => {
|
||||||
|
log::warn!("User {} does not have a selected garage", self.account.id);
|
||||||
|
("0_0".to_owned(), 0)
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to retrieve selected garage for user_id {}: {}", self.account.id, e);
|
||||||
|
("0_0".to_owned(), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selected_garage_slot(&self) -> u32 {
|
async fn all_slots_by_id(&self) -> super::UserSlots<C> {
|
||||||
self.account.garage.slot
|
let slots = match self.all_vehicles().await {
|
||||||
}
|
|
||||||
|
|
||||||
fn all_slots_by_id(&self) -> super::UserSlots<C> {
|
|
||||||
let slots = match self.all_vehicles() {
|
|
||||||
Ok(slots) => slots,
|
Ok(slots) => slots,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to load all vehicles: {}", e);
|
log::error!("Failed to load all vehicles: {}", e);
|
||||||
@@ -211,7 +228,7 @@ impl <C: Clone> super::User<C> for UserData {
|
|||||||
val_ty: polariton::serdes:: TypePrefix::HashMap,
|
val_ty: polariton::serdes:: TypePrefix::HashMap,
|
||||||
items: slots.into_iter().map(|slot| {
|
items: slots.into_iter().map(|slot| {
|
||||||
let slot_index = slot.slot;
|
let slot_index = slot.slot;
|
||||||
let garage_data: crate::data::garage_bay::GarageSlotInfo = slot.into();
|
let garage_data: crate::data::garage_bay::GarageSlotInfo = crate::persist::garage::db_into_data(slot);
|
||||||
(polariton::operation::Typed::Int(slot_index as _), garage_data.as_transmissible())
|
(polariton::operation::Typed::Int(slot_index as _), garage_data.as_transmissible())
|
||||||
}).collect(),
|
}).collect(),
|
||||||
});
|
});
|
||||||
@@ -220,77 +237,67 @@ impl <C: Clone> super::User<C> for UserData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn slot_by_id(&self, id: i32) -> Result<crate::persist::user::UserSlotData<C>, i16> {
|
async fn slot_by_id(&self, id: i32) -> Result<crate::persist::user::UserSlotData<C>, i16> {
|
||||||
match self.load_garage_by_id(id as _) {
|
match self.load_garage_by_slot(id as _).await {
|
||||||
Ok(slot) => {
|
Ok(Some(slot)) => {
|
||||||
let control_ty: crate::data::garage_bay::ControlType = slot.control_type.into();
|
let cube_count = slot.cube_count() as i32;
|
||||||
let control_options: crate::data::garage_bay::ControlOptions = slot.control_options.into();
|
let control_ty: crate::data::garage_bay::ControlType = crate::persist::garage::control_ty_into_data(slot.control_type);
|
||||||
|
let control_options = crate::data::garage_bay::ControlOptions {
|
||||||
|
vertical_strafing: slot.vertical_strafing,
|
||||||
|
sideways_driving: slot.sideways_driving,
|
||||||
|
tracks_turn_on_spot: slot.tracks_turn_on_spot,
|
||||||
|
};
|
||||||
Ok(crate::persist::user::UserSlotData {
|
Ok(crate::persist::user::UserSlotData {
|
||||||
data: polariton::operation::Typed::Bytes(slot.robot_data.into()),
|
data: polariton::operation::Typed::Bytes(slot.robot_data.into()),
|
||||||
colour_data: polariton::operation::Typed::Bytes(slot.colour_data.into()),
|
colour_data: polariton::operation::Typed::Bytes(slot.colour_data.into()),
|
||||||
cube_count: polariton::operation::Typed::Int(slot.cubes as _),
|
cube_count: polariton::operation::Typed::Int(cube_count),
|
||||||
weapon_order: polariton::operation::Typed::IntArr(slot.weapon_order.clone().into()),
|
weapon_order: polariton::operation::Typed::IntArr(rc_database::schema::parse_int_csv(&slot.weapon_order).into_iter().map(|x| x as i32).collect::<Vec<_>>().into()),
|
||||||
movement_categories: polariton::operation::Typed::IntArr(slot.movement_categories.into_iter().map(|cat| {
|
movement_categories: polariton::operation::Typed::IntArr(rc_database::schema::parse_int_csv(&slot.movement_categories).into_iter().map(|x| x as i32).collect::<Vec<_>>().into()),
|
||||||
let cat: crate::data::weapon_list::ItemCategory = cat.into();
|
|
||||||
cat.but_bigger()
|
|
||||||
}).collect::<Vec<_>>().into()),
|
|
||||||
control_type: polariton::operation::Typed::Int(control_ty as _),
|
control_type: polariton::operation::Typed::Int(control_ty as _),
|
||||||
control_options: control_options.as_transmissible(),
|
control_options: control_options.as_transmissible(),
|
||||||
mastery_level: polariton::operation::Typed::Int(0), // TODO
|
mastery_level: polariton::operation::Typed::Int(slot.mastery_level as i32),
|
||||||
robot_rank: polariton::operation::Typed::Int(slot.total_robot_ranking as _),
|
robot_rank: polariton::operation::Typed::Int(slot.total_robot_ranking as _),
|
||||||
cpu: polariton::operation::Typed::Int(slot.total_robot_cpu as _),
|
cpu: polariton::operation::Typed::Int(slot.total_robot_cpu as _),
|
||||||
cosmetic_cpu: polariton::operation::Typed::Int(slot.total_cosmetic_cpu as _),
|
cosmetic_cpu: polariton::operation::Typed::Int(slot.total_cosmetic_cpu as _),
|
||||||
uuid: polariton::operation::Typed::Str(format!("{}_{}", slot.uuid.0, slot.uuid.1).into()),
|
uuid: polariton::operation::Typed::Str(super::i64_as_uuid_str(slot.uuid).into()),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
Ok(None) => {
|
||||||
|
log::error!("Failed to find vehicle slot {} for user_id {}", id, self.account.id);
|
||||||
|
Err(DATABASE_ERR)
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to load vehicle {}: {}", id, e);
|
log::error!("Failed to retrieve vehicle {} for user_id {}: {}", id, self.account.id, e);
|
||||||
Err(INVALID_ROBOT_ERR)
|
Err(INVALID_ROBOT_ERR)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_slot(&self, vehicle: crate::persist::user::VehicleData) -> Result<(), i16> {
|
async fn save_slot(&self, vehicle: crate::persist::user::VehicleData) -> Result<(), i16> {
|
||||||
let id = vehicle.id as u32;
|
let entity = rc_database::schema::garage::ActiveModel {
|
||||||
let mut existing_data = self.load_garage_by_id(id).map_err(|e| {
|
robot_data: rc_database::sea_orm::ActiveValue::Set(vehicle.robot_data),
|
||||||
log::error!("Failed to load vehicle {}: {}", id, e);
|
colour_data: rc_database::sea_orm::ActiveValue::Set(vehicle.colour_data),
|
||||||
INVALID_ROBOT_ERR
|
weapon_order: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::dump_csv(&vehicle.weapon_order)),
|
||||||
})?;
|
..Default::default()
|
||||||
existing_data.slot = id;
|
};
|
||||||
existing_data.robot_data = vehicle.robot_data;
|
self.save_garage_by_slot(entity, vehicle.slot as u32).await.map_err(|e| {
|
||||||
existing_data.colour_data = vehicle.colour_data;
|
log::error!("Failed to save vehicle slot {} for user_id {}: {}", vehicle.slot, self.account.id, e);
|
||||||
log::debug!("weapon order: {:?}", vehicle.weapon_order);
|
|
||||||
existing_data.weapon_order = vehicle.weapon_order;
|
|
||||||
self.save_garage(&existing_data).map_err(|e| {
|
|
||||||
log::error!("Failed to save vehicle {}: {}", id, e);
|
|
||||||
DATABASE_ERR
|
DATABASE_ERR
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signup_date(&self) -> i64 {
|
fn signup_date(&self) -> i64 {
|
||||||
match self.root.metadata() {
|
super::since_windows_epoch(self.account.creation_time)
|
||||||
Ok(meta) => {
|
|
||||||
match meta.created() {
|
|
||||||
Ok(created) => {
|
|
||||||
match created.duration_since(std::time::SystemTime::UNIX_EPOCH) {
|
|
||||||
Ok(dur) => {
|
|
||||||
return super::since_windows_epoch(dur.as_secs() as i64);
|
|
||||||
},
|
|
||||||
Err(e) => log::error!("could not get duration since unix epoch of {}: {}", self.root.display(), e),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => log::error!("could not read creation time of {}: {}", self.root.display(), e),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => log::error!("could not retrieve metadata of {}: {}", self.root.display(), e),
|
|
||||||
}
|
|
||||||
super::since_windows_epoch(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn singleplayer_robots(&self) -> Result<polariton::operation::Typed<C>, i16> {
|
async fn singleplayer_robots(&self) -> Result<polariton::operation::Typed<C>, i16> {
|
||||||
let current_slot = self.load_garage_by_id(self.account.garage.slot).map_err(|e| {
|
let current_slot = self.db.garage_selected(self.account.id).await.map_err(|e| {
|
||||||
log::error!("Failed to load current vehicle: {}", e);
|
log::error!("Failed to retrieve selected vehicle for user_id {} (singleplayer_robots): {}", self.account.id, e);
|
||||||
|
DATABASE_ERR
|
||||||
|
})?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
log::error!("Failed to find selected vehicle for user_id {} (singleplayer_robots)", self.account.id);
|
||||||
INVALID_ROBOT_ERR
|
INVALID_ROBOT_ERR
|
||||||
})?;
|
})?;
|
||||||
let user_uuid = self.token.uuid.clone();
|
let user_uuid = self.token.uuid.clone();
|
||||||
@@ -298,52 +305,24 @@ impl <C: Clone> super::User<C> for UserData {
|
|||||||
players: vec![
|
players: vec![
|
||||||
crate::data::player_data::PlayerData {
|
crate::data::player_data::PlayerData {
|
||||||
name: user_uuid.clone(),
|
name: user_uuid.clone(),
|
||||||
display_name: user_uuid,
|
display_name: self.account.display_name.clone(),
|
||||||
mastery: current_slot.mastery_level,
|
mastery: current_slot.mastery_level as i32,
|
||||||
tier: 1, // FIXME
|
tier: 1, // FIXME
|
||||||
robot_name: current_slot.name,
|
robot_name: current_slot.name,
|
||||||
robot_map: current_slot.robot_data,
|
robot_map: current_slot.robot_data,
|
||||||
team: 0,
|
team: 0,
|
||||||
has_premium: true, // FIXME
|
has_premium: true, // FIXME
|
||||||
robot_uuid: format!("{}_{}", current_slot.uuid.0, current_slot.uuid.1),
|
robot_uuid: super::i64_as_uuid_str(current_slot.uuid).into(),
|
||||||
cpu: current_slot.total_robot_cpu as i32,
|
cpu: current_slot.total_robot_cpu as i32,
|
||||||
weapon_order: current_slot.weapon_order.clone(),
|
weapon_order: rc_database::schema::parse_int_csv(¤t_slot.weapon_order).into_iter().map(|x| x as i32).collect::<Vec<_>>(),
|
||||||
colour_map: current_slot.colour_data,
|
colour_map: current_slot.colour_data,
|
||||||
is_ai: false,
|
is_ai: false,
|
||||||
spawn_effect: "Spawn_Warp".to_owned(), // FIXME
|
spawn_effect: "Spawn_Warp".to_owned(), // FIXME
|
||||||
death_effect: "Explosion_Warp".to_owned(), // FIXME
|
death_effect: "Explosion_Warp".to_owned(), // FIXME
|
||||||
player_rank: 1, // FIXME
|
player_rank: 1, // FIXME
|
||||||
weapon_rank: current_slot.weapon_order.into_iter().map(|x| (x, if x == 0 { 0 } else { 1 })).collect(),
|
weapon_rank: rc_database::schema::parse_int_csv(¤t_slot.weapon_order).into_iter().map(|x| (x as i32, if x == 0 { 0 } else { 1 })).collect(),
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
}.as_transmissible())
|
}.as_transmissible())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
||||||
pub struct AccountInfo {
|
|
||||||
pub is_mod: bool,
|
|
||||||
pub is_admin: bool,
|
|
||||||
pub is_dev: bool,
|
|
||||||
pub password: Option<String>,
|
|
||||||
pub steam_id: Option<u64>,
|
|
||||||
pub inventory: super::UnlockedParts,
|
|
||||||
pub garage: super::SelectedGarage,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AccountInfo {
|
|
||||||
fn load(root: impl AsRef<std::path::Path>) -> std::io::Result<AccountInfo> {
|
|
||||||
let file = std::fs::File::open(root.as_ref().join(super::USER_FILE))?;
|
|
||||||
let buffered = std::io::BufReader::new(file);
|
|
||||||
let result = serde_json::from_reader(buffered)?;
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save(&self, root: impl AsRef<std::path::Path>) -> std::io::Result<()> {
|
|
||||||
let file = std::fs::File::create(root.as_ref().join(super::USER_FILE))?;
|
|
||||||
let buffered = std::io::BufWriter::new(file);
|
|
||||||
serde_json::to_writer_pretty(buffered, self)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,68 @@
|
|||||||
const REFERENCE_DIR: &str = "layout folder";
|
//const REFERENCE_DIR: &str = "layout folder";
|
||||||
|
|
||||||
fn build_reference_directory(root: impl AsRef<std::path::Path>) -> std::io::Result<()> {
|
fn current_unix_time() -> i64 {
|
||||||
std::fs::create_dir(&root)?;
|
chrono::Utc::now().timestamp()
|
||||||
let garage_dir = root.as_ref().join(super::GARAGE_DIR);
|
}
|
||||||
std::fs::create_dir(&garage_dir)?;
|
|
||||||
default_user_data().save(&root)?;
|
async fn build_new_account_data(user: &super::UserInfo, db: &rc_database::Database) -> Result<(), rc_database::sea_orm::DbErr> {
|
||||||
for slot in default_garage_slots() {
|
//std::fs::create_dir(&root)?;
|
||||||
|
//let garage_dir = root.as_ref().join(super::GARAGE_DIR);
|
||||||
|
//std::fs::create_dir(&garage_dir)?;
|
||||||
|
let user_data = db.insert_user(default_user_data(user)).await?;
|
||||||
|
db.insert_perms(default_user_perms(user_data.id)).await?;
|
||||||
|
db.insert_user_aux(default_user_aux_data(user_data.id)).await?;
|
||||||
|
db.insert_garages(default_garage_slots(user_data.id)).await?;
|
||||||
|
/*for slot in default_garage_slots() {
|
||||||
let filepath = garage_dir.join(format!("{}.json", slot.slot));
|
let filepath = garage_dir.join(format!("{}.json", slot.slot));
|
||||||
slot.save(filepath)?;
|
slot.save(filepath)?;
|
||||||
}
|
}*/
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_directory(new_dir: impl AsRef<std::path::Path>) -> std::io::Result<()> {
|
pub async fn setup_new_user(user: &super::UserInfo, db: &rc_database::Database) -> Result<(), rc_database::sea_orm::DbErr> {
|
||||||
let ref_path = new_dir.as_ref().parent().unwrap().join(REFERENCE_DIR);
|
/*let ref_path = new_dir.as_ref().parent().unwrap().join(REFERENCE_DIR);
|
||||||
if !ref_path.exists() {
|
if !ref_path.exists() {
|
||||||
log::debug!("Initialising reference directory {}", ref_path.display());
|
log::debug!("Initialising reference directory {}", ref_path.display());
|
||||||
build_reference_directory(&ref_path)?;
|
build_reference_directory(user)?;
|
||||||
}
|
}
|
||||||
log::debug!("Copying reference directory for new user: {}", new_dir.as_ref().display());
|
log::debug!("Copying reference directory for new user: {}", new_dir.as_ref().display());
|
||||||
so::copy_dir_all(ref_path, new_dir)?;
|
so::copy_dir_all(ref_path, new_dir)?;*/
|
||||||
|
build_new_account_data(user, db).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_user_data() -> super::AccountInfo {
|
fn default_user_data(user: &super::UserInfo) -> rc_database::schema::user::ActiveModel {
|
||||||
super::AccountInfo {
|
let password = if let super::ExtraUserInfo::Standalone { password } = &user.extra {
|
||||||
|
//password.to_owned()
|
||||||
|
use argon2::password_hash::PasswordHasher;
|
||||||
|
let argon2_algo = argon2::Argon2::default();
|
||||||
|
let salt = argon2::password_hash::SaltString::generate(&mut argon2::password_hash::rand_core::OsRng);
|
||||||
|
match argon2_algo.hash_password(password.as_bytes(), &salt) {
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to hash password for user {}/{}: {}", user.payload.public_id, user.payload.display_name, e);
|
||||||
|
"".to_owned()
|
||||||
|
},
|
||||||
|
Ok(password) => password.to_string(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Default::default()
|
||||||
|
};
|
||||||
|
let steam_id = if let super::ExtraUserInfo::Steam { id } = &user.extra {
|
||||||
|
Some(id.to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
rc_database::schema::user::ActiveModel {
|
||||||
|
id: Default::default(),
|
||||||
|
creation_time: rc_database::sea_orm::ActiveValue::Set(current_unix_time()),
|
||||||
|
public_id: rc_database::sea_orm::ActiveValue::Set(user.payload.public_id.clone()),
|
||||||
|
display_name: rc_database::sea_orm::ActiveValue::Set(user.payload.display_name.clone()),
|
||||||
|
password: rc_database::sea_orm::ActiveValue::Set(password),
|
||||||
|
email: rc_database::sea_orm::ActiveValue::Set("//TODO".to_owned()),
|
||||||
|
steam_id: rc_database::sea_orm::ActiveValue::Set(steam_id),
|
||||||
|
}
|
||||||
|
|
||||||
|
/*super::AccountInfo {
|
||||||
is_mod: false,
|
is_mod: false,
|
||||||
is_admin: false,
|
is_admin: false,
|
||||||
is_dev: false,
|
is_dev: false,
|
||||||
@@ -38,11 +76,112 @@ fn default_user_data() -> super::AccountInfo {
|
|||||||
uuid: (0, 0),
|
uuid: (0, 0),
|
||||||
slot: 0,
|
slot: 0,
|
||||||
},
|
},
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_user_aux_data(user_id: u32) -> Vec<rc_database::schema::user_aux::ActiveModel> {
|
||||||
|
vec![
|
||||||
|
rc_database::schema::user_aux::ActiveModel {
|
||||||
|
id: Default::default(),
|
||||||
|
user_id: rc_database::sea_orm::ActiveValue::Set(user_id),
|
||||||
|
creation_time: rc_database::sea_orm::ActiveValue::Set(current_unix_time()),
|
||||||
|
descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::UserXP),
|
||||||
|
data: rc_database::sea_orm::ActiveValue::Set("0".to_owned()),
|
||||||
|
},
|
||||||
|
rc_database::schema::user_aux::ActiveModel {
|
||||||
|
id: Default::default(),
|
||||||
|
user_id: rc_database::sea_orm::ActiveValue::Set(user_id),
|
||||||
|
creation_time: rc_database::sea_orm::ActiveValue::Set(current_unix_time()),
|
||||||
|
descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::PremiumExpiry),
|
||||||
|
data: rc_database::sea_orm::ActiveValue::Set(current_unix_time().to_string()),
|
||||||
|
},
|
||||||
|
rc_database::schema::user_aux::ActiveModel {
|
||||||
|
id: Default::default(),
|
||||||
|
user_id: rc_database::sea_orm::ActiveValue::Set(user_id),
|
||||||
|
creation_time: rc_database::sea_orm::ActiveValue::Set(current_unix_time()),
|
||||||
|
descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::UnlockedParts),
|
||||||
|
data: rc_database::sea_orm::ActiveValue::Set(
|
||||||
|
r#"{
|
||||||
|
"unlocked": [],
|
||||||
|
"override": "UnlockAll"
|
||||||
|
}"#.to_owned()),
|
||||||
|
},
|
||||||
|
rc_database::schema::user_aux::ActiveModel {
|
||||||
|
id: Default::default(),
|
||||||
|
user_id: rc_database::sea_orm::ActiveValue::Set(user_id),
|
||||||
|
creation_time: rc_database::sea_orm::ActiveValue::Set(current_unix_time()),
|
||||||
|
descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::TechPoints),
|
||||||
|
data: rc_database::sea_orm::ActiveValue::Set("1337".to_owned()),
|
||||||
|
},
|
||||||
|
rc_database::schema::user_aux::ActiveModel {
|
||||||
|
id: Default::default(),
|
||||||
|
user_id: rc_database::sea_orm::ActiveValue::Set(user_id),
|
||||||
|
creation_time: rc_database::sea_orm::ActiveValue::Set(current_unix_time()),
|
||||||
|
descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::UserRank),
|
||||||
|
data: rc_database::sea_orm::ActiveValue::Set("1".to_owned()),
|
||||||
|
},
|
||||||
|
rc_database::schema::user_aux::ActiveModel {
|
||||||
|
id: Default::default(),
|
||||||
|
user_id: rc_database::sea_orm::ActiveValue::Set(user_id),
|
||||||
|
creation_time: rc_database::sea_orm::ActiveValue::Set(current_unix_time()),
|
||||||
|
descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::UserFreeCurrency),
|
||||||
|
data: rc_database::sea_orm::ActiveValue::Set("10000".to_owned()),
|
||||||
|
},
|
||||||
|
rc_database::schema::user_aux::ActiveModel {
|
||||||
|
id: Default::default(),
|
||||||
|
user_id: rc_database::sea_orm::ActiveValue::Set(user_id),
|
||||||
|
creation_time: rc_database::sea_orm::ActiveValue::Set(current_unix_time()),
|
||||||
|
descriptor: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::user_aux::Descriptor::UserPaidCurrency),
|
||||||
|
data: rc_database::sea_orm::ActiveValue::Set("1000".to_owned()),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_user_perms(user_id: u32) -> rc_database::schema::permissions::ActiveModel {
|
||||||
|
rc_database::schema::permissions::ActiveModel {
|
||||||
|
id: Default::default(),
|
||||||
|
user_id: rc_database::sea_orm::ActiveValue::Set(user_id),
|
||||||
|
moderator: rc_database::sea_orm::ActiveValue::Set(false),
|
||||||
|
administrator: rc_database::sea_orm::ActiveValue::Set(false),
|
||||||
|
developer: rc_database::sea_orm::ActiveValue::Set(false),
|
||||||
|
royalty: rc_database::sea_orm::ActiveValue::Set(false),
|
||||||
|
banned: rc_database::sea_orm::ActiveValue::Set(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_garage_slots() -> Vec<crate::persist::GarageSlot> {
|
fn default_garage_slots(user_id: u32) -> Vec<rc_database::schema::garage::ActiveModel> {
|
||||||
|
let current_time = current_unix_time();
|
||||||
vec![
|
vec![
|
||||||
|
rc_database::schema::garage::ActiveModel {
|
||||||
|
id: Default::default(),
|
||||||
|
user_id: rc_database::sea_orm::ActiveValue::Set(user_id),
|
||||||
|
creation_time: rc_database::sea_orm::ActiveValue::Set(current_time),
|
||||||
|
slot: rc_database::sea_orm::ActiveValue::Set(0),
|
||||||
|
name: rc_database::sea_orm::ActiveValue::Set("Bay 0".to_owned()),
|
||||||
|
crf_id: rc_database::sea_orm::ActiveValue::Set(None),
|
||||||
|
was_rated: rc_database::sea_orm::ActiveValue::Set(false),
|
||||||
|
movement_categories: rc_database::sea_orm::ActiveValue::Set("".to_owned()),
|
||||||
|
uuid: rc_database::sea_orm::ActiveValue::Set(current_time),
|
||||||
|
thumbnail_version: rc_database::sea_orm::ActiveValue::Set(0),
|
||||||
|
total_robot_cpu: rc_database::sea_orm::ActiveValue::Set(0),
|
||||||
|
total_cosmetic_cpu: rc_database::sea_orm::ActiveValue::Set(0),
|
||||||
|
total_robot_ranking: rc_database::sea_orm::ActiveValue::Set(0),
|
||||||
|
bay_cpu: rc_database::sea_orm::ActiveValue::Set(2_000),
|
||||||
|
tutorial_robot: rc_database::sea_orm::ActiveValue::Set(false),
|
||||||
|
starter_robot_index: rc_database::sea_orm::ActiveValue::Set(None),
|
||||||
|
control_type: rc_database::sea_orm::ActiveValue::Set(rc_database::schema::garage::ControlType::Camera),
|
||||||
|
vertical_strafing: rc_database::sea_orm::ActiveValue::Set(false),
|
||||||
|
sideways_driving: rc_database::sea_orm::ActiveValue::Set(false),
|
||||||
|
tracks_turn_on_spot: rc_database::sea_orm::ActiveValue::Set(false),
|
||||||
|
mastery_level: rc_database::sea_orm::ActiveValue::Set(1),
|
||||||
|
bay_skin_id: rc_database::sea_orm::ActiveValue::Set("RC_MothershipSkin_Neptune_01".to_owned()),
|
||||||
|
weapon_order: rc_database::sea_orm::ActiveValue::Set("".to_owned()),
|
||||||
|
robot_data: rc_database::sea_orm::ActiveValue::Set(vec![0u8; 4]),
|
||||||
|
colour_data: rc_database::sea_orm::ActiveValue::Set(vec![0u8; 4]),
|
||||||
|
selected: rc_database::sea_orm::ActiveValue::Set(true),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
/*vec![
|
||||||
crate::persist::GarageSlot {
|
crate::persist::GarageSlot {
|
||||||
slot: 0,
|
slot: 0,
|
||||||
name: "Bay 1".to_owned(),
|
name: "Bay 1".to_owned(),
|
||||||
@@ -66,25 +205,5 @@ fn default_garage_slots() -> Vec<crate::persist::GarageSlot> {
|
|||||||
robot_data: vec![0; 4],
|
robot_data: vec![0; 4],
|
||||||
colour_data: vec![0; 4],
|
colour_data: vec![0; 4],
|
||||||
}
|
}
|
||||||
]
|
]*/
|
||||||
}
|
|
||||||
|
|
||||||
mod so {
|
|
||||||
// from https://stackoverflow.com/questions/26958489/how-to-copy-a-folder-recursively-in-rust
|
|
||||||
use std::path::Path;
|
|
||||||
use std::{io, fs};
|
|
||||||
|
|
||||||
pub(super) fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
|
|
||||||
fs::create_dir_all(&dst)?;
|
|
||||||
for entry in fs::read_dir(src)? {
|
|
||||||
let entry = entry?;
|
|
||||||
let ty = entry.file_type()?;
|
|
||||||
if ty.is_dir() {
|
|
||||||
copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
|
|
||||||
} else {
|
|
||||||
fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
mod account_json;
|
mod account_json;
|
||||||
pub use account_json::{AccountProvider, AccountInfo};
|
pub use account_json::AccountProvider;
|
||||||
|
|
||||||
mod garage_data;
|
mod garage_data;
|
||||||
pub use garage_data::SelectedGarage;
|
pub use garage_data::SelectedGarage;
|
||||||
|
|
||||||
mod initial_data;
|
mod initial_data;
|
||||||
pub use initial_data::setup_directory;
|
pub use initial_data::setup_new_user;
|
||||||
|
|
||||||
mod inventory;
|
mod inventory;
|
||||||
pub use inventory::UnlockedParts;
|
pub use inventory::UnlockedParts;
|
||||||
@@ -34,3 +34,11 @@ pub fn since_windows_epoch(since_unix_epoch: i64) -> i64 {
|
|||||||
//let time_in = chrono::Utc.from_utc_datetime(&chrono::NaiveDateTime::from_timestamp(since_unix_epoch, 0));
|
//let time_in = chrono::Utc.from_utc_datetime(&chrono::NaiveDateTime::from_timestamp(since_unix_epoch, 0));
|
||||||
time_in.signed_duration_since(windows_epoch).num_milliseconds() * 10_000
|
time_in.signed_duration_since(windows_epoch).num_milliseconds() * 10_000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn uuid_str(uuid: &(u32, u32)) -> String {
|
||||||
|
format!("{}_{}", uuid.0, uuid.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn i64_as_uuid_str(num: i64) -> String {
|
||||||
|
uuid_str(&super::garage::i64_split(num))
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,28 +25,30 @@ pub struct UserLoginInfo {
|
|||||||
pub is_new: bool,
|
pub is_new: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
pub trait UserProvider<C> {
|
pub trait UserProvider<C> {
|
||||||
fn authenticate(&self, user: UserToken, ext: std::collections::HashMap<std::any::TypeId, Box<dyn std::any::Any + Send + Sync + 'static>>) -> Result<Box<dyn User<C> + Send + Sync>, String>;
|
async fn authenticate(&self, user: UserToken, ext: std::collections::HashMap<std::any::TypeId, Box<dyn std::any::Any + Send + Sync + 'static>>) -> Result<Box<dyn User<C> + Send + Sync>, String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
pub trait UserAuthenticator {
|
pub trait UserAuthenticator {
|
||||||
fn login(&self, info: UserInfo) -> Result<UserLoginInfo, String>;
|
async fn login(&self, info: UserInfo) -> Result<UserLoginInfo, String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
pub trait User<C> {
|
pub trait User<C> {
|
||||||
fn ext(&self, ty: std::any::TypeId) -> Option<&'_ (dyn std::any::Any + Send + Sync + 'static)>;
|
fn ext(&self, ty: std::any::TypeId) -> Option<&'_ (dyn std::any::Any + Send + Sync + 'static)>;
|
||||||
fn token(&self) -> &'_ super::UserToken;
|
fn token(&self) -> &'_ super::UserToken;
|
||||||
fn is_mod(&self) -> bool;
|
fn is_mod(&self) -> bool;
|
||||||
fn is_admin(&self) -> bool;
|
fn is_admin(&self) -> bool;
|
||||||
fn is_dev(&self) -> bool;
|
fn is_dev(&self) -> bool;
|
||||||
fn unlocked_parts(&self) -> Vec<u32>;
|
async fn unlocked_parts(&self) -> Vec<u32>;
|
||||||
fn selected_garage_uuid(&self) -> String;
|
async fn selected_garage(&self) -> (String, u32);
|
||||||
fn selected_garage_slot(&self) -> u32;
|
async fn all_slots_by_id(&self) -> UserSlots<C>;
|
||||||
fn all_slots_by_id(&self) -> UserSlots<C>;
|
async fn slot_by_id(&self, id: i32) -> Result<UserSlotData<C>, i16>;
|
||||||
fn slot_by_id(&self, id: i32) -> Result<UserSlotData<C>, i16>;
|
async fn save_slot(&self, vehicle: VehicleData) -> Result<(), i16>;
|
||||||
fn save_slot(&self, vehicle: VehicleData) -> Result<(), i16>;
|
|
||||||
fn signup_date(&self) -> i64;
|
fn signup_date(&self) -> i64;
|
||||||
fn singleplayer_robots(&self) -> Result<polariton::operation::Typed<C>, i16>;
|
async fn singleplayer_robots(&self) -> Result<polariton::operation::Typed<C>, i16>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UserSlots<C> {
|
pub struct UserSlots<C> {
|
||||||
@@ -70,7 +72,7 @@ pub struct UserSlotData<C> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct VehicleData {
|
pub struct VehicleData {
|
||||||
pub id: i32,
|
pub slot: i32,
|
||||||
pub robot_data: Vec<u8>,
|
pub robot_data: Vec<u8>,
|
||||||
pub colour_data: Vec<u8>,
|
pub colour_data: Vec<u8>,
|
||||||
pub weapon_order: Vec<i32>,
|
pub weapon_order: Vec<i32>,
|
||||||
|
|||||||
@@ -7,13 +7,14 @@ pub struct UserState<C: Clone = ()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl <C: Clone> UserState<C> {
|
impl <C: Clone> UserState<C> {
|
||||||
pub fn update_with_auth(&self, auth_str: &str) -> bool {
|
pub async fn update_with_auth(&self, auth_str: &str) -> bool {
|
||||||
self.update_with_auth_ext(auth_str, |_| Some(Default::default()))
|
self.update_with_auth_ext(auth_str, |_| Some(Default::default())).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_with_auth_ext<F: FnOnce(&crate::persist::user::UserToken) -> Option<std::collections::HashMap<std::any::TypeId, Box<dyn std::any::Any + Send + Sync + 'static>>>>(&self, auth_str: &str, ext_f: F) -> bool {
|
pub async fn update_with_auth_ext<F: FnOnce(&crate::persist::user::UserToken) -> Option<std::collections::HashMap<std::any::TypeId, Box<dyn std::any::Any + Send + Sync + 'static>>>>(&self, auth_str: &str, ext_f: F) -> bool {
|
||||||
let mut lock = self.state.write().unwrap();
|
//let mut lock = self.state.write().unwrap();
|
||||||
match &*lock {
|
let init_state_clone = self.state.write().unwrap().clone();
|
||||||
|
match init_state_clone {
|
||||||
InitState::Unauthenticated(auth) => {
|
InitState::Unauthenticated(auth) => {
|
||||||
let splits: Vec<&str> = auth_str.split(';').collect();
|
let splits: Vec<&str> = auth_str.split(';').collect();
|
||||||
if splits.len() != 3 {
|
if splits.len() != 3 {
|
||||||
@@ -30,8 +31,9 @@ impl <C: Clone> UserState<C> {
|
|||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
match auth.authenticate(token, ext) {
|
match auth.authenticate(token, ext).await {
|
||||||
Ok(user) => {
|
Ok(user) => {
|
||||||
|
let mut lock = self.state.write().unwrap();
|
||||||
*lock = InitState::Authenticated(std::sync::Arc::new(user));
|
*lock = InitState::Authenticated(std::sync::Arc::new(user));
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
@@ -74,6 +76,7 @@ impl <C: Clone> UserState<C> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
enum InitState<C> {
|
enum InitState<C> {
|
||||||
Unauthenticated(std::sync::Arc<crate::persist::user::UserImpl>),
|
Unauthenticated(std::sync::Arc<crate::persist::user::UserImpl>),
|
||||||
Authenticated(std::sync::Arc<Box<dyn crate::persist::user::User<C> + Send + Sync>>),
|
Authenticated(std::sync::Arc<Box<dyn crate::persist::user::User<C> + Send + Sync>>),
|
||||||
|
|||||||
20
rc_database/Cargo.toml
Normal file
20
rc_database/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
[package]
|
||||||
|
name = "rc_database"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
readme.workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
all_databases = ["mysql", "postgres", "sqlite"]
|
||||||
|
mysql = [ "sea-orm/sqlx-mysql" ]
|
||||||
|
postgres = [ "sea-orm/sqlx-postgres" ]
|
||||||
|
sqlite = [ "sea-orm/sqlx-sqlite" ]
|
||||||
|
default = [ "all_databases" ]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
sea-orm = { version = "1.1.10", features = [ "runtime-tokio-rustls", "macros" ] }
|
||||||
|
sea-orm-migration = "1.1.10"
|
||||||
|
itertools = "0.14"
|
||||||
9
rc_database/src/lib.rs
Normal file
9
rc_database/src/lib.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
mod migration;
|
||||||
|
pub use migration::Migrator;
|
||||||
|
|
||||||
|
pub mod schema;
|
||||||
|
|
||||||
|
mod wrapper;
|
||||||
|
pub use wrapper::Database;
|
||||||
|
|
||||||
|
pub use sea_orm;
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
impl MigrationName for Migration {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"m20250424_000001_create_user_table"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
// Define how to apply this migration: Create the Users table.
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.create_table(
|
||||||
|
Table::create()
|
||||||
|
.table(crate::schema::user::Entity)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(crate::schema::user::Column::Id)
|
||||||
|
.unsigned()
|
||||||
|
.not_null()
|
||||||
|
.auto_increment()
|
||||||
|
.primary_key(),
|
||||||
|
)
|
||||||
|
.col(ColumnDef::new(crate::schema::user::Column::CreationTime).big_integer().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::user::Column::PublicId).string().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::user::Column::DisplayName).string().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::user::Column::Password).string().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::user::Column::Email).string().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::user::Column::SteamId).string())
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define how to rollback this migration: Drop the Users table.
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.drop_table(Table::drop().table(crate::schema::user::Entity).to_owned())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
impl MigrationName for Migration {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"m20250424_000002_create_user_permissions_table"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
// Define how to apply this migration: Create the Permissions table.
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.create_table(
|
||||||
|
Table::create()
|
||||||
|
.table(crate::schema::permissions::Entity)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(crate::schema::permissions::Column::Id)
|
||||||
|
.unsigned()
|
||||||
|
.not_null()
|
||||||
|
.auto_increment()
|
||||||
|
.primary_key(),
|
||||||
|
)
|
||||||
|
.col(ColumnDef::new(crate::schema::permissions::Column::UserId).unsigned().not_null())
|
||||||
|
.foreign_key(
|
||||||
|
ForeignKey::create()
|
||||||
|
.name("fk-permissions-user_id")
|
||||||
|
.from(crate::schema::permissions::Entity, crate::schema::permissions::Column::UserId)
|
||||||
|
.to(crate::schema::user::Entity, crate::schema::user::Column::Id),
|
||||||
|
)
|
||||||
|
.col(ColumnDef::new(crate::schema::permissions::Column::Moderator).boolean().not_null())
|
||||||
|
//.col(crate::schema::permissions::Column::Moderator.def()) // I wish this worked...
|
||||||
|
.col(ColumnDef::new(crate::schema::permissions::Column::Administrator).boolean().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::permissions::Column::Developer).boolean().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::permissions::Column::Royalty).boolean().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::permissions::Column::Banned).boolean().not_null())
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define how to rollback this migration: Drop the Permissions table.
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.drop_table(Table::drop().table(crate::schema::permissions::Entity).to_owned())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
impl MigrationName for Migration {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"m20250424_000003_create_garage_table"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
// Define how to apply this migration: Create the Garages table.
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.create_table(
|
||||||
|
Table::create()
|
||||||
|
.table(crate::schema::garage::Entity)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(crate::schema::garage::Column::Id)
|
||||||
|
.unsigned()
|
||||||
|
.not_null()
|
||||||
|
.auto_increment()
|
||||||
|
.primary_key(),
|
||||||
|
)
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::UserId).unsigned().not_null())
|
||||||
|
.foreign_key(
|
||||||
|
ForeignKey::create()
|
||||||
|
.name("fk-garage-user_id")
|
||||||
|
.from(crate::schema::garage::Entity, crate::schema::garage::Column::UserId)
|
||||||
|
.to(crate::schema::user::Entity, crate::schema::user::Column::Id),
|
||||||
|
)
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::CreationTime).big_integer().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::Slot).unsigned().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::Name).string().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::CrfId).unsigned())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::WasRated).boolean().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::MovementCategories).string().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::Uuid).big_integer().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::ThumbnailVersion).unsigned().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::TotalRobotCpu).unsigned().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::TotalCosmeticCpu).unsigned().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::TotalRobotRanking).unsigned().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::BayCpu).unsigned().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::TutorialRobot).boolean().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::StarterRobotIndex).unsigned())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::ControlType).tiny_unsigned().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::VerticalStrafing).boolean().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::SidewaysDriving).boolean().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::TracksTurnOnSpot).boolean().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::MasteryLevel).unsigned().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::BaySkinId).string().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::WeaponOrder).string().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::RobotData).blob().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::ColourData).blob().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::garage::Column::Selected).boolean().not_null())
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define how to rollback this migration: Drop the Garages table.
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.drop_table(Table::drop().table(crate::schema::garage::Entity).to_owned())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
impl MigrationName for Migration {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"m20250424_000004_create_user_aux_table"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
// Define how to apply this migration: Create the User auxiliary table.
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.create_table(
|
||||||
|
Table::create()
|
||||||
|
.table(crate::schema::user_aux::Entity)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(crate::schema::user_aux::Column::Id)
|
||||||
|
.unsigned()
|
||||||
|
.not_null()
|
||||||
|
.auto_increment()
|
||||||
|
.primary_key(),
|
||||||
|
)
|
||||||
|
.col(ColumnDef::new(crate::schema::user_aux::Column::UserId).unsigned().not_null())
|
||||||
|
.foreign_key(
|
||||||
|
ForeignKey::create()
|
||||||
|
.name("fk-users_aux-user_id")
|
||||||
|
.from(crate::schema::user_aux::Entity, crate::schema::user_aux::Column::UserId)
|
||||||
|
.to(crate::schema::user::Entity, crate::schema::user::Column::Id),
|
||||||
|
)
|
||||||
|
.col(ColumnDef::new(crate::schema::user_aux::Column::CreationTime).big_integer().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::user_aux::Column::Descriptor).string().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::user_aux::Column::Data).string().not_null())
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define how to rollback this migration: Drop the User auxiliary table.
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.drop_table(Table::drop().table(crate::schema::user_aux::Entity).to_owned())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
impl MigrationName for Migration {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"m20250424_000005_create_campaign_tables"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
// Define how to apply this migration: Create the Campaigns table.
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.create_table(
|
||||||
|
Table::create()
|
||||||
|
.table(crate::schema::campaign::Entity)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(crate::schema::campaign::Column::Id)
|
||||||
|
.unsigned()
|
||||||
|
.not_null()
|
||||||
|
.auto_increment()
|
||||||
|
.primary_key(),
|
||||||
|
)
|
||||||
|
.col(ColumnDef::new(crate::schema::campaign::Column::UserId).unsigned().not_null())
|
||||||
|
.foreign_key(
|
||||||
|
ForeignKey::create()
|
||||||
|
.name("fk-campaign-user_id")
|
||||||
|
.from(crate::schema::campaign::Entity, crate::schema::campaign::Column::UserId)
|
||||||
|
.to(crate::schema::user::Entity, crate::schema::user::Column::Id),
|
||||||
|
)
|
||||||
|
.col(ColumnDef::new(crate::schema::campaign::Column::CreationTime).big_integer().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::campaign::Column::CampaignId).string().not_null())
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
manager
|
||||||
|
.create_table(
|
||||||
|
Table::create()
|
||||||
|
.table(crate::schema::campaign_difficulty_completion::Entity)
|
||||||
|
.col(
|
||||||
|
ColumnDef::new(crate::schema::campaign_difficulty_completion::Column::Id)
|
||||||
|
.unsigned()
|
||||||
|
.not_null()
|
||||||
|
.auto_increment()
|
||||||
|
.primary_key(),
|
||||||
|
)
|
||||||
|
.col(ColumnDef::new(crate::schema::campaign_difficulty_completion::Column::CampaignId).unsigned().not_null())
|
||||||
|
.foreign_key(
|
||||||
|
ForeignKey::create()
|
||||||
|
.name("fk-campaigns_completion-campaign_id")
|
||||||
|
.from(crate::schema::campaign_difficulty_completion::Entity, crate::schema::campaign_difficulty_completion::Column::CampaignId)
|
||||||
|
.to(crate::schema::campaign::Entity, crate::schema::campaign::Column::Id),
|
||||||
|
)
|
||||||
|
.col(ColumnDef::new(crate::schema::campaign_difficulty_completion::Column::CreationTime).big_integer().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::campaign_difficulty_completion::Column::Level).unsigned().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::campaign_difficulty_completion::Column::Wave).unsigned().not_null())
|
||||||
|
.col(ColumnDef::new(crate::schema::campaign_difficulty_completion::Column::Complete).boolean().not_null())
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define how to rollback this migration: Drop the Campaigns table.
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.drop_table(Table::drop().table(crate::schema::campaign::Entity).to_owned())
|
||||||
|
.await?;
|
||||||
|
manager
|
||||||
|
.drop_table(Table::drop().table(crate::schema::campaign_difficulty_completion::Entity).to_owned())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
22
rc_database/src/migration/mod.rs
Normal file
22
rc_database/src/migration/mod.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
use sea_orm_migration::{MigratorTrait, MigrationTrait, prelude::async_trait};
|
||||||
|
|
||||||
|
mod m20250424_000001_create_user_table;
|
||||||
|
mod m20250424_000002_create_user_permissions_table;
|
||||||
|
mod m20250424_000003_create_garage_table;
|
||||||
|
mod m20250424_000004_create_user_aux_table;
|
||||||
|
mod m20250424_000005_create_campaign_tables;
|
||||||
|
|
||||||
|
pub struct Migrator;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigratorTrait for Migrator {
|
||||||
|
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||||
|
vec![
|
||||||
|
Box::new(m20250424_000001_create_user_table::Migration),
|
||||||
|
Box::new(m20250424_000002_create_user_permissions_table::Migration),
|
||||||
|
Box::new(m20250424_000003_create_garage_table::Migration),
|
||||||
|
Box::new(m20250424_000004_create_user_aux_table::Migration),
|
||||||
|
Box::new(m20250424_000005_create_campaign_tables::Migration),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
37
rc_database/src/schema/campaign.rs
Normal file
37
rc_database/src/schema/campaign.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "campaigns")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: u32,
|
||||||
|
pub user_id: u32,
|
||||||
|
pub creation_time: i64, // seconds since unix epoch
|
||||||
|
pub campaign_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::user::Entity",
|
||||||
|
from = "Column::UserId",
|
||||||
|
to = "super::user::Column::Id"
|
||||||
|
)]
|
||||||
|
User,
|
||||||
|
#[sea_orm(has_many = "super::campaign_difficulty_completion::Entity")]
|
||||||
|
CampaignCompletion,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::user::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::User.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::campaign_difficulty_completion::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::CampaignCompletion.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
31
rc_database/src/schema/campaign_difficulty_completion.rs
Normal file
31
rc_database/src/schema/campaign_difficulty_completion.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "campaigns_completion")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: u32,
|
||||||
|
pub campaign_id: u32,
|
||||||
|
pub creation_time: i64, // seconds since unix epoch
|
||||||
|
pub level: u32,
|
||||||
|
pub wave: u32,
|
||||||
|
pub complete: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::campaign::Entity",
|
||||||
|
from = "Column::CampaignId",
|
||||||
|
to = "super::campaign::Column::Id"
|
||||||
|
)]
|
||||||
|
Campaign,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::campaign::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Campaign.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
6
rc_database/src/schema/common_query.rs
Normal file
6
rc_database/src/schema/common_query.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
use sea_orm::FromQueryResult;
|
||||||
|
|
||||||
|
#[derive(FromQueryResult)]
|
||||||
|
pub struct Id {
|
||||||
|
pub id: u32,
|
||||||
|
}
|
||||||
70
rc_database/src/schema/garage.rs
Normal file
70
rc_database/src/schema/garage.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "garages")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: u32,
|
||||||
|
pub user_id: u32,
|
||||||
|
pub creation_time: i64, // seconds since unix epoch
|
||||||
|
pub slot: u32,
|
||||||
|
pub name: String,
|
||||||
|
pub crf_id: Option<u32>,
|
||||||
|
pub was_rated: bool,
|
||||||
|
pub movement_categories: String, // csv?
|
||||||
|
pub uuid: i64,
|
||||||
|
pub thumbnail_version: u32,
|
||||||
|
pub total_robot_cpu: u32,
|
||||||
|
pub total_cosmetic_cpu: u32,
|
||||||
|
pub total_robot_ranking: u32,
|
||||||
|
pub bay_cpu: u32,
|
||||||
|
pub tutorial_robot: bool,
|
||||||
|
pub starter_robot_index: Option<u32>,
|
||||||
|
pub control_type: ControlType,
|
||||||
|
pub vertical_strafing: bool,
|
||||||
|
pub sideways_driving: bool,
|
||||||
|
pub tracks_turn_on_spot: bool,
|
||||||
|
pub mastery_level: u32,
|
||||||
|
pub bay_skin_id: String,
|
||||||
|
pub weapon_order: String, // csv?
|
||||||
|
pub robot_data: Vec<u8>,
|
||||||
|
pub colour_data: Vec<u8>,
|
||||||
|
pub selected: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Model {
|
||||||
|
pub fn cube_count(&self) -> u32 {
|
||||||
|
if self.robot_data.len() >= 4 {
|
||||||
|
u32::from_le_bytes([self.robot_data[0], self.robot_data[1], self.robot_data[2], self.robot_data[3]])
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::user::Entity",
|
||||||
|
from = "Column::UserId",
|
||||||
|
to = "super::user::Column::Id"
|
||||||
|
)]
|
||||||
|
User,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::user::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::User.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
|
||||||
|
#[sea_orm(rs_type = "u8", db_type = "TinyInteger")]
|
||||||
|
pub enum ControlType {
|
||||||
|
Camera = 0,
|
||||||
|
Keyboard = 1,
|
||||||
|
Count = 2,
|
||||||
|
}
|
||||||
17
rc_database/src/schema/mod.rs
Normal file
17
rc_database/src/schema/mod.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
pub mod user;
|
||||||
|
pub mod user_aux;
|
||||||
|
pub mod permissions;
|
||||||
|
pub mod garage;
|
||||||
|
pub mod campaign;
|
||||||
|
pub mod campaign_difficulty_completion;
|
||||||
|
pub mod common_query;
|
||||||
|
|
||||||
|
pub fn parse_int_csv(s: &str) -> Vec<u32> {
|
||||||
|
s.split(',').filter_map(|i_as_s| {
|
||||||
|
i_as_s.parse().ok()
|
||||||
|
}).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dump_csv<T: std::string::ToString>(slice: &[T]) -> String {
|
||||||
|
itertools::Itertools::intersperse(slice.iter().map(|x| x.to_string()), ",".to_owned()).collect()
|
||||||
|
}
|
||||||
32
rc_database/src/schema/permissions.rs
Normal file
32
rc_database/src/schema/permissions.rs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "permissions")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: u32,
|
||||||
|
pub user_id: u32,
|
||||||
|
pub moderator: bool,
|
||||||
|
pub administrator: bool,
|
||||||
|
pub developer: bool,
|
||||||
|
pub royalty: bool,
|
||||||
|
pub banned: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::user::Entity",
|
||||||
|
from = "Column::UserId",
|
||||||
|
to = "super::user::Column::Id"
|
||||||
|
)]
|
||||||
|
User,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::user::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::User.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
52
rc_database/src/schema/user.rs
Normal file
52
rc_database/src/schema/user.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "users")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: u32,
|
||||||
|
pub creation_time: i64, // seconds since unix epoch
|
||||||
|
pub public_id: String,
|
||||||
|
pub display_name: String,
|
||||||
|
pub password: String,
|
||||||
|
pub email: String,
|
||||||
|
pub steam_id: Option<String>, // u64
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(has_one = "super::permissions::Entity")]
|
||||||
|
Permission,
|
||||||
|
#[sea_orm(has_many = "super::garage::Entity")]
|
||||||
|
Garages,
|
||||||
|
#[sea_orm(has_many = "super::user_aux::Entity")]
|
||||||
|
Aux,
|
||||||
|
#[sea_orm(has_many = "super::campaign::Entity")]
|
||||||
|
Campaigns,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::permissions::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Permission.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::garage::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Garages.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::user_aux::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Aux.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::campaign::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::Campaigns.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
42
rc_database/src/schema/user_aux.rs
Normal file
42
rc_database/src/schema/user_aux.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "users_aux")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: u32,
|
||||||
|
pub user_id: u32,
|
||||||
|
pub creation_time: i64, // seconds since unix epoch
|
||||||
|
pub descriptor: Descriptor,
|
||||||
|
pub data: String, // usually JSON
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {
|
||||||
|
#[sea_orm(
|
||||||
|
belongs_to = "super::user::Entity",
|
||||||
|
from = "Column::UserId",
|
||||||
|
to = "super::user::Column::Id"
|
||||||
|
)]
|
||||||
|
User,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Related<super::user::Entity> for Entity {
|
||||||
|
fn to() -> RelationDef {
|
||||||
|
Relation::User.def()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
|
||||||
|
#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)", rename_all = "PascalCase")]
|
||||||
|
pub enum Descriptor {
|
||||||
|
UserXP, // u32
|
||||||
|
PremiumExpiry, // u64, seconds since Unix epoch
|
||||||
|
UnlockedParts, // rc_core::persist::user::UnlockedParts
|
||||||
|
TechPoints, // u32
|
||||||
|
UserRank, // u32
|
||||||
|
UserFreeCurrency, // u64
|
||||||
|
UserPaidCurrency, // u64
|
||||||
|
}
|
||||||
115
rc_database/src/wrapper.rs
Normal file
115
rc_database/src/wrapper.rs
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
use sea_orm_migration::MigratorTrait;
|
||||||
|
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect};
|
||||||
|
|
||||||
|
pub struct Database {
|
||||||
|
orm: sea_orm::DatabaseConnection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Database {
|
||||||
|
pub async fn init(uri: &str) -> Result<Self, sea_orm::DbErr>{
|
||||||
|
let db = sea_orm::Database::connect(uri).await?;
|
||||||
|
//let schema_manager = SchemaManager::new(&db);
|
||||||
|
super::Migrator::up(&db, None).await?;
|
||||||
|
Ok(Self {
|
||||||
|
orm: db,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn user_by_public_id(&self, public_id: String) -> Result<Option<crate::schema::user::Model>, sea_orm::DbErr> {
|
||||||
|
crate::schema::user::Entity::find()
|
||||||
|
.filter(crate::schema::user::Column::PublicId.eq(public_id))
|
||||||
|
.one(&self.orm)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn insert_user(&self, entity: crate::schema::user::ActiveModel) -> Result<crate::schema::user::Model, sea_orm::DbErr> {
|
||||||
|
entity.insert(&self.orm).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn user_aux_by_user_id(&self, user_id: u32) -> Result<Vec<crate::schema::user_aux::Model>, sea_orm::DbErr> {
|
||||||
|
crate::schema::user_aux::Entity::find()
|
||||||
|
.filter(crate::schema::user_aux::Column::UserId.eq(user_id))
|
||||||
|
.all(&self.orm)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn user_aux_by_user_id_and_descriptor(&self, user_id: u32, descriptor: crate::schema::user_aux::Descriptor) -> Result<Option<crate::schema::user_aux::Model>, sea_orm::DbErr> {
|
||||||
|
crate::schema::user_aux::Entity::find()
|
||||||
|
.filter(crate::schema::user_aux::Column::UserId.eq(user_id))
|
||||||
|
.filter(crate::schema::user_aux::Column::Descriptor.eq(descriptor))
|
||||||
|
.one(&self.orm)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn insert_user_aux(&self, entities: Vec<crate::schema::user_aux::ActiveModel>) -> Result<(), sea_orm::DbErr> {
|
||||||
|
crate::schema::user_aux::Entity::insert_many(entities.into_iter()).exec(&self.orm).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn perms_by_user_id(&self, user_id: u32) -> Result<Option<crate::schema::permissions::Model>, sea_orm::DbErr> {
|
||||||
|
crate::schema::permissions::Entity::find()
|
||||||
|
.filter(crate::schema::permissions::Column::UserId.eq(user_id))
|
||||||
|
.one(&self.orm)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn insert_perms(&self, entity: crate::schema::permissions::ActiveModel) -> Result<crate::schema::permissions::Model, sea_orm::DbErr> {
|
||||||
|
entity.insert(&self.orm).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn garage_selected(&self, user_id: u32) -> Result<Option<crate::schema::garage::Model>, sea_orm::DbErr> {
|
||||||
|
crate::schema::garage::Entity::find()
|
||||||
|
.filter(crate::schema::garage::Column::UserId.eq(user_id))
|
||||||
|
.filter(crate::schema::garage::Column::Selected.eq(true))
|
||||||
|
.one(&self.orm)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn garage_by_user_id_and_slot(&self, user_id: u32, garage_slot: u32) -> Result<Option<crate::schema::garage::Model>, sea_orm::DbErr> {
|
||||||
|
crate::schema::garage::Entity::find()
|
||||||
|
.filter(crate::schema::garage::Column::UserId.eq(user_id))
|
||||||
|
.filter(crate::schema::garage::Column::Slot.eq(garage_slot))
|
||||||
|
.one(&self.orm)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn garages_by_user_id(&self, user_id: u32) -> Result<Vec<crate::schema::garage::Model>, sea_orm::DbErr> {
|
||||||
|
crate::schema::garage::Entity::find()
|
||||||
|
.filter(crate::schema::garage::Column::UserId.eq(user_id))
|
||||||
|
.order_by_asc(crate::schema::garage::Column::Slot)
|
||||||
|
.all(&self.orm)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn insert_garages(&self, entities: Vec<crate::schema::garage::ActiveModel>) -> Result<(), sea_orm::DbErr> {
|
||||||
|
crate::schema::garage::Entity::insert_many(entities.into_iter()).exec(&self.orm).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_garage(&self, entity: crate::schema::garage::ActiveModel, id: u32) -> Result<crate::schema::garage::Model, sea_orm::DbErr> {
|
||||||
|
crate::schema::garage::Entity::update(entity)
|
||||||
|
.filter(crate::schema::garage::Column::Id.eq(id))
|
||||||
|
.exec(&self.orm)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_garage_by_user_id_and_slot(&self, mut entity: crate::schema::garage::ActiveModel, user_id: u32, slot: u32) -> Result<Option<crate::schema::garage::Model>, sea_orm::DbErr> {
|
||||||
|
let id_opt = crate::schema::garage::Entity::find()
|
||||||
|
.select_only()
|
||||||
|
.column(crate::schema::garage::Column::Id)
|
||||||
|
.filter(crate::schema::garage::Column::UserId.eq(user_id))
|
||||||
|
.filter(crate::schema::garage::Column::Slot.eq(slot))
|
||||||
|
.into_model::<crate::schema::common_query::Id>()
|
||||||
|
.one(&self.orm)
|
||||||
|
.await?;
|
||||||
|
if let Some(id) = id_opt {
|
||||||
|
entity.id = sea_orm::ActiveValue::Set(id.id);
|
||||||
|
Ok(Some(crate::schema::garage::Entity::update(entity)
|
||||||
|
.exec(&self.orm)
|
||||||
|
.await?))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,3 +22,4 @@ serde_json.workspace = true
|
|||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
rc_core = { version = "*", path = "../rc_core" }
|
rc_core = { version = "*", path = "../rc_core" }
|
||||||
rand = "0.9"
|
rand = "0.9"
|
||||||
|
async-trait.workspace = true
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
log::debug!("Got cli args {:?}", args);
|
log::debug!("Got cli args {:?}", args);
|
||||||
|
|
||||||
let cubes = rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data");
|
let cubes = rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data");
|
||||||
let users = std::sync::Arc::new(rc_core::persist::user::UserImpl::load(&args.data, &cubes).expect("Bad user data"));
|
let users = std::sync::Arc::new(rc_core::persist::user::UserImpl::load(&args.data, &cubes).await.expect("Bad user data"));
|
||||||
let init_ctx = std::sync::Arc::new(InitConfig {
|
let init_ctx = std::sync::Arc::new(InitConfig {
|
||||||
cubes,
|
cubes,
|
||||||
users,
|
users,
|
||||||
|
|||||||
@@ -1,13 +1,33 @@
|
|||||||
use polariton_server::operations::SimpleFunc;
|
use polariton::operation::{ParameterTable, Typed, OperationResponse};
|
||||||
use polariton::operation::{ParameterTable, Typed};
|
|
||||||
|
const CODE: u8 = 177;
|
||||||
|
|
||||||
const PARAM_KEY: u8 = 54;
|
const PARAM_KEY: u8 = 54;
|
||||||
|
|
||||||
pub(super) fn garage_id_provider() -> SimpleFunc<177, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result<ParameterTable, i16>) + Sync + Sync> {
|
pub(super) fn garage_id_provider() -> GarageIdProvider {
|
||||||
SimpleFunc::new(|params, user: &crate::UserTy| {
|
GarageIdProvider
|
||||||
let user_info = user.user()?;
|
}
|
||||||
let mut params = params.to_dict();
|
|
||||||
params.insert(PARAM_KEY, Typed::Str(user_info.selected_garage_uuid().into()));
|
async fn do_handling(params: ParameterTable<()>, user: &crate::UserTy) -> Result<ParameterTable, i16> {
|
||||||
Ok(params.into())
|
let user_info = user.user()?;
|
||||||
})
|
let mut params = params.to_dict();
|
||||||
|
params.insert(PARAM_KEY, Typed::Str(user_info.selected_garage().await.0.into()));
|
||||||
|
Ok(params.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GarageIdProvider;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl polariton_server::operations::Operation<()> for GarageIdProvider {
|
||||||
|
type User = crate::UserTy;
|
||||||
|
|
||||||
|
async fn handle_async(&self, params: ParameterTable<()>, user: &Self::User) -> OperationResponse<()> {
|
||||||
|
polariton_server::operations::result_to_op_resp::<CODE, ()>(do_handling(params, user).await)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl polariton_server::operations::OperationCode for GarageIdProvider {
|
||||||
|
fn op_code() -> u8 {
|
||||||
|
CODE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,38 @@
|
|||||||
use polariton_server::operations::SimpleFunc;
|
use polariton::operation::{ParameterTable, Typed, OperationResponse};
|
||||||
use polariton::operation::{ParameterTable, Typed};
|
|
||||||
|
const CODE: u8 = 40;
|
||||||
|
|
||||||
const SLOTS_PARAM_KEY: u8 = 44;
|
const SLOTS_PARAM_KEY: u8 = 44;
|
||||||
const SELECTED_SLOT_PARAM_KEY: u8 = 43;
|
const SELECTED_SLOT_PARAM_KEY: u8 = 43;
|
||||||
const SLOT_ORDER_PARAM_KEY: u8 = 58;
|
const SLOT_ORDER_PARAM_KEY: u8 = 58;
|
||||||
|
|
||||||
pub(super) fn garage_slot_provider() -> SimpleFunc<40, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result<ParameterTable, i16>) + Sync + Sync> {
|
pub(super) fn garage_slot_provider() -> GarageSlotsProvider {
|
||||||
SimpleFunc::new(|params, user: &crate::UserTy| {
|
GarageSlotsProvider
|
||||||
let user_info = user.user()?;
|
}
|
||||||
let mut params = params.to_dict();
|
|
||||||
let all_slots = user_info.all_slots_by_id();
|
async fn do_handling(params: ParameterTable<()>, user: &crate::UserTy) -> Result<ParameterTable, i16> {
|
||||||
params.insert(SLOTS_PARAM_KEY, all_slots.slot_info);
|
let user_info = user.user()?;
|
||||||
params.insert(SELECTED_SLOT_PARAM_KEY, Typed::Int(user_info.selected_garage_slot() as _));
|
let mut params = params.to_dict();
|
||||||
params.insert(SLOT_ORDER_PARAM_KEY, all_slots.slot_order);
|
let all_slots = user_info.all_slots_by_id().await;
|
||||||
Ok(params.into())
|
params.insert(SLOTS_PARAM_KEY, all_slots.slot_info);
|
||||||
})
|
params.insert(SELECTED_SLOT_PARAM_KEY, Typed::Int(user_info.selected_garage().await.1 as _));
|
||||||
|
params.insert(SLOT_ORDER_PARAM_KEY, all_slots.slot_order);
|
||||||
|
Ok(params.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GarageSlotsProvider;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl polariton_server::operations::Operation<()> for GarageSlotsProvider {
|
||||||
|
type User = crate::UserTy;
|
||||||
|
|
||||||
|
async fn handle_async(&self, params: ParameterTable<()>, user: &Self::User) -> OperationResponse<()> {
|
||||||
|
polariton_server::operations::result_to_op_resp::<CODE, ()>(do_handling(params, user).await)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl polariton_server::operations::OperationCode for GarageSlotsProvider {
|
||||||
|
fn op_code() -> u8 {
|
||||||
|
CODE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use polariton_server::operations::SimpleFunc;
|
use polariton::operation::{ParameterTable, Typed, OperationResponse};
|
||||||
use polariton::operation::{ParameterTable, Typed};
|
|
||||||
|
const CODE_MACHINE_PROVIDER: u8 = 43;
|
||||||
|
const CODE_MACHINE_SAVER: u8 = 41;
|
||||||
|
|
||||||
const SLOT_PARAM_KEY: u8 = 45; // uint
|
const SLOT_PARAM_KEY: u8 = 45; // uint
|
||||||
const DATA_PARAM_KEY: u8 = 49; // byte arr
|
const DATA_PARAM_KEY: u8 = 49; // byte arr
|
||||||
@@ -10,25 +12,44 @@ const CONTROL_TYPE_PARAM_KEY: u8 = 59; // int
|
|||||||
const CONTROL_OPTIONS_PARAM_KEY: u8 = 60; // bool arr
|
const CONTROL_OPTIONS_PARAM_KEY: u8 = 60; // bool arr
|
||||||
const MASTERY_LEVEL_PARAM_KEY: u8 = 18; // int
|
const MASTERY_LEVEL_PARAM_KEY: u8 = 18; // int
|
||||||
|
|
||||||
pub(super) fn garage_machine_provider() -> SimpleFunc<43, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result<ParameterTable, i16>) + Sync + Sync> {
|
pub(super) fn garage_machine_provider() -> MachineProvider {
|
||||||
SimpleFunc::new(|params, user: &crate::UserTy| {
|
MachineProvider
|
||||||
let mut params = params.to_dict();
|
}
|
||||||
let user_info = user.user()?;
|
|
||||||
if let Some(Typed::Int(garage_slot)) = params.get(&SLOT_PARAM_KEY) {
|
async fn do_get(params: ParameterTable<()>, user: &crate::UserTy) -> Result<ParameterTable, i16> {
|
||||||
log::debug!("Got machine request for slot {:?}", garage_slot);
|
let mut params = params.to_dict();
|
||||||
let machine = user_info.slot_by_id(*garage_slot)?;
|
let user_info = user.user()?;
|
||||||
params.insert(DATA_PARAM_KEY, machine.data);
|
if let Some(Typed::Int(garage_slot)) = params.get(&SLOT_PARAM_KEY) {
|
||||||
params.insert(CUBES_COUNT_PARAM_KEY, machine.cube_count);
|
log::debug!("Got machine request for slot {:?}", garage_slot);
|
||||||
params.insert(WEAPON_ORDER_PARAM_KEY, machine.weapon_order);
|
let machine = user_info.slot_by_id(*garage_slot).await?;
|
||||||
params.insert(MOVEMENT_CATEGORIES_PARAM_KEY, machine.movement_categories);
|
params.insert(DATA_PARAM_KEY, machine.data);
|
||||||
params.insert(CONTROL_TYPE_PARAM_KEY, machine.control_type);
|
params.insert(CUBES_COUNT_PARAM_KEY, machine.cube_count);
|
||||||
params.insert(CONTROL_OPTIONS_PARAM_KEY, machine.control_options);
|
params.insert(WEAPON_ORDER_PARAM_KEY, machine.weapon_order);
|
||||||
params.insert(MASTERY_LEVEL_PARAM_KEY, machine.mastery_level);
|
params.insert(MOVEMENT_CATEGORIES_PARAM_KEY, machine.movement_categories);
|
||||||
} else {
|
params.insert(CONTROL_TYPE_PARAM_KEY, machine.control_type);
|
||||||
params.insert(SLOT_PARAM_KEY, Typed::Int(user_info.selected_garage_slot() as _));
|
params.insert(CONTROL_OPTIONS_PARAM_KEY, machine.control_options);
|
||||||
}
|
params.insert(MASTERY_LEVEL_PARAM_KEY, machine.mastery_level);
|
||||||
Ok(params.into())
|
} else {
|
||||||
})
|
params.insert(SLOT_PARAM_KEY, Typed::Int(user_info.selected_garage().await.1 as _));
|
||||||
|
}
|
||||||
|
Ok(params.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MachineProvider;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl polariton_server::operations::Operation<()> for MachineProvider {
|
||||||
|
type User = crate::UserTy;
|
||||||
|
|
||||||
|
async fn handle_async(&self, params: ParameterTable<()>, user: &Self::User) -> OperationResponse<()> {
|
||||||
|
polariton_server::operations::result_to_op_resp::<CODE_MACHINE_PROVIDER, ()>(do_get(params, user).await)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl polariton_server::operations::OperationCode for MachineProvider {
|
||||||
|
fn op_code() -> u8 {
|
||||||
|
CODE_MACHINE_PROVIDER
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ERROR_PARAM_KEY: u8 = 47; // int
|
const ERROR_PARAM_KEY: u8 = 47; // int
|
||||||
@@ -38,39 +59,58 @@ const COMPRESSED_COLOUR_DATA_PARAM_KEY: u8 = 33; // byte arr
|
|||||||
|
|
||||||
const INVALID_ROBOT_ERR: i16 = 140;
|
const INVALID_ROBOT_ERR: i16 = 140;
|
||||||
|
|
||||||
pub(super) fn garage_machine_save_provider() -> SimpleFunc<41, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result<ParameterTable, i16>) + Sync + Sync> {
|
pub(super) fn garage_machine_save_provider() -> MachineSaver {
|
||||||
SimpleFunc::new(|params, user: &crate::UserTy| {
|
MachineSaver
|
||||||
log::debug!("machine save params: {:?}", params);
|
}
|
||||||
let mut params = params.to_dict();
|
|
||||||
if let Some(Typed::Int(slot_index)) = params.remove(&SLOT_PARAM_KEY) {
|
async fn do_save(params: ParameterTable<()>, user: &crate::UserTy) -> Result<ParameterTable, i16> {
|
||||||
if let Some(Typed::Bytes(robot_data)) = params.remove(&COMPRESSED_ROBOT_DATA_PARAM_KEY) {
|
log::debug!("machine save params: {:?}", params);
|
||||||
if let Some(Typed::Bytes(colour_data)) = params.remove(&COMPRESSED_COLOUR_DATA_PARAM_KEY) {
|
let mut params = params.to_dict();
|
||||||
if let Some(Typed::Arr(weapon_order)) = params.remove(&WEAPON_ORDER_PARAM_KEY) {
|
if let Some(Typed::Int(slot_index)) = params.remove(&SLOT_PARAM_KEY) {
|
||||||
let weapon_order_filtered: Vec<_> = weapon_order.items.into_iter().filter_map(|ty| if let Typed::Int(i) = ty { Some(i) } else { None }).collect();
|
if let Some(Typed::Bytes(robot_data)) = params.remove(&COMPRESSED_ROBOT_DATA_PARAM_KEY) {
|
||||||
let user_info = user.user()?;
|
if let Some(Typed::Bytes(colour_data)) = params.remove(&COMPRESSED_COLOUR_DATA_PARAM_KEY) {
|
||||||
let vehicle_data = rc_core::persist::user::VehicleData {
|
if let Some(Typed::Arr(weapon_order)) = params.remove(&WEAPON_ORDER_PARAM_KEY) {
|
||||||
id: slot_index,
|
let weapon_order_filtered: Vec<_> = weapon_order.items.into_iter().filter_map(|ty| if let Typed::Int(i) = ty { Some(i) } else { None }).collect();
|
||||||
robot_data: robot_data.vec,
|
let user_info = user.user()?;
|
||||||
colour_data: colour_data.vec,
|
let vehicle_data = rc_core::persist::user::VehicleData {
|
||||||
weapon_order: weapon_order_filtered,
|
slot: slot_index,
|
||||||
};
|
robot_data: robot_data.vec,
|
||||||
user_info.save_slot(vehicle_data)?;
|
colour_data: colour_data.vec,
|
||||||
let mut params_out = std::collections::HashMap::with_capacity(1);
|
weapon_order: weapon_order_filtered,
|
||||||
params_out.insert(ERROR_PARAM_KEY, Typed::Int(0));
|
};
|
||||||
return Ok(params_out.into());
|
user_info.save_slot(vehicle_data).await?;
|
||||||
} else {
|
let mut params_out = std::collections::HashMap::with_capacity(1);
|
||||||
log::warn!("weapon order is not this type (or does not exist)");
|
params_out.insert(ERROR_PARAM_KEY, Typed::Int(0));
|
||||||
}
|
return Ok(params_out.into());
|
||||||
} else {
|
} else {
|
||||||
log::warn!("colour data is not this type (or does not exist)");
|
log::warn!("weapon order is not this type (or does not exist)");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log::warn!("robot data is not this type (or does not exist)");
|
log::warn!("colour data is not this type (or does not exist)");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log::warn!("slot is not this type (or does not exist)");
|
log::warn!("robot data is not this type (or does not exist)");
|
||||||
}
|
}
|
||||||
Err(INVALID_ROBOT_ERR)
|
} else {
|
||||||
})
|
log::warn!("slot is not this type (or does not exist)");
|
||||||
|
}
|
||||||
|
Err(INVALID_ROBOT_ERR)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MachineSaver;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl polariton_server::operations::Operation<()> for MachineSaver {
|
||||||
|
type User = crate::UserTy;
|
||||||
|
|
||||||
|
async fn handle_async(&self, params: ParameterTable<()>, user: &Self::User) -> OperationResponse<()> {
|
||||||
|
polariton_server::operations::result_to_op_resp::<CODE_MACHINE_SAVER, ()>(do_save(params, user).await)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl polariton_server::operations::OperationCode for MachineSaver {
|
||||||
|
fn op_code() -> u8 {
|
||||||
|
CODE_MACHINE_SAVER
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,42 @@
|
|||||||
use polariton_server::operations::SimpleFunc;
|
use polariton::operation::{ParameterTable, Typed, OperationResponse};
|
||||||
use polariton::operation::{ParameterTable, Typed};
|
|
||||||
|
const CODE: u8 = 33;
|
||||||
|
|
||||||
// const USERNAME_PARAM_KEY: u8 = 30; // str
|
// const USERNAME_PARAM_KEY: u8 = 30; // str
|
||||||
const SLOT_PARAM_KEY: u8 = 31; // int
|
const SLOT_PARAM_KEY: u8 = 31; // int
|
||||||
const DATA_PARAM_KEY: u8 = 33; // byte arr
|
const DATA_PARAM_KEY: u8 = 33; // byte arr
|
||||||
|
|
||||||
pub(super) fn garage_machine_colour_provider() -> SimpleFunc<33, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result<ParameterTable, i16>) + Sync + Sync> {
|
pub(super) fn garage_machine_colour_provider() -> MachineColour {
|
||||||
SimpleFunc::new(|params, user: &crate::UserTy| {
|
MachineColour
|
||||||
let mut params = params.to_dict();
|
}
|
||||||
let user_info = user.user()?;
|
|
||||||
if let Some(Typed::Int(garage_slot)) = params.get(&SLOT_PARAM_KEY) {
|
async fn do_handling(params: ParameterTable<()>, user: &crate::UserTy) -> Result<ParameterTable, i16> {
|
||||||
log::debug!("Got machine colour request for slot {:?}", garage_slot);
|
let mut params = params.to_dict();
|
||||||
let machine = user_info.slot_by_id(*garage_slot)?;
|
let user_info = user.user()?;
|
||||||
params.insert(DATA_PARAM_KEY, machine.colour_data);
|
if let Some(Typed::Int(garage_slot)) = params.get(&SLOT_PARAM_KEY) {
|
||||||
} else {
|
log::debug!("Got machine colour request for slot {:?}", garage_slot);
|
||||||
params.insert(SLOT_PARAM_KEY, Typed::Int(user_info.selected_garage_slot() as _));
|
let machine = user_info.slot_by_id(*garage_slot).await?;
|
||||||
}
|
params.insert(DATA_PARAM_KEY, machine.colour_data);
|
||||||
|
} else {
|
||||||
Ok(params.into())
|
params.insert(SLOT_PARAM_KEY, Typed::Int(user_info.selected_garage().await.1 as _));
|
||||||
})
|
}
|
||||||
|
|
||||||
|
Ok(params.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MachineColour;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl polariton_server::operations::Operation<()> for MachineColour {
|
||||||
|
type User = crate::UserTy;
|
||||||
|
|
||||||
|
async fn handle_async(&self, params: ParameterTable<()>, user: &Self::User) -> OperationResponse<()> {
|
||||||
|
polariton_server::operations::result_to_op_resp::<CODE, ()>(do_handling(params, user).await)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl polariton_server::operations::OperationCode for MachineColour {
|
||||||
|
fn op_code() -> u8 {
|
||||||
|
CODE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,18 +7,19 @@ impl MoreLobbyAuth {
|
|||||||
const AUTH_PAYLOAD_KEY: u8 = 245;
|
const AUTH_PAYLOAD_KEY: u8 = 245;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
impl <C: Send + 'static> Operation<C> for MoreLobbyAuth {
|
impl <C: Send + 'static> Operation<C> for MoreLobbyAuth {
|
||||||
type User = crate::UserTy;
|
type User = crate::UserTy;
|
||||||
|
|
||||||
fn handle(&self, params: polariton::operation::ParameterTable<C>, user: &Self::User) -> polariton::operation::OperationResponse<C> {
|
async fn handle_async(&self, params: polariton::operation::ParameterTable<C>, user: &Self::User) -> polariton::operation::OperationResponse<C> {
|
||||||
let params_dict = params.to_dict();
|
let params_dict = params.to_dict();
|
||||||
if let Some(Typed::Str(auth_payload)) = params_dict.get(&Self::AUTH_PAYLOAD_KEY) {
|
if let Some(Typed::Str(auth_payload)) = params_dict.get(&Self::AUTH_PAYLOAD_KEY) {
|
||||||
//let mut write_lock = user.write().unwrap();
|
//let mut write_lock = user.write().unwrap();
|
||||||
if user.update_with_auth(&auth_payload.string) {
|
if user.update_with_auth(&auth_payload.string).await {
|
||||||
let mut resp_params = std::collections::HashMap::new();
|
let mut resp_params = std::collections::HashMap::new();
|
||||||
resp_params.insert(Self::AUTH_PAYLOAD_KEY, polariton::operation::Typed::Byte(0));
|
resp_params.insert(Self::AUTH_PAYLOAD_KEY, polariton::operation::Typed::Byte(0));
|
||||||
return polariton::operation::OperationResponse {
|
return polariton::operation::OperationResponse {
|
||||||
code: 230,
|
code: Self::op_code(),
|
||||||
return_code: 0,
|
return_code: 0,
|
||||||
message: polariton::operation::Typed::Null,
|
message: polariton::operation::Typed::Null,
|
||||||
params: resp_params.into(),
|
params: resp_params.into(),
|
||||||
@@ -26,12 +27,16 @@ impl <C: Send + 'static> Operation<C> for MoreLobbyAuth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
polariton::operation::OperationResponse {
|
polariton::operation::OperationResponse {
|
||||||
code: 230,
|
code: Self::op_code(),
|
||||||
return_code: 120,
|
return_code: 120,
|
||||||
message: polariton::operation::Typed::Null,
|
message: polariton::operation::Typed::Null,
|
||||||
params: std::collections::HashMap::new().into(),
|
params: std::collections::HashMap::new().into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle(&self, _params: polariton::operation::ParameterTable<C>, _user: &Self::User) -> polariton::operation::OperationResponse<C> {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OperationCode for MoreLobbyAuth {
|
impl OperationCode for MoreLobbyAuth {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ pub(super) fn platform_config_provider() -> SimpleFunc<165, crate::UserTy, impl
|
|||||||
(Typed::Str("UseDecimalSystem".into()), Typed::Bool(false.into())),
|
(Typed::Str("UseDecimalSystem".into()), Typed::Bool(false.into())),
|
||||||
(Typed::Str("FeedbackURL".into()), Typed::Str("https://mstdn.ca/@ngram".into())),
|
(Typed::Str("FeedbackURL".into()), Typed::Str("https://mstdn.ca/@ngram".into())),
|
||||||
(Typed::Str("SupportURL".into()), Typed::Str("https://git.ngni.us/OpenJam/servers".into())),
|
(Typed::Str("SupportURL".into()), Typed::Str("https://git.ngni.us/OpenJam/servers".into())),
|
||||||
(Typed::Str("WikiURL".into()), Typed::Str("https://docs.rs/libfj/latest/libfj/".into())),
|
(Typed::Str("WikiURL".into()), Typed::Str("https://git.ngram.ca/OpenJam/servers/wiki".into())),
|
||||||
].into(),
|
].into(),
|
||||||
}));
|
}));
|
||||||
Ok(params.into())
|
Ok(params.into())
|
||||||
|
|||||||
@@ -1,22 +1,40 @@
|
|||||||
use polariton_server::operations::SimpleFunc;
|
use polariton::operation::{ParameterTable, Typed, OperationResponse};
|
||||||
use polariton::operation::{ParameterTable, Typed};
|
|
||||||
|
|
||||||
const USERNAME_PARAM_KEY: u8 = 30; // in; str
|
const USERNAME_PARAM_KEY: u8 = 30; // in; str
|
||||||
const RANK_PARAM_KEY: u8 = 84; // out; int
|
const RANK_PARAM_KEY: u8 = 84; // out; int
|
||||||
const CPU_PARAM_KEY: u8 = 177; // out; int
|
const CPU_PARAM_KEY: u8 = 177; // out; int
|
||||||
const COSMETIC_CPU_PARAM_KEY: u8 = 176; // out; int
|
const COSMETIC_CPU_PARAM_KEY: u8 = 176; // out; int
|
||||||
|
|
||||||
pub(super) fn player_robot_rank_provider() -> SimpleFunc<79, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result<ParameterTable, i16>) + Sync + Sync> {
|
pub(super) fn player_robot_rank_provider() -> PlayerRank {
|
||||||
SimpleFunc::new(|params, user: &crate::UserTy| {
|
PlayerRank
|
||||||
let user = user.user()?;
|
}
|
||||||
let mut params = params.to_dict();
|
|
||||||
if let Some(Typed::Str(username)) = params.get(&USERNAME_PARAM_KEY) {
|
async fn do_handling(params: ParameterTable<()>, user: &crate::UserTy) -> Result<ParameterTable, i16> {
|
||||||
log::debug!("Get robot rank for user {}", username.string);
|
let user = user.user()?;
|
||||||
}
|
let mut params = params.to_dict();
|
||||||
let robot = user.slot_by_id(user.selected_garage_slot() as i32)?;
|
if let Some(Typed::Str(username)) = params.get(&USERNAME_PARAM_KEY) {
|
||||||
params.insert(RANK_PARAM_KEY, robot.robot_rank);
|
log::debug!("Get robot rank for user {}", username.string);
|
||||||
params.insert(CPU_PARAM_KEY, robot.cpu);
|
}
|
||||||
params.insert(COSMETIC_CPU_PARAM_KEY, robot.cosmetic_cpu);
|
let robot = user.slot_by_id(user.selected_garage().await.1 as i32).await?;
|
||||||
Ok(params.into())
|
params.insert(RANK_PARAM_KEY, robot.robot_rank);
|
||||||
})
|
params.insert(CPU_PARAM_KEY, robot.cpu);
|
||||||
|
params.insert(COSMETIC_CPU_PARAM_KEY, robot.cosmetic_cpu);
|
||||||
|
Ok(params.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PlayerRank;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl polariton_server::operations::Operation<()> for PlayerRank {
|
||||||
|
type User = crate::UserTy;
|
||||||
|
|
||||||
|
async fn handle_async(&self, params: ParameterTable<()>, user: &Self::User) -> OperationResponse<()> {
|
||||||
|
polariton_server::operations::result_to_op_resp::<79, ()>(do_handling(params, user).await)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl polariton_server::operations::OperationCode for PlayerRank {
|
||||||
|
fn op_code() -> u8 {
|
||||||
|
79
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,3 +16,4 @@ polariton.workspace = true
|
|||||||
polariton_auth = { version = "*", path = "../polariton_auth" }
|
polariton_auth = { version = "*", path = "../polariton_auth" }
|
||||||
polariton_server.workspace = true
|
polariton_server.workspace = true
|
||||||
rc_core = { version = "*", path = "../rc_core" }
|
rc_core = { version = "*", path = "../rc_core" }
|
||||||
|
async-trait.workspace = true
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
log::debug!("Got cli args {:?}", args);
|
log::debug!("Got cli args {:?}", args);
|
||||||
|
|
||||||
let cubes = rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data");
|
let cubes = rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data");
|
||||||
let users = std::sync::Arc::new(rc_core::persist::user::UserImpl::load(&args.data, &cubes).expect("Bad user data"));
|
let users = std::sync::Arc::new(rc_core::persist::user::UserImpl::load(&args.data, &cubes).await.expect("Bad user data"));
|
||||||
|
|
||||||
let server = std::sync::Arc::new(polariton_server::Server::new(operations::handler(), polariton_server::events::EventsHandler::new()));
|
let server = std::sync::Arc::new(polariton_server::Server::new(operations::handler(), polariton_server::events::EventsHandler::new()));
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +1,58 @@
|
|||||||
use polariton_server::operations::SimpleFunc;
|
use polariton::operation::{ParameterTable, Typed, OperationResponse};
|
||||||
use polariton::operation::{ParameterTable, Typed};
|
|
||||||
|
const CODE: u8 = 1;
|
||||||
|
|
||||||
const PARAM_KEY: u8 = 8;
|
const PARAM_KEY: u8 = 8;
|
||||||
|
|
||||||
pub(super) fn tdm_machines_provider() -> SimpleFunc<1, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result<ParameterTable, i16>) + Sync + Sync> {
|
pub(super) fn tdm_machines_provider() -> AiRobots {
|
||||||
SimpleFunc::new(|params, user: &crate::UserTy| {
|
AiRobots
|
||||||
let ulock = user.user()?;
|
}
|
||||||
let mut params = params.to_dict();
|
|
||||||
params.insert(PARAM_KEY, ulock.singleplayer_robots()?);
|
async fn do_handling(params: ParameterTable<()>, user: &crate::UserTy) -> Result<ParameterTable<()>, i16> {
|
||||||
let event_tx = user.event_sender();
|
let ulock = user.user()?;
|
||||||
let user_bot_data = ulock.slot_by_id(ulock.selected_garage_slot() as _)?;
|
let mut params = params.to_dict();
|
||||||
tokio::spawn(async move {
|
params.insert(PARAM_KEY, ulock.singleplayer_robots().await?);
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(20)).await;
|
let event_tx = user.event_sender();
|
||||||
log::debug!("Sending singleplayer event");
|
let user_bot_data = ulock.slot_by_id(ulock.selected_garage().await.1 as _).await?;
|
||||||
let mut spawn_params = std::collections::HashMap::with_capacity(4);
|
tokio::spawn(async move {
|
||||||
spawn_params.insert(2 /* robot GUID */, Typed::Str("1337_1337".into()));
|
tokio::time::sleep(std::time::Duration::from_secs(20)).await;
|
||||||
spawn_params.insert(3 /* machine model */, user_bot_data.data);
|
log::debug!("Sending singleplayer event");
|
||||||
spawn_params.insert(4 /* robot name */, Typed::Str("RE_robot_spawn_name0".into())); // FIXME
|
let mut spawn_params = std::collections::HashMap::with_capacity(4);
|
||||||
spawn_params.insert(7 /* color model */, user_bot_data.colour_data);
|
spawn_params.insert(2 /* robot GUID */, Typed::Str("1337_1337".into()));
|
||||||
event_tx.send(polariton_server::ToSend::Data {
|
spawn_params.insert(3 /* machine model */, user_bot_data.data);
|
||||||
data: polariton::packet::Data::Event(polariton::operation::Event { code: 3, params: spawn_params.into() }),
|
spawn_params.insert(4 /* robot name */, Typed::Str("RE_robot_spawn_name0".into())); // FIXME
|
||||||
encrypt: true,
|
spawn_params.insert(7 /* color model */, user_bot_data.colour_data);
|
||||||
channel: 0,
|
event_tx.send(polariton_server::ToSend::Data {
|
||||||
reliable: true,
|
data: polariton::packet::Data::Event(polariton::operation::Event { code: 3, params: spawn_params.into() }),
|
||||||
}).unwrap();
|
encrypt: true,
|
||||||
/*let mut update_params = std::collections::HashMap::with_capacity(1);
|
channel: 0,
|
||||||
update_params.insert(6 /* ??? */, Typed::Int(5));
|
reliable: true,
|
||||||
event_tx.send(polariton_server::ToSend::Data {
|
}).unwrap();
|
||||||
data: polariton::packet::Data::Event(polariton::operation::Event { code: 5, params: update_params.into() }),
|
/*let mut update_params = std::collections::HashMap::with_capacity(1);
|
||||||
encrypt: true,
|
update_params.insert(6 /* ??? */, Typed::Int(5));
|
||||||
channel: 0,
|
event_tx.send(polariton_server::ToSend::Data {
|
||||||
reliable: true,
|
data: polariton::packet::Data::Event(polariton::operation::Event { code: 5, params: update_params.into() }),
|
||||||
}).unwrap();*/
|
encrypt: true,
|
||||||
});
|
channel: 0,
|
||||||
Ok(params.into())
|
reliable: true,
|
||||||
})
|
}).unwrap();*/
|
||||||
|
});
|
||||||
|
Ok(params.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AiRobots;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl polariton_server::operations::Operation<()> for AiRobots {
|
||||||
|
type User = crate::UserTy;
|
||||||
|
|
||||||
|
async fn handle_async(&self, params: ParameterTable<()>, user: &Self::User) -> OperationResponse<()> {
|
||||||
|
polariton_server::operations::result_to_op_resp::<CODE, ()>(do_handling(params, user).await)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl polariton_server::operations::OperationCode for AiRobots {
|
||||||
|
fn op_code() -> u8 {
|
||||||
|
CODE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,18 +7,19 @@ impl MoreLobbyAuth {
|
|||||||
const AUTH_PAYLOAD_KEY: u8 = 245;
|
const AUTH_PAYLOAD_KEY: u8 = 245;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
impl <C: Send + 'static> Operation<C> for MoreLobbyAuth {
|
impl <C: Send + 'static> Operation<C> for MoreLobbyAuth {
|
||||||
type User = crate::UserTy;
|
type User = crate::UserTy;
|
||||||
|
|
||||||
fn handle(&self, params: polariton::operation::ParameterTable<C>, user: &Self::User) -> polariton::operation::OperationResponse<C> {
|
async fn handle_async(&self, params: polariton::operation::ParameterTable<C>, user: &Self::User) -> polariton::operation::OperationResponse<C> {
|
||||||
let params_dict = params.to_dict();
|
let params_dict = params.to_dict();
|
||||||
if let Some(Typed::Str(auth_payload)) = params_dict.get(&Self::AUTH_PAYLOAD_KEY) {
|
if let Some(Typed::Str(auth_payload)) = params_dict.get(&Self::AUTH_PAYLOAD_KEY) {
|
||||||
//let mut write_lock = user.write().unwrap();
|
//let mut write_lock = user.write().unwrap();
|
||||||
if user.update_with_auth(&auth_payload.string) {
|
if user.update_with_auth(&auth_payload.string).await {
|
||||||
let mut resp_params = std::collections::HashMap::new();
|
let mut resp_params = std::collections::HashMap::new();
|
||||||
resp_params.insert(Self::AUTH_PAYLOAD_KEY, polariton::operation::Typed::Byte(0));
|
resp_params.insert(Self::AUTH_PAYLOAD_KEY, polariton::operation::Typed::Byte(0));
|
||||||
return polariton::operation::OperationResponse {
|
return polariton::operation::OperationResponse {
|
||||||
code: 230,
|
code: Self::op_code(),
|
||||||
return_code: 0,
|
return_code: 0,
|
||||||
message: polariton::operation::Typed::Null,
|
message: polariton::operation::Typed::Null,
|
||||||
params: resp_params.into(),
|
params: resp_params.into(),
|
||||||
@@ -26,7 +27,7 @@ impl <C: Send + 'static> Operation<C> for MoreLobbyAuth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
polariton::operation::OperationResponse {
|
polariton::operation::OperationResponse {
|
||||||
code: 230,
|
code: Self::op_code(),
|
||||||
return_code: 120,
|
return_code: 120,
|
||||||
message: polariton::operation::Typed::Null,
|
message: polariton::operation::Typed::Null,
|
||||||
params: std::collections::HashMap::new().into(),
|
params: std::collections::HashMap::new().into(),
|
||||||
|
|||||||
@@ -16,3 +16,4 @@ polariton.workspace = true
|
|||||||
polariton_auth = { version = "*", path = "../polariton_auth" }
|
polariton_auth = { version = "*", path = "../polariton_auth" }
|
||||||
polariton_server.workspace = true
|
polariton_server.workspace = true
|
||||||
rc_core = { version = "*", path = "../rc_core" }
|
rc_core = { version = "*", path = "../rc_core" }
|
||||||
|
async-trait.workspace = true
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
log::debug!("Got cli args {:?}", args);
|
log::debug!("Got cli args {:?}", args);
|
||||||
|
|
||||||
let cubes = rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data");
|
let cubes = rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data");
|
||||||
let users = std::sync::Arc::new(rc_core::persist::user::UserImpl::load(&args.data, &cubes).expect("Bad user data"));
|
let users = std::sync::Arc::new(rc_core::persist::user::UserImpl::load(&args.data, &cubes).await.expect("Bad user data"));
|
||||||
|
|
||||||
let server = std::sync::Arc::new(polariton_server::Server::new(operations::handler(), polariton_server::events::EventsHandler::new()));
|
let server = std::sync::Arc::new(polariton_server::Server::new(operations::handler(), polariton_server::events::EventsHandler::new()));
|
||||||
|
|
||||||
|
|||||||
@@ -7,17 +7,18 @@ impl MoreLobbyAuth {
|
|||||||
const AUTH_PAYLOAD_KEY: u8 = 245;
|
const AUTH_PAYLOAD_KEY: u8 = 245;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
impl <C: Send + 'static> Operation<C> for MoreLobbyAuth {
|
impl <C: Send + 'static> Operation<C> for MoreLobbyAuth {
|
||||||
type User = crate::UserTy;
|
type User = crate::UserTy;
|
||||||
|
|
||||||
fn handle(&self, params: polariton::operation::ParameterTable<C>, user: &Self::User) -> polariton::operation::OperationResponse<C> {
|
async fn handle_async(&self, params: polariton::operation::ParameterTable<C>, user: &Self::User) -> polariton::operation::OperationResponse<C> {
|
||||||
let params_dict = params.to_dict();
|
let params_dict = params.to_dict();
|
||||||
if let Some(Typed::Str(auth_payload)) = params_dict.get(&Self::AUTH_PAYLOAD_KEY) {
|
if let Some(Typed::Str(auth_payload)) = params_dict.get(&Self::AUTH_PAYLOAD_KEY) {
|
||||||
if user.update_with_auth(&auth_payload.string) {
|
if user.update_with_auth(&auth_payload.string).await {
|
||||||
let mut resp_params = std::collections::HashMap::with_capacity(1);
|
let mut resp_params = std::collections::HashMap::with_capacity(1);
|
||||||
resp_params.insert(Self::AUTH_PAYLOAD_KEY, polariton::operation::Typed::Byte(0));
|
resp_params.insert(Self::AUTH_PAYLOAD_KEY, polariton::operation::Typed::Byte(0));
|
||||||
return polariton::operation::OperationResponse {
|
return polariton::operation::OperationResponse {
|
||||||
code: 230,
|
code: Self::op_code(),
|
||||||
return_code: 0,
|
return_code: 0,
|
||||||
message: polariton::operation::Typed::Null,
|
message: polariton::operation::Typed::Null,
|
||||||
params: resp_params.into(),
|
params: resp_params.into(),
|
||||||
@@ -25,7 +26,7 @@ impl <C: Send + 'static> Operation<C> for MoreLobbyAuth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
polariton::operation::OperationResponse {
|
polariton::operation::OperationResponse {
|
||||||
code: 230,
|
code: Self::op_code(),
|
||||||
return_code: 120,
|
return_code: 120,
|
||||||
message: polariton::operation::Typed::Null,
|
message: polariton::operation::Typed::Null,
|
||||||
params: std::collections::HashMap::new().into(),
|
params: std::collections::HashMap::new().into(),
|
||||||
|
|||||||
Reference in New Issue
Block a user