1
0
mirror of https://git.ngram.ca/OpenJam/rc-servers synced 2026-08-23 23:08:52 +00:00

Implement factory in main database, close #80

This commit is contained in:
NG (Graham)
2026-02-01 18:14:16 -05:00
parent a346299de8
commit 3b909327d8
33 changed files with 741 additions and 78 deletions

4
Cargo.lock generated
View File

@@ -2546,9 +2546,13 @@ dependencies = [
name = "oj_rc_database"
version = "1.1.0"
dependencies = [
"async-trait",
"itertools",
"libfj",
"oj_rc_factory",
"sea-orm",
"sea-orm-migration",
"serde_json",
]
[[package]]

View File

@@ -14531,7 +14531,7 @@
},
"factory": {
"adapter": {
"variant": "Arc",
"variant": "BuiltIn",
"uri": "sqlite:../data/robocraft/rc_archive.db?mode=ro"
}
},

View File

@@ -149,8 +149,8 @@ impl std::convert::From<oj_rc_factory::VehicleQueryInfo> for ItemResult {
combat_rating: value.combat_rating,
cpu: value.cpu as i32,
total_robot_ranking: value.total_robot_ranking as i32,
//expiry_date: ticks_from_now(&value.expiry_date),
expiry_date: i32::MAX as _,
expiry_date: ticks_from_now(&value.expiry_date),
//expiry_date: i32::MAX as _,
buyable: value.buyable,
added_by: value.added_by,
added_by_display_name: value.added_by_display_name,

View File

@@ -1,5 +1,6 @@
pub enum Factory {
Arc(oj_rc_factory::arc::ArcAdapter),
Primary(oj_rc_database::FactoryDatabase),
Custom(Box<dyn oj_rc_factory::VehicleFactoryAdapter + Send + Sync + 'static>),
None,
}
@@ -9,6 +10,7 @@ impl oj_rc_factory::VehicleFactoryAdapter for Factory {
async fn vehicle(&self, id: i32) -> Result<Option<(oj_rc_factory::VehicleInfo, oj_rc_factory::VehicleQueryInfo)>, Box<dyn std::error::Error>> {
match self {
Self::Arc(x) => x.vehicle(id).await,
Self::Primary(x) => x.vehicle(id).await,
Self::Custom(x) => x.vehicle(id).await,
Self::None => Ok(None),
}
@@ -17,6 +19,7 @@ impl oj_rc_factory::VehicleFactoryAdapter for Factory {
async fn list(&self, query: libfj::robocraft::ListQuery) -> Result<Vec<oj_rc_factory::VehicleQueryInfo>, Box<dyn std::error::Error>> {
match self {
Self::Arc(x) => x.list(query).await,
Self::Primary(x) => x.list(query).await,
Self::Custom(x) => x.list(query).await,
Self::None => Ok(Vec::default()),
}
@@ -25,6 +28,7 @@ impl oj_rc_factory::VehicleFactoryAdapter for Factory {
async fn upload(&self, vehicle: oj_rc_factory::VehicleUploadInfo) -> Result<oj_rc_factory::VehicleThumbnailInfo, Box<dyn std::error::Error>> {
match self {
Self::Arc(x) => x.upload(vehicle).await,
Self::Primary(x) => x.upload(vehicle).await,
Self::Custom(x) => x.upload(vehicle).await,
Self::None => Ok(oj_rc_factory::VehicleThumbnailInfo {
id: i32::MIN,
@@ -37,6 +41,7 @@ impl oj_rc_factory::VehicleFactoryAdapter for Factory {
async fn rate_vehicle(&self, id: i32, combat: i32, cosmetic: i32) -> Result<(), Box<dyn std::error::Error>> {
match self {
Self::Arc(x) => x.rate_vehicle(id, combat, cosmetic).await,
Self::Primary(x) => x.rate_vehicle(id, combat, cosmetic).await,
Self::Custom(x) => x.rate_vehicle(id, combat, cosmetic).await,
Self::None => Ok(()),
}
@@ -45,6 +50,7 @@ impl oj_rc_factory::VehicleFactoryAdapter for Factory {
async fn purchase(&self, id: i32) -> Result<(), Box<dyn std::error::Error>> {
match self {
Self::Arc(x) => x.purchase(id).await,
Self::Primary(x) => x.purchase(id).await,
Self::Custom(x) => x.purchase(id).await,
Self::None => Ok(()),
}
@@ -53,14 +59,25 @@ impl oj_rc_factory::VehicleFactoryAdapter for Factory {
async fn update_vehicle(&self, id: i32, cube_data: Option<Vec<u8>>, colour_data: Option<Vec<u8>>) -> Result<(), Box<dyn std::error::Error>> {
match self {
Self::Arc(x) => x.update_vehicle(id, cube_data, colour_data).await,
Self::Primary(x) => x.update_vehicle(id, cube_data, colour_data).await,
Self::Custom(x) => x.update_vehicle(id, cube_data, colour_data).await,
Self::None => Ok(()),
}
}
async fn remove_vehicle(&self, id: i32, user_id: i32) -> Result<(), Box<dyn std::error::Error>> {
match self {
Self::Arc(x) => x.remove_vehicle(id, user_id).await,
Self::Primary(x) => x.remove_vehicle(id, user_id).await,
Self::Custom(x) => x.remove_vehicle(id, user_id).await,
Self::None => Ok(()),
}
}
async fn set_featured(&self, id: i32, is_featured: bool) -> Result<(), Box<dyn std::error::Error>> {
match self {
Self::Arc(x) => x.set_featured(id, is_featured).await,
Self::Primary(x) => x.set_featured(id, is_featured).await,
Self::Custom(x) => x.set_featured(id, is_featured).await,
Self::None => Ok(()),
}
@@ -68,9 +85,10 @@ impl oj_rc_factory::VehicleFactoryAdapter for Factory {
}
impl Factory {
pub async fn from_config(conf: &crate::persist::FactoryConfig, settings: &crate::persist::config::ServerConfig) -> Result<Self, Box<dyn std::error::Error + 'static>> {
pub async fn from_config(conf: &crate::persist::FactoryConfig, settings: &crate::persist::config::ServerConfig, builtin_factory_provider: &(dyn (Fn() -> oj_rc_database::FactoryDatabase) + Sync)) -> Result<Self, Box<dyn std::error::Error + 'static>> {
Ok(match &conf.adapter {
crate::persist::AdapterSettings::Arc(x) => Self::Arc(oj_rc_factory::arc::ArcAdapter::init(&x.uri, x.show_expired, settings.cdn_url.to_owned(), x.override_cdn, x.spoof_username).await?),
crate::persist::AdapterSettings::BuiltIn => Self::Primary(builtin_factory_provider()),
crate::persist::AdapterSettings::None => Self::None,
})
}

View File

@@ -310,8 +310,8 @@ impl <C: Clone + Send> super::ConfigProvider<C> for CubeConfig {
}
}
async fn factory(&self) -> Result<crate::factory::Factory, Box<dyn std::error::Error + 'static>> {
crate::factory::Factory::from_config(&self.factory, &<Self as super::ConfigProvider<()>>::server_config(self)).await
async fn factory(&self, builtin_factory_provider: &(dyn (Fn() -> oj_rc_database::FactoryDatabase) + Sync)) -> Result<crate::factory::Factory, Box<dyn std::error::Error + 'static>> {
crate::factory::Factory::from_config(&self.factory, &<Self as super::ConfigProvider<()>>::server_config(self), builtin_factory_provider).await
}
fn cubes(&self) -> &'_ indexmap::IndexMap<String, crate::persist::Cube> {

View File

@@ -20,7 +20,7 @@ pub trait ConfigProvider<C: Clone> {
fn public_channels(&self) -> Typed<C>;
fn server_config(&self) -> ServerConfig;
fn garage_upgrades(&self) -> GarageUpgrades;
async fn factory(&self) -> Result<crate::factory::Factory, Box<dyn std::error::Error + 'static>>;
async fn factory(&self, builtin_factory_provider: &(dyn (Fn() -> oj_rc_database::FactoryDatabase) + Sync)) -> Result<crate::factory::Factory, Box<dyn std::error::Error + 'static>>;
fn cubes(&self) -> &'_ indexmap::IndexMap<String, crate::persist::Cube>;
fn chat_system_config(&self) -> ChatSystemConfig;
fn gamemode_events(&self) -> GameEventSequence;

View File

@@ -41,6 +41,10 @@ impl AccountProvider {
})
}
pub fn factory_impl(&self) -> oj_rc_database::FactoryDatabase {
self.db.vehicle_factory(self.cdn.clone())
}
pub async fn multiplayer_init(&self) -> Result<(), oj_rc_database::sea_orm::DbErr> {
self.db.complete_all_games().await
}

View File

@@ -6,6 +6,10 @@ fn u64_negative_on_err<E>(result: Result<u64, E>) -> i64 {
#[async_trait::async_trait]
impl super::CommonUser for UserData {
fn account_id(&self) -> i32 {
self.account.id
}
fn public_id(&self) -> &'_ str {
&self.account.public_id
}

View File

@@ -23,6 +23,8 @@ impl super::FactoryUser for UserData {
thumbnail: vehicle.thumbnail,
added_by: self.account.public_id.clone(),
added_by_display_name: self.account.display_name.clone(),
added_by_id: self.account.id,
garage_id: slot.id,
cpu: slot.total_robot_cpu as u32,
total_robot_ranking: slot.total_robot_ranking as u32,
build_version: vehicle.version,

View File

@@ -410,6 +410,7 @@ pub struct ResolvedVehicle {
#[async_trait::async_trait]
pub trait CommonUser: Send + Sync {
fn account_id(&self) -> i32;
async fn resolve_config_vehicle(&self, vehicle: &crate::persist::config::VehicleInfo, factory: &dyn oj_rc_factory::VehicleFactoryAdapter, weapon_order: &crate::cubes::WeaponListParser, cpu_counter: &crate::cubes::CpuListParser) -> Result<ResolvedVehicle, polariton_server::operations::SimpleOpError>;
fn public_id(&self) -> &'_ str;
fn is_mod(&self) -> bool;

View File

@@ -23,6 +23,8 @@ fn default_variant() -> AdapterSettings {
pub enum AdapterSettings {
#[serde(alias = "sqlite")]
Arc(ArcFactorySettings),
#[serde(alias = "integrated")]
BuiltIn,
None,
}

View File

@@ -12,9 +12,14 @@ all_databases = ["mysql", "postgres", "sqlite"]
mysql = [ "sea-orm/sqlx-mysql" ]
postgres = [ "sea-orm/sqlx-postgres" ]
sqlite = [ "sea-orm/sqlx-sqlite" ]
default = [ "all_databases" ]
default = [ "all_databases", "factory" ]
factory = [ "oj_rc_factory", "libfj", "async-trait", "sea-orm/with-chrono", "serde_json" ]
[dependencies]
sea-orm = { version = "1.1.10", features = [ "runtime-tokio-rustls", "macros" ] }
sea-orm-migration = "1.1.10"
itertools = "0.14"
oj_rc_factory = { version = "*", path = "../rc_factory", optional = true }
async-trait = { workspace = true, optional = true }
libfj = { workspace = true, optional = true }
serde_json = { workspace = true, optional = true }

View File

@@ -0,0 +1,368 @@
use oj_rc_factory::{VehicleFactoryAdapter, VehicleInfo, VehicleQueryInfo, VehicleThumbnailInfo, VehicleUploadInfo};
use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect, sqlx::types::chrono, ActiveModelTrait, TransactionTrait};
pub struct FactoryDatabase {
orm: std::sync::Arc<sea_orm::DatabaseConnection>,
cdn: std::sync::Arc<String>,
upload_duration: std::time::Duration,
}
impl FactoryDatabase {
pub async fn init(uri: &str, cdn: std::sync::Arc<String>) -> Result<Self, sea_orm::DbErr>{
let db = sea_orm::Database::connect(uri).await?;
Ok(Self {
orm: std::sync::Arc::new(db),
cdn,
upload_duration: std::time::Duration::from_secs(365 * 24 * 60 * 60), // 1 year
})
}
pub fn init_preconnected(orm: std::sync::Arc<sea_orm::DatabaseConnection>, cdn: std::sync::Arc<String>) -> Self {
Self {
orm,
cdn,
upload_duration: std::time::Duration::from_secs(365 * 24 * 60 * 60), // 1 year
}
}
async fn full_vehicle_by_id(&self, id: i32) -> Result<Option<(crate::schema::factory::vehicle::Model, crate::schema::user::Model)>, sea_orm::DbErr> {
Ok(crate::schema::factory::vehicle::Entity::find_by_id(id)
.find_also_related(crate::schema::user::Entity)
.one(self.orm.as_ref())
.await?
.and_then(|(vehicle, user_opt)| user_opt.map(|user| (vehicle, user))))
}
async fn list_query(&self, query: libfj::robocraft::ListQuery) -> Result<Vec<(crate::schema::factory::vehicle_query::VehicleInfo, crate::schema::factory::vehicle_query::VehicleOwnerInfo)>, sea_orm::DbErr> {
let mut query_builder = crate::schema::factory::vehicle::Entity::find()
.filter(crate::schema::factory::vehicle::Column::RemovedDate.is_null())
.filter(crate::schema::factory::vehicle::Column::BanDate.is_null())
.filter(crate::schema::factory::vehicle::Column::Buyable.eq(true));
match query.order {
libfj::robocraft::FactoryOrderType::Suggested => { query_builder = query_builder.order_by_desc(crate::schema::factory::vehicle::Column::GetCount); },
libfj::robocraft::FactoryOrderType::CombatRating => { query_builder = query_builder.order_by_desc(crate::schema::factory::vehicle::Column::CombatRating); },
libfj::robocraft::FactoryOrderType::CosmeticRating => { query_builder = query_builder.order_by_desc(crate::schema::factory::vehicle::Column::CosmeticRating); },
libfj::robocraft::FactoryOrderType::Added => { query_builder = query_builder.order_by_desc(crate::schema::factory::vehicle::Column::AddedTime); },
libfj::robocraft::FactoryOrderType::CPU => { query_builder = query_builder.order_by_desc(crate::schema::factory::vehicle::Column::Cpu); },
libfj::robocraft::FactoryOrderType::MostBought => { query_builder = query_builder.order_by_desc(crate::schema::factory::vehicle::Column::GetCount); },
}
if !query.text_filter.is_empty() {
let query_text = format!("%{}%", query.text_filter.replace('%', ""));
if query.player_filter {
query_builder = query_builder.filter(
sea_orm::sea_query::Condition::any()
.add(crate::schema::user::Column::PublicId.like(query_text.clone()))
.add(crate::schema::user::Column::DisplayName.like(query_text))
);
} else {
match query.text_search_field {
libfj::robocraft::FactoryTextSearchField::All => {
query_builder = query_builder.filter(
sea_orm::sea_query::Condition::any()
.add(crate::schema::user::Column::PublicId.like(query_text.clone()))
.add(crate::schema::user::Column::DisplayName.like(query_text.clone()))
.add(crate::schema::factory::vehicle::Column::Name.like(query_text.clone()))
.add(crate::schema::factory::vehicle::Column::Description.like(query_text))
);
},
libfj::robocraft::FactoryTextSearchField::Name => {
query_builder = query_builder.filter(
sea_orm::sea_query::Condition::any()
.add(crate::schema::factory::vehicle::Column::Name.like(query_text.clone()))
);
},
libfj::robocraft::FactoryTextSearchField::Player => {
query_builder = query_builder.filter(
sea_orm::sea_query::Condition::any()
.add(crate::schema::user::Column::PublicId.like(query_text.clone()))
.add(crate::schema::user::Column::DisplayName.like(query_text))
);
},
}
}
}
// TODO support movement and weapon filters
if query.minimum_cpu > 0 {
query_builder = query_builder.filter(crate::schema::factory::vehicle::Column::Cpu.gte(query.minimum_cpu as u32));
}
if query.maximum_cpu < usize::MAX {
query_builder = query_builder.filter(crate::schema::factory::vehicle::Column::Cpu.lte(query.maximum_cpu as u32));
}
let query_builder = query_builder
.find_also_related(crate::schema::user::Entity)
.select_only()
// WARNING: jank sea_orm query generation (fix)
.column_as(crate::schema::factory::vehicle::Column::Id, "A_id")
.column_as(crate::schema::factory::vehicle::Column::UserId, "A_user_id")
.column_as(crate::schema::factory::vehicle::Column::CreationTime, "A_creation_time")
.column_as(crate::schema::factory::vehicle::Column::Name, "A_name")
.column_as(crate::schema::factory::vehicle::Column::Description, "A_description")
.column_as(crate::schema::factory::vehicle::Column::AddedTime, "A_added_time")
.column_as(crate::schema::factory::vehicle::Column::ExpiryTime, "A_expiry_time")
.column_as(crate::schema::factory::vehicle::Column::Cpu, "A_cpu")
.column_as(crate::schema::factory::vehicle::Column::TotalRobotRanking, "A_total_robot_ranking")
.column_as(crate::schema::factory::vehicle::Column::GetCount, "A_get_count")
.column_as(crate::schema::factory::vehicle::Column::Buyable, "A_buyable")
.column_as(crate::schema::factory::vehicle::Column::RemovedDate, "A_removed_date")
.column_as(crate::schema::factory::vehicle::Column::BanDate, "A_ban_date")
.column_as(crate::schema::factory::vehicle::Column::Featured, "A_featured")
.column_as(crate::schema::factory::vehicle::Column::BannerMessage, "A_banner_message")
.column_as(crate::schema::factory::vehicle::Column::CombatRating, "A_combat_rating")
.column_as(crate::schema::factory::vehicle::Column::CosmeticRating, "A_cosmetic_rating")
.column_as(crate::schema::factory::vehicle::Column::CubeAmounts, "A_cube_amounts")
.column_as(crate::schema::user::Column::PublicId, "B_public_id")
.column_as(crate::schema::user::Column::DisplayName, "B_display_name")
.into_model::<crate::schema::factory::vehicle_query::VehicleInfo, crate::schema::factory::vehicle_query::VehicleOwnerInfo>();
//.into_partial_model::<crate::schema::factory::vehicle_query::VehicleInfo, crate::schema::factory::vehicle_query::VehicleOwnerInfo>();
let pagination = query_builder.paginate(self.orm.as_ref(), query.page_size as u64);
Ok(pagination.fetch_page((query.page - 1) as u64).await?
.into_iter()
.filter_map(|(vehicle, user_opt)| user_opt.map(|user| (vehicle, user)))
.collect())
}
fn thumbnail_url(&self, id: i32) -> String {
format!("{}/roboshop/Live/{}", self.cdn, id)
}
fn calculate_cube_amounts(&self, data: &[u8]) -> String {
let mut counts: std::collections::HashMap<u32, u32> = std::collections::HashMap::new();
let data = &data[4..];
for chunk in data.chunks_exact(8) {
let id_bytes: [u8; 4] = chunk[0..4].try_into().unwrap();
let part_id = u32::from_le_bytes(id_bytes);
*counts.entry(part_id).or_insert(0) += 1;
}
let mut str_map: std::collections::HashMap<String, u32> = std::collections::HashMap::new();
for (k, v) in counts {
str_map.insert(k.to_string(), v);
}
serde_json::to_string(&str_map).unwrap_or_else(|_| "{}".to_string())
}
}
#[async_trait::async_trait]
impl VehicleFactoryAdapter for FactoryDatabase {
async fn vehicle(&self, id: i32) -> Result<Option<(VehicleInfo, VehicleQueryInfo)>, Box<dyn std::error::Error>> {
let entities = self.full_vehicle_by_id(id).await?;
Ok(entities.map(|(vehicle, user)| (
VehicleInfo {
id,
cube_data: vehicle.cube_data,
colour_data: vehicle.colour_data,
},
VehicleQueryInfo {
id,
name: vehicle.name,
description: vehicle.description,
thumbnail: self.thumbnail_url(id),
added_by: user.public_id,
added_by_display_name: user.display_name,
added_date: vehicle.added_time.and_utc(),
expiry_date: vehicle.expiry_time.and_utc(),
cpu: vehicle.cpu as _,
total_robot_ranking: vehicle.total_robot_ranking as _,
rent_count: vehicle.get_count as _,
buy_count: vehicle.get_count as _,
removed_date: vehicle.removed_date.map(|t| t.and_utc()),
ban_date: vehicle.ban_date.map(|t| t.and_utc()),
featured: vehicle.featured,
buyable: vehicle.buyable,
banner_message: vehicle.banner_message,
combat_rating: vehicle.combat_rating,
cosmetic_rating: vehicle.cosmetic_rating,
cube_amounts: serde_json::from_str(&vehicle.cube_amounts).unwrap_or_default(),
},
)))
}
async fn list(&self, query: libfj::robocraft::ListQuery) -> Result<Vec<VehicleQueryInfo>, Box<dyn std::error::Error>> {
let entities = self.list_query(query).await?;
Ok(entities.into_iter().map(|(vehicle, user)| VehicleQueryInfo {
id: vehicle.id,
name: vehicle.name,
description: vehicle.description,
thumbnail: self.thumbnail_url(vehicle.id),
added_by: user.public_id,
added_by_display_name: user.display_name,
added_date: vehicle.added_time.and_utc(),
expiry_date: vehicle.expiry_time.and_utc(),
cpu: vehicle.cpu as _,
total_robot_ranking: vehicle.total_robot_ranking as _,
rent_count: vehicle.get_count as _,
buy_count: vehicle.get_count as _,
removed_date: vehicle.removed_date.map(|t| t.and_utc()),
ban_date: vehicle.ban_date.map(|t| t.and_utc()),
featured: vehicle.featured,
buyable: vehicle.buyable,
banner_message: vehicle.banner_message,
combat_rating: vehicle.combat_rating,
cosmetic_rating: vehicle.cosmetic_rating,
cube_amounts: serde_json::from_str(&vehicle.cube_amounts).unwrap_or_default(),
}).collect())
}
async fn upload(&self, vehicle: VehicleUploadInfo) -> Result<VehicleThumbnailInfo, Box<dyn std::error::Error>> {
let now = chrono::Utc::now();
let entity = crate::schema::factory::vehicle::ActiveModel {
id: sea_orm::ActiveValue::NotSet,
user_id: sea_orm::ActiveValue::Set(vehicle.added_by_id),
garage_id: sea_orm::ActiveValue::Set(vehicle.garage_id),
creation_time: sea_orm::ActiveValue::Set(now.timestamp()),
name: sea_orm::ActiveValue::Set(vehicle.name),
description: sea_orm::ActiveValue::Set(vehicle.description),
added_time: sea_orm::ActiveValue::Set(now.naive_utc()),
expiry_time: sea_orm::ActiveValue::Set((now + self.upload_duration).naive_utc()),
cpu: sea_orm::ActiveValue::Set(vehicle.cpu as i32),
total_robot_ranking: sea_orm::ActiveValue::Set(vehicle.total_robot_ranking as i32),
get_count: sea_orm::ActiveValue::Set(0),
buyable: sea_orm::ActiveValue::Set(true),
removed_date: sea_orm::ActiveValue::Set(None),
ban_date: sea_orm::ActiveValue::Set(None),
featured: sea_orm::ActiveValue::Set(false),
banner_message: sea_orm::ActiveValue::Set(None),
combat_rating: sea_orm::ActiveValue::Set(2.5),
cosmetic_rating: sea_orm::ActiveValue::Set(2.5),
cube_amounts: sea_orm::ActiveValue::Set(self.calculate_cube_amounts(&vehicle.cube_data)),
cube_data: sea_orm::ActiveValue::Set(vehicle.cube_data),
colour_data: sea_orm::ActiveValue::Set(vehicle.colour_data),
};
let saved_entity = entity.insert(self.orm.as_ref()).await?;
Ok(VehicleThumbnailInfo {
id: saved_entity.id,
thumbnail: vehicle.thumbnail,
needs_upload: true,
})
}
async fn rate_vehicle(&self, id: i32, combat: i32, cosmetic: i32) -> Result<(), Box<dyn std::error::Error>> {
let result = self.orm.as_ref().transaction::<'_, '_, _, (), sea_orm::DbErr>(|txn| {
Box::pin(async move {
let select_q = crate::schema::factory::vehicle::Entity::find_by_id(id)
.select_only()
.columns([
crate::schema::factory::vehicle::Column::CombatRating,
crate::schema::factory::vehicle::Column::CosmeticRating,
crate::schema::factory::vehicle::Column::GetCount,
])
.into_tuple::<(f64, f64, i32)>();
if let Some((curr_combat_rating, curr_cosmetic_rating, get_count)) = select_q.one(txn).await? {
let get_count = get_count.clamp(1, 100) as f64;
let next_combat_rating = curr_combat_rating + (((combat as f64) - curr_combat_rating) / get_count);
let next_cosmetic_rating = curr_cosmetic_rating + (((cosmetic as f64) - curr_cosmetic_rating) / get_count);
crate::schema::factory::vehicle::ActiveModel {
id: sea_orm::ActiveValue::Set(id),
cosmetic_rating: sea_orm::ActiveValue::Set(next_cosmetic_rating),
combat_rating: sea_orm::ActiveValue::Set(next_combat_rating),
..Default::default()
}.update(txn).await?;
}
Ok(())
})
}).await;
if let Err(e) = result {
return match e {
sea_orm::TransactionError::Connection(x) => Err(x.into()),
sea_orm::TransactionError::Transaction(x) => Err(x.into()),
};
}
Ok(())
}
/// Just update any purchase trackers
async fn purchase(&self, id: i32) -> Result<(), Box<dyn std::error::Error>> {
crate::schema::factory::vehicle::Entity::update_many()
.col_expr(crate::schema::factory::vehicle::Column::GetCount, crate::schema::factory::vehicle::Column::GetCount.into_expr().add(1))
.filter(crate::schema::factory::vehicle::Column::Id.eq(id))
.exec(self.orm.as_ref()).await?;
Ok(())
}
async fn update_vehicle(&self, id: i32, cube_data: Option<Vec<u8>>, colour_data: Option<Vec<u8>>) -> Result<(), Box<dyn std::error::Error>> {
let cube_amounts = if let Some(cube_data) = &cube_data {
sea_orm::ActiveValue::Set(self.calculate_cube_amounts(cube_data))
} else {
sea_orm::ActiveValue::NotSet
};
let result = self.orm.as_ref().transaction::<'_, '_, _, (), sea_orm::DbErr>(|txn| {
Box::pin(async move {
let select_q = crate::schema::factory::vehicle::Entity::find_by_id(id)
.select_only()
.columns([
crate::schema::factory::vehicle::Column::Id,
]);
if select_q.count(txn).await? == 1 {
let mut to_update = crate::schema::factory::vehicle::ActiveModel {
id: sea_orm::ActiveValue::Set(id),
cube_amounts,
..Default::default()
};
if let Some(cube_data) = cube_data {
to_update.cube_data = sea_orm::ActiveValue::Set(cube_data);
}
if let Some(colour_data) = colour_data {
to_update.colour_data = sea_orm::ActiveValue::Set(colour_data);
}
to_update.update(txn).await?;
}
Ok(())
})
}).await;
if let Err(e) = result {
return match e {
sea_orm::TransactionError::Connection(x) => Err(x.into()),
sea_orm::TransactionError::Transaction(x) => Err(x.into()),
};
}
Ok(())
}
async fn remove_vehicle(&self, id: i32, user_id: i32) -> Result<(), Box<dyn std::error::Error>> {
let now = chrono::Utc::now();
let result = self.orm.as_ref().transaction::<'_, '_, _, (), sea_orm::DbErr>(|txn| {
Box::pin(async move {
let select_q = crate::schema::factory::vehicle::Entity::find_by_id(id)
.select_only()
.columns([
crate::schema::factory::vehicle::Column::Id,
crate::schema::factory::vehicle::Column::UserId,
])
.into_tuple::<(i32, i32)>();
if let Some((_selected_id, selected_user_id)) = select_q.one(txn).await? {
if selected_user_id != user_id {
//log::warn!("User {} tried to remove factory vehicle owned by {}", user_id, selected_user_id);
return Ok(());
}
crate::schema::factory::vehicle::ActiveModel {
id: sea_orm::ActiveValue::Set(id),
buyable: sea_orm::ActiveValue::Set(false),
removed_date: sea_orm::ActiveValue::Set(Some(now.naive_utc())),
..Default::default()
}.update(txn).await?;
}
Ok(())
})
}).await;
if let Err(e) = result {
return match e {
sea_orm::TransactionError::Connection(x) => Err(x.into()),
sea_orm::TransactionError::Transaction(x) => Err(x.into()),
};
}
Ok(())
}
async fn set_featured(&self, id: i32, is_featured: bool) -> Result<(), Box<dyn std::error::Error>> {
crate::schema::factory::vehicle::ActiveModel {
id: sea_orm::ActiveValue::Set(id),
featured: sea_orm::ActiveValue::Set(is_featured),
..Default::default()
}.update(self.orm.as_ref()).await?;
Ok(())
}
}

View File

@@ -9,4 +9,9 @@ pub use wrapper::Database;
mod metrics;
pub use metrics::DatabaseMetrics;
#[cfg(feature = "factory")]
mod factory_wrapper;
#[cfg(feature = "factory")]
pub use factory_wrapper::FactoryDatabase;
pub use sea_orm;

View File

@@ -0,0 +1,69 @@
use sea_orm_migration::prelude::*;
pub struct Migration;
impl MigrationName for Migration {
fn name(&self) -> &str {
"m20260126_000001_create_factory_vehicle_table"
}
}
#[async_trait::async_trait]
impl MigrationTrait for Migration {
// Define how to apply this migration: Create the Factory Vehicles table.
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(crate::schema::factory::vehicle::Entity)
.col(
ColumnDef::new(crate::schema::factory::vehicle::Column::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::UserId).integer().not_null())
.foreign_key(
ForeignKey::create()
.name("fk-factory_vehicles-user_id")
.from(crate::schema::factory::vehicle::Entity, crate::schema::factory::vehicle::Column::UserId)
.to(crate::schema::user::Entity, crate::schema::user::Column::Id),
)
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::GarageId).integer().not_null())
.foreign_key(
ForeignKey::create()
.name("fk-factory_vehicles-garage_id")
.from(crate::schema::factory::vehicle::Entity, crate::schema::factory::vehicle::Column::GarageId)
.to(crate::schema::garage::Entity, crate::schema::garage::Column::Id),
)
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::CreationTime).big_integer().not_null())
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::Name).string().not_null())
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::Description).string().not_null())
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::AddedTime).date_time().not_null())
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::ExpiryTime).date_time().not_null())
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::Cpu).integer().not_null())
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::TotalRobotRanking).integer().not_null())
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::GetCount).integer().not_null())
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::Buyable).boolean().not_null())
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::RemovedDate).date_time().null())
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::BanDate).date_time().null())
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::Featured).boolean().not_null())
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::BannerMessage).string().null())
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::CombatRating).double().not_null())
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::CosmeticRating).double().not_null())
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::CubeAmounts).string().not_null())
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::CubeData).binary().not_null())
.col(ColumnDef::new(crate::schema::factory::vehicle::Column::ColourData).binary().not_null())
.to_owned(),
)
.await
}
// Define how to rollback this migration: Drop the Factory Vehicles table.
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(crate::schema::factory::vehicle::Entity).to_owned())
.await
}
}

