mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Add minimal robot factory functionality and arc archive support for #6
This commit is contained in:
16
rc_factory/Cargo.toml
Normal file
16
rc_factory/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "rc_factory"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
readme.workspace = true
|
||||
|
||||
[dependencies]
|
||||
sea-orm = { version = "1.1.10", features = [ "runtime-tokio-rustls", "macros" ] }
|
||||
async-trait.workspace = true
|
||||
libfj.workspace = true
|
||||
chrono.workspace = true
|
||||
log.workspace = true
|
||||
base64 = "0.22"
|
||||
122
rc_factory/src/arc/adapter.rs
Normal file
122
rc_factory/src/arc/adapter.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder};
|
||||
|
||||
pub struct ArcAdapter {
|
||||
orm: sea_orm::DatabaseConnection,
|
||||
ignore_expiry: bool,
|
||||
}
|
||||
|
||||
impl ArcAdapter {
|
||||
pub async fn init(uri: &str, show_expired: bool) -> Result<Self, sea_orm::DbErr>{
|
||||
log::debug!("Connecting to Archive of RoboCraft (ARC) vehicle factory database URI: {}", uri);
|
||||
let db = sea_orm::Database::connect(uri).await?;
|
||||
Ok(Self {
|
||||
orm: db,
|
||||
ignore_expiry: show_expired
|
||||
})
|
||||
}
|
||||
|
||||
fn default_query(&self) -> sea_orm::Select<super::entities::robot_metadata::Entity> {
|
||||
super::entities::robot_metadata::Entity::find()
|
||||
.order_by_desc(super::entities::robot_metadata::Column::RentCount)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl crate::VehicleFactoryAdapter for ArcAdapter {
|
||||
async fn vehicle(&self, id: u32) -> Result<Option<crate::VehicleInfo>, Box<dyn std::error::Error>> {
|
||||
log::debug!("Get vehicle id {}", id);
|
||||
let cubes = super::entities::robot_cubes::Entity::find_by_id(id).one(&self.orm).await?;
|
||||
if let Some(cubes) = cubes {
|
||||
use base64::Engine;
|
||||
Ok(Some(crate::VehicleInfo {
|
||||
id: id as _,
|
||||
cube_data: base64::prelude::BASE64_STANDARD.decode(cubes.cube_data.as_bytes()).unwrap_or_default(),
|
||||
colour_data: base64::prelude::BASE64_STANDARD.decode(cubes.colour_data.as_bytes()).unwrap_or_default(),
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async fn list(&self, query: libfj::robocraft::ListQuery) -> Result<Vec<crate::VehicleQueryInfo>, Box<dyn std::error::Error>> {
|
||||
log::debug!("Search vehicles with query {:?}", query);
|
||||
let query_params = if query.default_page {
|
||||
log::debug!("Default vehicle list query");
|
||||
self.default_query()
|
||||
} else {
|
||||
let mut query_builder = super::entities::robot_metadata::Entity::find();
|
||||
match query.order {
|
||||
libfj::robocraft::FactoryOrderType::Suggested => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::RentCount); },
|
||||
libfj::robocraft::FactoryOrderType::CombatRating => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::CombatRating); },
|
||||
libfj::robocraft::FactoryOrderType::CosmeticRating => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::CosmeticRating); },
|
||||
libfj::robocraft::FactoryOrderType::Added => { query_builder = query_builder.order_by_asc(super::entities::robot_metadata::Column::AddedDate); },
|
||||
libfj::robocraft::FactoryOrderType::CPU => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::Cpu); },
|
||||
libfj::robocraft::FactoryOrderType::MostBought => { query_builder = query_builder.order_by_desc(super::entities::robot_metadata::Column::BuyCount); },
|
||||
}
|
||||
if !query.text_filter.is_empty() {
|
||||
if query.player_filter {
|
||||
query_builder = query_builder.filter(
|
||||
sea_orm::sea_query::Condition::any()
|
||||
.add(super::entities::robot_metadata::Column::AddedBy.like(query.text_filter.clone()))
|
||||
.add(super::entities::robot_metadata::Column::AddedByDisplayName.like(query.text_filter))
|
||||
);
|
||||
} else {
|
||||
query_builder = query_builder.filter(
|
||||
sea_orm::sea_query::Condition::any()
|
||||
.add(super::entities::robot_metadata::Column::AddedBy.like(query.text_filter.clone()))
|
||||
.add(super::entities::robot_metadata::Column::AddedByDisplayName.like(query.text_filter.clone()))
|
||||
.add(super::entities::robot_metadata::Column::Name.like(query.text_filter.clone()))
|
||||
.add(super::entities::robot_metadata::Column::Description.like(query.text_filter.clone()))
|
||||
);
|
||||
}
|
||||
}
|
||||
// movement filters not supported
|
||||
// weapon filters not supported
|
||||
if query.minimum_cpu > 0 {
|
||||
query_builder = query_builder.filter(super::entities::robot_metadata::Column::Cpu.gte(query.minimum_cpu as u32));
|
||||
}
|
||||
if query.maximum_cpu < usize::MAX {
|
||||
query_builder = query_builder.filter(super::entities::robot_metadata::Column::Cpu.lte(query.maximum_cpu as u32));
|
||||
}
|
||||
if query.buyable {
|
||||
query_builder = query_builder.filter(super::entities::robot_metadata::Column::Buyable.ne(0));
|
||||
}
|
||||
query_builder
|
||||
};
|
||||
|
||||
// FIXME add support for query.prepend_featured_bot
|
||||
let metadata_pages = query_params.paginate(&self.orm, query.page_size as u64);
|
||||
let metadatas = metadata_pages.fetch_page(query.page as u64).await?;
|
||||
let mut infos = Vec::with_capacity(metadatas.len());
|
||||
for meta in metadatas {
|
||||
//let cube_amounts = super::entities::robot_cubes::Entity::find_by_id(meta.id).one(&self.orm).await?.map(|x| x.cube_amounts).unwrap_or_else(|| "".to_owned());
|
||||
infos.push(
|
||||
crate::VehicleQueryInfo {
|
||||
id: meta.id as _,
|
||||
name: meta.name,
|
||||
description: meta.description,
|
||||
thumbnail: meta.thumbnail,
|
||||
added_by: meta.added_by,
|
||||
added_by_display_name: meta.added_by_display_name,
|
||||
added_date: crate::traits::parse_rc_date(&meta.added_date).unwrap_or_default(),
|
||||
expiry_date: if self.ignore_expiry { chrono::Utc::now() + chrono::Duration::weeks(2) } else { crate::traits::parse_rc_date(&meta.expiry_date).unwrap_or_default() },
|
||||
cpu: meta.cpu as _,
|
||||
total_robot_ranking: meta.total_robot_ranking as _,
|
||||
rent_count: meta.rent_count as _,
|
||||
buy_count: meta.buy_count as _,
|
||||
buyable: meta.buyable != 0,
|
||||
removed_date: Default::default(),
|
||||
ban_date: Default::default(),
|
||||
featured: meta.featured != 0,
|
||||
banner_message: Default::default(),
|
||||
combat_rating: meta.combat_rating,
|
||||
cosmetic_rating: meta.cosmetic_rating,
|
||||
cube_amounts: Default::default(),
|
||||
}
|
||||
);
|
||||
}
|
||||
log::debug!("Search vehicles returned {} results", infos.len());
|
||||
Ok(infos)
|
||||
}
|
||||
}
|
||||
7
rc_factory/src/arc/entities/mod.rs
Normal file
7
rc_factory/src/arc/entities/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.11
|
||||
|
||||
//pub mod prelude;
|
||||
|
||||
pub mod robot_cubes;
|
||||
pub mod robot_metadata;
|
||||
//pub mod state;
|
||||
5
rc_factory/src/arc/entities/prelude.rs
Normal file
5
rc_factory/src/arc/entities/prelude.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.11
|
||||
|
||||
pub use super::robot_cubes::Entity as RobotCubes;
|
||||
pub use super::robot_metadata::Entity as RobotMetadata;
|
||||
//pub use super::state::Entity as State;
|
||||
21
rc_factory/src/arc/entities/robot_cubes.rs
Normal file
21
rc_factory/src/arc/entities/robot_cubes.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.11
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||
#[sea_orm(table_name = "ROBOT_CUBES")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: u32,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub cube_data: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub colour_data: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub cube_amounts: String,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
39
rc_factory/src/arc/entities/robot_metadata.rs
Normal file
39
rc_factory/src/arc/entities/robot_metadata.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.11
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "ROBOT_METADATA")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: u32,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub name: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub description: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub thumbnail: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub added_by: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub added_by_display_name: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub added_date: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub expiry_date: String,
|
||||
pub cpu: u32,
|
||||
pub total_robot_ranking: i32,
|
||||
pub rent_count: i32,
|
||||
pub buy_count: i32,
|
||||
pub buyable: i32,
|
||||
pub featured: i32,
|
||||
#[sea_orm(column_type = "Float")]
|
||||
pub combat_rating: f64,
|
||||
#[sea_orm(column_type = "Float")]
|
||||
pub cosmetic_rating: f64,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
18
rc_factory/src/arc/entities/state.rs
Normal file
18
rc_factory/src/arc/entities/state.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.11
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
||||
#[sea_orm(table_name = "STATE")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: i32,
|
||||
pub next_page: i32,
|
||||
pub last_page_size: i32,
|
||||
pub last_sequential_id: i32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
6
rc_factory/src/arc/mod.rs
Normal file
6
rc_factory/src/arc/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
//! Adapter for sqlite database generated by https://github.com/NGnius/arc
|
||||
|
||||
mod adapter;
|
||||
pub use adapter::ArcAdapter;
|
||||
|
||||
mod entities;
|
||||
4
rc_factory/src/lib.rs
Normal file
4
rc_factory/src/lib.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod arc;
|
||||
|
||||
mod traits;
|
||||
pub use traits::{VehicleFactoryAdapter, VehicleInfo, VehicleQueryInfo};
|
||||
52
rc_factory/src/traits.rs
Normal file
52
rc_factory/src/traits.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
#[async_trait::async_trait]
|
||||
pub trait VehicleFactoryAdapter: Send + Sync + 'static {
|
||||
async fn vehicle(&self, id: u32) -> Result<Option<VehicleInfo>, Box<dyn std::error::Error>>;
|
||||
async fn list(&self, query: libfj::robocraft::ListQuery) -> Result<Vec<VehicleQueryInfo>, Box<dyn std::error::Error>>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VehicleInfo {
|
||||
pub id: i32,
|
||||
pub cube_data: Vec<u8>,
|
||||
pub colour_data: Vec<u8>,
|
||||
}
|
||||
|
||||
pub fn parse_rc_date(s: &str) -> chrono::ParseResult<chrono::DateTime<chrono::Utc>> {
|
||||
let naive = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S")?;
|
||||
Ok(chrono::DateTime::from_naive_utc_and_offset(naive, chrono::Utc))
|
||||
}
|
||||
|
||||
impl std::convert::From<libfj::robocraft::FactoryRobotGetInfo> for VehicleInfo {
|
||||
fn from(value: libfj::robocraft::FactoryRobotGetInfo) -> Self {
|
||||
use base64::Engine;
|
||||
Self {
|
||||
id: value.item_id as _,
|
||||
cube_data: base64::prelude::BASE64_STANDARD.decode(value.cube_data.as_bytes()).unwrap_or_default(),
|
||||
colour_data: base64::prelude::BASE64_STANDARD.decode(value.colour_data.as_bytes()).unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VehicleQueryInfo {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub thumbnail: String, // url
|
||||
pub added_by: String,
|
||||
pub added_by_display_name: String,
|
||||
pub added_date: chrono::DateTime<chrono::Utc>,
|
||||
pub expiry_date: chrono::DateTime<chrono::Utc>,
|
||||
pub cpu: u32,
|
||||
pub total_robot_ranking: u32,
|
||||
pub rent_count: u32,
|
||||
pub buy_count: u32,
|
||||
pub buyable: bool,
|
||||
pub removed_date: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub ban_date: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub featured: bool,
|
||||
pub banner_message: Option<String>,
|
||||
pub combat_rating: f64,
|
||||
pub cosmetic_rating: f64,
|
||||
pub cube_amounts: std::collections::HashMap<u32, u32>,
|
||||
}
|
||||
Reference in New Issue
Block a user