View File

@@ -13,6 +13,8 @@ mod m20250722_000001_create_game_event_table;
mod m20250816_000001_add_fake_players;
mod m20250918_000001_add_player_variant;
mod m20251228_000001_create_score_table;
#[cfg(feature = "factory")]
mod m20260126_000001_create_factory_vehicle_table;
pub struct Migrator;
@@ -33,6 +35,8 @@ impl MigratorTrait for Migrator {
Box::new(m20250816_000001_add_fake_players::Migration),
Box::new(m20250918_000001_add_player_variant::Migration),
Box::new(m20251228_000001_create_score_table::Migration),
#[cfg(feature = "factory")]
Box::new(m20260126_000001_create_factory_vehicle_table::Migration),
]
}
}

View File

@@ -0,0 +1,2 @@
pub mod vehicle;
pub mod vehicle_query;

View File

@@ -0,0 +1,58 @@
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, DeriveEntityModel)]
#[sea_orm(table_name = "factory_vehicles")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub user_id: i32,
pub garage_id: i32,
pub creation_time: i64, // seconds since unix epoch
pub name: String,
pub description: String,
pub added_time: DateTime,
pub expiry_time: DateTime,
pub cpu: i32,
pub total_robot_ranking: i32,
pub get_count: i32,
pub buyable: bool,
pub removed_date: Option<DateTime>,
pub ban_date: Option<DateTime>,
pub featured: bool,
pub banner_message: Option<String>,
pub combat_rating: f64,
pub cosmetic_rating: f64,
pub cube_amounts: String,
pub cube_data: Vec<u8>,
pub colour_data: Vec<u8>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::super::user::Entity",
from = "Column::UserId",
to = "super::super::user::Column::Id"
)]
User,
#[sea_orm(
belongs_to = "super::super::garage::Entity",
from = "Column::UserId",
to = "super::super::garage::Column::Id"
)]
Garage,
}
impl Related<super::super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl Related<super::super::garage::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@@ -0,0 +1,32 @@
use sea_orm::{DerivePartialModel, FromQueryResult};
use sea_orm::entity::prelude::DateTime;
#[derive(FromQueryResult, DerivePartialModel)]
#[sea_orm(entity = "crate::schema::factory::vehicle::Entity")]
pub struct VehicleInfo {
pub id: i32,
pub user_id: i32,
pub creation_time: i64, // seconds since unix epoch
pub name: String,
pub description: String,
pub added_time: DateTime,
pub expiry_time: DateTime,
pub cpu: i32,
pub total_robot_ranking: i32,
pub get_count: i32,
pub buyable: bool,
pub removed_date: Option<DateTime>,
pub ban_date: Option<DateTime>,
pub featured: bool,
pub banner_message: Option<String>,
pub combat_rating: f64,
pub cosmetic_rating: f64,
pub cube_amounts: String,
}
#[derive(FromQueryResult, DerivePartialModel)]
#[sea_orm(entity = "crate::schema::user::Entity")]
pub struct VehicleOwnerInfo {
pub public_id: String,
pub display_name: String,
}

View File

@@ -54,6 +54,8 @@ pub enum Relation {
to = "super::user::Column::Id"
)]
User,
#[sea_orm(has_many = "super::factory::vehicle::Entity")]
FactoryUploads,
}
impl Related<super::user::Entity> for Entity {
@@ -62,6 +64,12 @@ impl Related<super::user::Entity> for Entity {
}
}
impl Related<super::factory::vehicle::Entity> for Entity {
fn to() -> RelationDef {
Relation::FactoryUploads.def()
}
}
impl ActiveModelBehavior for ActiveModel {}
#[repr(i32)]

View File

@@ -10,6 +10,8 @@ pub mod multiplayer_game;
pub mod multiplayer_game_player;
pub mod game_event;
pub mod multiplayer_game_score;
#[cfg(feature = "factory")]
pub mod factory;
pub fn parse_int_csv(s: &str) -> Vec<u32> {
s.split(',').filter_map(|i_as_s| {

View File

@@ -25,6 +25,8 @@ pub enum Relation {
Campaigns,
#[sea_orm(has_many = "super::multiplayer_game_player::Entity")]
Player,
#[sea_orm(has_many = "super::factory::vehicle::Entity")]
FactoryUploads,
}
impl Related<super::permissions::Entity> for Entity {
@@ -57,4 +59,10 @@ impl Related<super::multiplayer_game_player::Entity> for Entity {
}
}
impl Related<super::factory::vehicle::Entity> for Entity {
fn to() -> RelationDef {
Relation::FactoryUploads.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@@ -2,7 +2,7 @@ use sea_orm_migration::MigratorTrait;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect, TransactionTrait};
pub struct Database {
orm: sea_orm::DatabaseConnection,
orm: std::sync::Arc<sea_orm::DatabaseConnection>,
metrics: std::sync::Arc<std::sync::Mutex<super::metrics::MetricsState>>,
}
@@ -14,14 +14,18 @@ impl Database {
//let schema_manager = SchemaManager::new(&db);
super::Migrator::up(&db, None).await?;
Ok(Self {
orm: db,
orm: std::sync::Arc::new(db),
metrics: metrics_data,
})
}
pub fn vehicle_factory(&self, cdn: std::sync::Arc<String>) -> super::FactoryDatabase {
super::FactoryDatabase::init_preconnected(self.orm.clone(), cdn)
}
pub async fn user_count(&self) -> Result<u64, sea_orm::DbErr> {
crate::schema::user::Entity::find()
.count(&self.orm)
.count(self.orm.as_ref())
.await
}
@@ -31,14 +35,14 @@ impl Database {
sea_orm::sea_query::Func::lower(crate::schema::user::Column::DisplayName.into_expr())
).eq(public_id.to_lowercase())
)
.one(&self.orm)
.one(self.orm.as_ref())
.await
}
pub async fn user_by_steam_id(&self, steam_id: u64) -> Result<Option<crate::schema::user::Model>, sea_orm::DbErr> {
crate::schema::user::Entity::find()
.filter(crate::schema::user::Column::SteamId.eq(Some(steam_id.to_string())))
.one(&self.orm)
.one(self.orm.as_ref())
.await
}
@@ -48,7 +52,7 @@ impl Database {
sea_orm::sea_query::Func::lower(crate::schema::user::Column::Email.into_expr())
).eq(email.to_lowercase())
)
.one(&self.orm)
.one(self.orm.as_ref())
.await
}
@@ -67,13 +71,13 @@ impl Database {
}
pub async fn insert_user(&self, entity: crate::schema::user::ActiveModel) -> Result<crate::schema::user::Model, sea_orm::DbErr> {
entity.insert(&self.orm).await
entity.insert(self.orm.as_ref()).await
}
pub async fn user_aux_by_user_id(&self, user_id: i32) -> 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)
.all(self.orm.as_ref())
.await
}
@@ -81,12 +85,12 @@ impl Database {
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)
.one(self.orm.as_ref())
.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?;
crate::schema::user_aux::Entity::insert_many(entities.into_iter()).exec(self.orm.as_ref()).await?;
Ok(())
}
@@ -97,13 +101,13 @@ impl Database {
.filter(crate::schema::user_aux::Column::UserId.eq(user_id))
.filter(crate::schema::user_aux::Column::Descriptor.eq(descriptor.clone()))
.into_model::<crate::schema::common_query::Id>()
.one(&self.orm)
.one(self.orm.as_ref())
.await?;
if let Some(id) = id_opt {
// update
entity.id = sea_orm::ActiveValue::Set(id.id);
Ok(Some(crate::schema::user_aux::Entity::update(entity)
.exec(&self.orm)
.exec(self.orm.as_ref())
.await?))
} else {
Ok(None)
@@ -142,12 +146,12 @@ impl Database {
pub async fn perms_by_user_id(&self, user_id: i32) -> 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)
.one(self.orm.as_ref())
.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
entity.insert(self.orm.as_ref()).await
}
pub async fn update_perms_by_user_id(&self, mut entity: crate::schema::permissions::ActiveModel, user_id: i32) -> Result<Option<crate::schema::permissions::Model>, sea_orm::DbErr> {
@@ -156,12 +160,12 @@ impl Database {
.column(crate::schema::permissions::Column::Id)
.filter(crate::schema::permissions::Column::UserId.eq(user_id))
.into_model::<crate::schema::common_query::Id>()
.one(&self.orm)
.one(self.orm.as_ref())
.await?;
if let Some(id) = id_opt {
entity.id = sea_orm::ActiveValue::Set(id.id);
Ok(Some(crate::schema::permissions::Entity::update(entity)
.exec(&self.orm)
.exec(self.orm.as_ref())
.await?))
} else {
Ok(None)
@@ -170,7 +174,7 @@ impl Database {
pub async fn garage_count(&self) -> Result<u64, sea_orm::DbErr> {
crate::schema::garage::Entity::find()
.count(&self.orm)
.count(self.orm.as_ref())
.await
}
@@ -180,7 +184,7 @@ impl Database {
.column_as(crate::schema::garage::Column::Slot.max(), "column")
.filter(crate::schema::garage::Column::UserId.eq(user_id))
.into_model::<crate::schema::common_query::SingleColumn<i32>>()
.one(&self.orm)
.one(self.orm.as_ref())
.await?;
Ok(result.map(|x| *x).unwrap_or(0))
}
@@ -189,7 +193,7 @@ impl Database {
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)
.one(self.orm.as_ref())
.await
}
@@ -197,7 +201,7 @@ impl Database {
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)
.one(self.orm.as_ref())
.await
}
@@ -205,35 +209,35 @@ impl Database {
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)
.all(self.orm.as_ref())
.await
}
pub async fn garage_by_uuid(&self, uuid: i64) -> Result<Option<crate::schema::garage::Model>, sea_orm::DbErr> {
crate::schema::garage::Entity::find()
.filter(crate::schema::garage::Column::Uuid.eq(uuid))
.one(&self.orm)
.one(self.orm.as_ref())
.await
}
pub async fn garage_by_id(&self, id: i32) -> Result<Option<crate::schema::garage::Model>, sea_orm::DbErr> {
crate::schema::garage::Entity::find_by_id(id)
.one(&self.orm)
.one(self.orm.as_ref())
.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?;
crate::schema::garage::Entity::insert_many(entities.into_iter()).exec(self.orm.as_ref()).await?;
Ok(())
}
pub async fn insert_garage(&self, entity: crate::schema::garage::ActiveModel) -> Result<crate::schema::garage::Model, sea_orm::DbErr> {
entity.insert(&self.orm).await
entity.insert(self.orm.as_ref()).await
}
pub async fn update_garage(&self, entity: crate::schema::garage::ActiveModel) -> Result<crate::schema::garage::Model, sea_orm::DbErr> {
crate::schema::garage::Entity::update(entity)
.exec(&self.orm)
.exec(self.orm.as_ref())
.await
}
@@ -244,12 +248,12 @@ impl Database {
.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)
.one(self.orm.as_ref())
.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)
.exec(self.orm.as_ref())
.await?))
} else {
Ok(None)
@@ -262,12 +266,12 @@ impl Database {
.column(crate::schema::garage::Column::Id)
.filter(crate::schema::garage::Column::Uuid.eq(uuid))
.into_model::<crate::schema::common_query::Id>()
.one(&self.orm)
.one(self.orm.as_ref())
.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)
.exec(self.orm.as_ref())
.await?))
} else {
Ok(None)
@@ -307,7 +311,7 @@ impl Database {
.filter(crate::schema::sanction::Column::UserId.eq(user_id))
.filter(crate::schema::sanction::Column::Descriptor.eq(desc))
.filter(crate::schema::sanction::Column::Acknowledged.is_null())
.count(&self.orm)
.count(self.orm.as_ref())
.await
}
@@ -316,17 +320,17 @@ impl Database {
.filter(crate::schema::sanction::Column::UserId.eq(user_id))
.filter(crate::schema::sanction::Column::Acknowledged.is_null())
.order_by_asc(crate::schema::sanction::Column::CreationTime)
.all(&self.orm)
.all(self.orm.as_ref())
.await
}
pub async fn insert_sanction(&self, entity: crate::schema::sanction::ActiveModel) -> Result<crate::schema::sanction::Model, sea_orm::DbErr> {
entity.insert(&self.orm).await
entity.insert(self.orm.as_ref()).await
}
pub async fn game_count(&self) -> Result<u64, sea_orm::DbErr> {
crate::schema::multiplayer_game::Entity::find()
.count(&self.orm)
.count(self.orm.as_ref())
.await
}
@@ -334,7 +338,7 @@ impl Database {
crate::schema::multiplayer_game::Entity::find()
.filter(crate::schema::multiplayer_game::Column::Guid.eq(game_guid))
.order_by_desc(crate::schema::multiplayer_game::Column::CreationTime)
.one(&self.orm)
.one(self.orm.as_ref())
.await
}
@@ -346,7 +350,7 @@ impl Database {
.filter(crate::schema::multiplayer_game_player::Column::UserId.eq(user_id))
.order_by_desc(crate::schema::multiplayer_game::Column::CreationTime)
//.into_model()
.one(&self.orm)
.one(self.orm.as_ref())
.await?
.map(|(x, _)| x))
}
@@ -359,7 +363,7 @@ impl Database {
.filter(crate::schema::multiplayer_game_player::Column::UserId.eq(user_id))
.order_by_asc(crate::schema::multiplayer_game::Column::CreationTime)
//.into_model()
.one(&self.orm)
.one(self.orm.as_ref())
.await?
.and_then(|(game, player)| player.map(|player| (game, player))))
}
@@ -368,7 +372,7 @@ impl Database {
crate::schema::multiplayer_game::Entity::update_many()
.col_expr(crate::schema::multiplayer_game::Column::IsComplete, sea_orm::sea_query::Expr::value(true))
.filter(crate::schema::multiplayer_game::Column::Guid.eq(game_guid))
.exec(&self.orm)
.exec(self.orm.as_ref())
.await?;
Ok(())
}
@@ -377,13 +381,13 @@ impl Database {
crate::schema::multiplayer_game::Entity::update_many()
.col_expr(crate::schema::multiplayer_game::Column::IsComplete, sea_orm::sea_query::Expr::value(true))
.filter(crate::schema::multiplayer_game::Column::IsComplete.eq(false))
.exec(&self.orm)
.exec(self.orm.as_ref())
.await?;
Ok(())
}
pub async fn insert_game(&self, entity: crate::schema::multiplayer_game::ActiveModel) -> Result<crate::schema::multiplayer_game::Model, sea_orm::DbErr> {
entity.insert(&self.orm).await
entity.insert(self.orm.as_ref()).await
}
pub async fn players_by_game_guid_and_completion(&self, game_guid: i64, is_complete: bool) -> Result<Vec<crate::schema::multiplayer_game_player::Model>, sea_orm::DbErr> {
@@ -392,7 +396,7 @@ impl Database {
.filter(crate::schema::multiplayer_game::Column::Guid.eq(game_guid))
.filter(crate::schema::multiplayer_game::Column::IsComplete.eq(is_complete))
//.into_model::<crate::schema::multiplayer_game_player::Model>()
.all(&self.orm)
.all(self.orm.as_ref())
.await?
.into_iter()
.map(|(player, _)| player)
@@ -407,7 +411,7 @@ impl Database {
//.join(sea_orm::JoinType::InnerJoin, crate::schema::user::Relation::Player.def())
.filter(crate::schema::multiplayer_game::Column::Guid.eq(game_guid))
.filter(crate::schema::multiplayer_game::Column::IsComplete.eq(is_complete))
.all(&self.orm)
.all(self.orm.as_ref())
.await?
.into_iter()
.filter_map(|(player, user, _)| user.map(|user| (player, user)))
@@ -423,7 +427,7 @@ impl Database {
.filter(crate::schema::multiplayer_game::Column::Guid.eq(game_guid))
.filter(crate::schema::multiplayer_game::Column::IsComplete.eq(is_complete))
.filter(crate::schema::multiplayer_game_player::Column::Team.eq(team))
.all(&self.orm)
.all(self.orm.as_ref())
.await?
.into_iter()
.filter_map(|(player, user, _)| user.map(|user| (player, user)))
@@ -438,7 +442,7 @@ impl Database {
.filter(crate::schema::multiplayer_game::Column::Guid.eq(game_guid))
.filter(crate::schema::multiplayer_game::Column::IsComplete.eq(is_complete))
.filter(crate::schema::multiplayer_game_player::Column::Team.eq(team))
.all(&self.orm)
.all(self.orm.as_ref())
.await?
.into_iter()
.map(|(player, _)| player)
@@ -449,7 +453,7 @@ impl Database {
Ok(crate::schema::multiplayer_game_player::Entity::find()
.find_also_related(crate::schema::multiplayer_game::Entity)
.filter(crate::schema::multiplayer_game::Column::Id.eq(game_id))
.all(&self.orm)
.all(self.orm.as_ref())
.await?
.into_iter()
.map(|(player, _)| player)
@@ -462,17 +466,17 @@ impl Database {
.filter(crate::schema::multiplayer_game::Column::Guid.eq(game_guid))
.filter(crate::schema::multiplayer_game_player::Column::UserId.eq(user_id))
//.order_by_asc(crate::schema::multiplayer_game::Column::CreationTime)
.one(&self.orm)
.one(self.orm.as_ref())
.await?
.and_then(|(_, player)| player))
}
pub async fn insert_player(&self, entity: crate::schema::multiplayer_game_player::ActiveModel) -> Result<crate::schema::multiplayer_game_player::Model, sea_orm::DbErr> {
entity.insert(&self.orm).await
entity.insert(self.orm.as_ref()).await
}
pub async fn insert_players(&self, entities: Vec<crate::schema::multiplayer_game_player::ActiveModel>) -> Result<(), sea_orm::DbErr> {
crate::schema::multiplayer_game_player::Entity::insert_many(entities.into_iter()).exec(&self.orm).await?;
crate::schema::multiplayer_game_player::Entity::insert_many(entities.into_iter()).exec(self.orm.as_ref()).await?;
Ok(())
}
@@ -482,7 +486,7 @@ impl Database {
is_claimed: sea_orm::Set(is_claimed),
..Default::default()
})
.exec(&self.orm)
.exec(self.orm.as_ref())
.await?;
Ok(())
}
@@ -493,18 +497,18 @@ impl Database {
.filter(crate::schema::game_event::Column::End.gte(time))
.filter(crate::schema::game_event::Column::Variant.eq(variant))
.order_by_desc(crate::schema::game_event::Column::Start)
.one(&self.orm)
.one(self.orm.as_ref())
.await
}
pub async fn insert_game_event(&self, entity: crate::schema::game_event::ActiveModel) -> Result<crate::schema::game_event::Model, sea_orm::DbErr> {
entity.insert(&self.orm).await
entity.insert(self.orm.as_ref()).await
}
pub async fn score_by_player_id(&self, player_id: i32) -> Result<Option<crate::schema::multiplayer_game_score::Model>, sea_orm::DbErr> {
crate::schema::multiplayer_game_score::Entity::find()
.filter(crate::schema::multiplayer_game_score::Column::PlayerId.eq(player_id))
.one(&self.orm)
.one(self.orm.as_ref())
.await
}
@@ -514,17 +518,17 @@ impl Database {
.filter(crate::schema::multiplayer_game_player::Column::UserId.eq(user_id))
.filter(crate::schema::multiplayer_game_score::Column::IsClaimed.eq(is_claimed))
.order_by_asc(crate::schema::multiplayer_game_player::Column::CreationTime)
.one(&self.orm)
.one(self.orm.as_ref())
.await
.map(|opt| opt.and_then(|(_player, score)| score))
}
pub async fn insert_score(&self, entity: crate::schema::multiplayer_game_score::ActiveModel) -> Result<crate::schema::multiplayer_game_score::Model, sea_orm::DbErr> {
entity.insert(&self.orm).await
entity.insert(self.orm.as_ref()).await
}
pub async fn update_score(&self, entity: crate::schema::multiplayer_game_score::ActiveModel) -> Result<crate::schema::multiplayer_game_score::Model, sea_orm::DbErr> {
crate::schema::multiplayer_game_score::Entity::update(entity).exec(&self.orm).await
crate::schema::multiplayer_game_score::Entity::update(entity).exec(self.orm.as_ref()).await
}
pub async fn score_claim(&self, score_id: i32) -> Result<(), sea_orm::DbErr> {
@@ -533,7 +537,7 @@ impl Database {
is_claimed: sea_orm::Set(true),
..Default::default()
})
.exec(&self.orm)
.exec(self.orm.as_ref())
.await?;
Ok(())
}
@@ -544,7 +548,7 @@ impl Database {
.filter(crate::schema::multiplayer_game_player::Column::UserId.eq(user_id))
.filter(crate::schema::multiplayer_game_score::Column::IsClaimed.eq(is_claimed))
.order_by_asc(crate::schema::multiplayer_game_player::Column::CreationTime)
.count(&self.orm)
.count(self.orm.as_ref())
.await
}

View File

@@ -294,14 +294,10 @@ impl crate::VehicleFactoryAdapter for ArcAdapter {
if self.is_readonly {
return Ok(());
}
// TODO this should probably be a transaction
if let Some(meta) = super::entities::robot_metadata::Entity::find_by_id(id as u32).one(&self.orm).await? {
super::entities::robot_metadata::ActiveModel {
id: sea_orm::ActiveValue::Set(id as u32),
buy_count: sea_orm::ActiveValue::Set(meta.buy_count + 1),
..Default::default()
}.update(&self.orm).await?;
}
super::entities::robot_metadata::Entity::update_many()
.col_expr(super::entities::robot_metadata::Column::BuyCount, super::entities::robot_metadata::Column::BuyCount.into_expr().add(1))
.filter(super::entities::robot_metadata::Column::Id.eq(id))
.exec(&self.orm).await?;
Ok(())
}
@@ -325,6 +321,18 @@ impl crate::VehicleFactoryAdapter for ArcAdapter {
Ok(())
}
async fn remove_vehicle(&self, id: i32, _user_id: i32) -> Result<(), Box<dyn std::error::Error>> {
if self.is_readonly {
return Ok(());
}
super::entities::robot_metadata::ActiveModel {
id: sea_orm::ActiveValue::Set(id as u32),
buyable: sea_orm::ActiveValue::Set(0),
..Default::default()
}.update(&self.orm).await?;
Ok(())
}
async fn set_featured(&self, id: i32, is_featured: bool) -> Result<(), Box<dyn std::error::Error>> {
if self.is_readonly {
return Ok(());

View File

@@ -7,6 +7,7 @@ pub trait VehicleFactoryAdapter: Send + Sync + 'static {
/// Just update any purchase trackers
async fn purchase(&self, id: i32) -> Result<(), Box<dyn std::error::Error>>;
async fn update_vehicle(&self, id: i32, cube_data: Option<Vec<u8>>, colour_data: Option<Vec<u8>>) -> Result<(), Box<dyn std::error::Error>>;
async fn remove_vehicle(&self, id: i32, user_id: i32) -> Result<(), Box<dyn std::error::Error>>;
async fn set_featured(&self, id: i32, is_featured: bool) -> Result<(), Box<dyn std::error::Error>>;
}
@@ -64,6 +65,8 @@ pub struct VehicleUploadInfo {
pub thumbnail: Vec<u8>,
pub added_by: String,
pub added_by_display_name: String,
pub added_by_id: i32,
pub garage_id: i32,
pub cpu: u32,
pub total_robot_ranking: u32,
pub cube_data: Vec<u8>,

View File

@@ -14,6 +14,10 @@ pub struct CliArgs {
/// Assets root
#[arg(long, default_value_t = {"../assets/robocraft".to_string()})]
pub assets: String,
/// User data root
#[arg(long, default_value_t = {"../data/robocraft".to_string()})]
pub data: String,
}
impl CliArgs {

View File

@@ -25,7 +25,8 @@ async fn main() -> std::io::Result<()> {
let cli_args = cli::CliArgs::get();
let conf = ConfigImpl::load(&cli_args.assets).map_err(io_error)?;
let factory_enum = <ConfigImpl as ConfigProvider<()>>::factory(&conf).await.map_err(io_error)?;
let users = oj_rc_core::persist::user::UserImpl::load(&cli_args.data, &conf).await.map_err(io_error)?;
let factory_enum = <ConfigImpl as ConfigProvider<()>>::factory(&conf, &|| users.factory_impl()).await.map_err(io_error)?;
let factory_data = actix_web::web::Data::new(factory_enum);
let mut handlebars_conf = handlebars::Handlebars::new();

View File

@@ -33,7 +33,7 @@ async fn main() -> std::io::Result<()> {
let config = oj_rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data");
let users = std::sync::Arc::new(oj_rc_core::persist::user::UserImpl::load(&args.data, &config).await.expect("Bad user data"));
let factory = std::sync::Arc::new(<oj_rc_core::persist::config::ConfigImpl as oj_rc_core::ConfigProvider<()>>::factory::<'_, '_>(&config).await.expect("Bad vehicle factory (CRF) config"));
let factory = std::sync::Arc::new(<oj_rc_core::persist::config::ConfigImpl as oj_rc_core::ConfigProvider<()>>::factory(&config, &|| users.factory_impl()).await.expect("Bad vehicle factory (CRF) config"));
let parsers = oj_rc_core::cubes::CubeParsers::new(&config);
let queue = std::sync::Arc::new(QueueHandler::new(&config, &args.redirect, factory.clone(), parsers.cpu_counter(), parsers.weapon_order()));

View File

@@ -28,7 +28,7 @@ async fn main() -> std::io::Result<()> {
let config = oj_rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data");
let users = std::sync::Arc::new(oj_rc_core::persist::user::UserImpl::load(&args.data, &config).await.expect("Bad user data"));
users.multiplayer_init().await.expect("Multiplayer init task failed");
let factory = std::sync::Arc::new(<oj_rc_core::persist::config::ConfigImpl as oj_rc_core::ConfigProvider<()>>::factory::<'_, '_>(&config).await.expect("Bad vehicle factory (CRF) config"));
let factory = std::sync::Arc::new(<oj_rc_core::persist::config::ConfigImpl as oj_rc_core::ConfigProvider<()>>::factory(&config, &|| users.factory_impl()).await.expect("Bad vehicle factory (CRF) config"));
let parsers = std::sync::Arc::new(oj_rc_core::cubes::CubeParsers::new(&config));
let matches = matches::GameMatches::new(&config, parsers.clone(), factory.clone());
let matches_chann = matches.spawn();

View File

@@ -33,7 +33,7 @@ async fn main() -> std::io::Result<()> {
let cubes = oj_rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data");
let users = std::sync::Arc::new(oj_rc_core::persist::user::UserImpl::load(&args.data, &cubes).await.expect("Bad user data"));
let factory = std::sync::Arc::new(<oj_rc_core::persist::config::ConfigImpl as oj_rc_core::ConfigProvider<()>>::factory::<'_, '_>(&cubes).await.expect("Bad vehicle factory (CRF) config"));
let factory = std::sync::Arc::new(<oj_rc_core::persist::config::ConfigImpl as oj_rc_core::ConfigProvider<()>>::factory(&cubes, &|| users.factory_impl()).await.expect("Bad vehicle factory (CRF) config"));
let parsers = oj_rc_core::cubes::CubeParsers::new(&cubes);
let init_ctx = std::sync::Arc::new(InitConfig {
cubes,

View File

@@ -0,0 +1,44 @@
use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl};
use polariton::operation::{ParameterTable, Typed};
use oj_rc_factory::VehicleFactoryAdapter;
const CODE: u8 = 91;
const ID_PARAM_KEY: u8 = 94; // int; in
const EARNINGS_PARAM_KEY: u8 = 96;
pub(super) struct FactoryRemoveVehicle {
factory: std::sync::Arc<oj_rc_core::factory::Factory>,
}
#[async_trait::async_trait]
impl <C: Send + 'static> SimpleOperation<C> for FactoryRemoveVehicle {
type User = crate::UserTy;
const CODE: u8 = CODE;
async fn handle(&self, mut params: ParameterTable<C>, user: &Self::User) -> Result<ParameterTable<C>, SimpleOpError> {
if let Some(Typed::Int(factory_id)) = params.remove(&ID_PARAM_KEY) {
let user_info = user.user()?;
let user_id = user_info.account_id();
self.factory.remove_vehicle(factory_id, user_id).await.map_err(|e| {
log::error!("Failed to remove vehicle {} by user {} from factory: {}", factory_id, user_id, e);
SimpleOpError::with_message(
oj_rc_core::data::error_codes::WebServicesError::DatabaseError as i16,
format!("Failed to remove vehicle (for offset) from factory: {}", e)
)
})?;
}
// TODO Factory earnings
params.insert(EARNINGS_PARAM_KEY, Typed::HashMap(vec![
(Typed::Str("buyCount".into()), Typed::Int(0)),
(Typed::Str("earnings".into()), Typed::Int(0)),
].into()));
Ok(params)
}
}
pub(super) fn factory_remove_provider<C: Send + 'static>(factory: &std::sync::Arc<oj_rc_core::factory::Factory>) -> SimpleOpImpl<C, crate::UserTy, FactoryRemoveVehicle> {
SimpleOpImpl::new(FactoryRemoveVehicle {
factory: factory.to_owned()
})
}

View File

@@ -106,6 +106,7 @@ mod crf_shift_vehicle;
mod crf_make_featured;
mod crf_unmake_featured;
mod tech_tree_unlock_cube;
mod crf_remove;
use polariton_server::operations::OperationsHandler;
@@ -236,4 +237,6 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler<crate::UserTy>
.add(crf_unmake_featured::factory_featured_provider(&init_ctx.factory))
.add(polariton_server::operations::Ack::<101, _>::default()) // RestoreCRFFeaturedRobot unused and also redundant???
.add(tech_tree_unlock_cube::tech_tree_cube_unlock_provider(&init_ctx.cubes))
.add(crf_remove::factory_remove_provider(&init_ctx.factory))
.add(polariton_server::operations::Ack::<94, _>::default()) // ReportCommunityShopItemRequest FIXME: log reports
}

View File

@@ -30,7 +30,7 @@ async fn main() -> std::io::Result<()> {
let config = oj_rc_core::persist::config::ConfigImpl::load(&args.assets).expect("Bad config data");
let users = std::sync::Arc::new(oj_rc_core::persist::user::UserImpl::load(&args.data, &config).await.expect("Bad user data"));
let factory = std::sync::Arc::new(<oj_rc_core::persist::config::ConfigImpl as oj_rc_core::ConfigProvider<()>>::factory::<'_, '_>(&config).await.expect("Bad vehicle factory (CRF) config"));
let factory = std::sync::Arc::new(<oj_rc_core::persist::config::ConfigImpl as oj_rc_core::ConfigProvider<()>>::factory(&config, &|| users.factory_impl()).await.expect("Bad vehicle factory (CRF) config"));
let parsers = oj_rc_core::cubes::CubeParsers::new(&config);
let init_ctx = InitConfig {