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

Add web adapter for vehicle factory

This commit is contained in:
NG (Graham)
2026-02-08 17:21:32 -05:00
parent 378d2d2cd8
commit dde005b371
7 changed files with 176 additions and 5 deletions

4
Cargo.lock generated
View File

@@ -2032,9 +2032,7 @@ checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]] [[package]]
name = "libfj" name = "libfj"
version = "0.9.0" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f55afe6b910429afcd02d7810760e07e8489ad662f7df0e938a6154fe413d41"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"base64", "base64",

View File

@@ -30,8 +30,8 @@ members = [
[workspace.dependencies] [workspace.dependencies]
actix-web = { version = "4", default-features = false, features = [ "macros", "compress-brotli", "compress-gzip", "compress-zstd", "ws"] } actix-web = { version = "4", default-features = false, features = [ "macros", "compress-brotli", "compress-gzip", "compress-zstd", "ws"] }
actix-files = "0.6" actix-files = "0.6"
#libfj = { version = "0.9", path = "../libfj" } libfj = { version = "0.10", path = "../libfj" }
libfj = { version = "0.9" } #libfj = { version = "0.9" }
log = "0.4" log = "0.4"
env_logger = "0.11" env_logger = "0.11"
clap = { version = "4.5", features = [ "derive" ] } clap = { version = "4.5", features = [ "derive" ] }

View File

@@ -1,6 +1,7 @@
pub enum Factory { pub enum Factory {
Arc(oj_rc_factory::arc::ArcAdapter), Arc(oj_rc_factory::arc::ArcAdapter),
Primary(oj_rc_database::FactoryDatabase), Primary(oj_rc_database::FactoryDatabase),
Web(oj_rc_factory::web::WebAdapter),
Custom(Box<dyn oj_rc_factory::VehicleFactoryAdapter + Send + Sync + 'static>), Custom(Box<dyn oj_rc_factory::VehicleFactoryAdapter + Send + Sync + 'static>),
None, None,
} }
@@ -11,6 +12,7 @@ impl oj_rc_factory::VehicleFactoryAdapter for Factory {
match self { match self {
Self::Arc(x) => x.vehicle(id).await, Self::Arc(x) => x.vehicle(id).await,
Self::Primary(x) => x.vehicle(id).await, Self::Primary(x) => x.vehicle(id).await,
Self::Web(x) => x.vehicle(id).await,
Self::Custom(x) => x.vehicle(id).await, Self::Custom(x) => x.vehicle(id).await,
Self::None => Ok(None), Self::None => Ok(None),
} }
@@ -20,6 +22,7 @@ impl oj_rc_factory::VehicleFactoryAdapter for Factory {
match self { match self {
Self::Arc(x) => x.list(query).await, Self::Arc(x) => x.list(query).await,
Self::Primary(x) => x.list(query).await, Self::Primary(x) => x.list(query).await,
Self::Web(x) => x.list(query).await,
Self::Custom(x) => x.list(query).await, Self::Custom(x) => x.list(query).await,
Self::None => Ok(Vec::default()), Self::None => Ok(Vec::default()),
} }
@@ -29,6 +32,7 @@ impl oj_rc_factory::VehicleFactoryAdapter for Factory {
match self { match self {
Self::Arc(x) => x.upload(vehicle).await, Self::Arc(x) => x.upload(vehicle).await,
Self::Primary(x) => x.upload(vehicle).await, Self::Primary(x) => x.upload(vehicle).await,
Self::Web(x) => x.upload(vehicle).await,
Self::Custom(x) => x.upload(vehicle).await, Self::Custom(x) => x.upload(vehicle).await,
Self::None => Ok(oj_rc_factory::VehicleThumbnailInfo { Self::None => Ok(oj_rc_factory::VehicleThumbnailInfo {
id: i32::MIN, id: i32::MIN,
@@ -42,6 +46,7 @@ impl oj_rc_factory::VehicleFactoryAdapter for Factory {
match self { match self {
Self::Arc(x) => x.rate_vehicle(id, combat, cosmetic).await, Self::Arc(x) => x.rate_vehicle(id, combat, cosmetic).await,
Self::Primary(x) => x.rate_vehicle(id, combat, cosmetic).await, Self::Primary(x) => x.rate_vehicle(id, combat, cosmetic).await,
Self::Web(x) => x.rate_vehicle(id, combat, cosmetic).await,
Self::Custom(x) => x.rate_vehicle(id, combat, cosmetic).await, Self::Custom(x) => x.rate_vehicle(id, combat, cosmetic).await,
Self::None => Ok(()), Self::None => Ok(()),
} }
@@ -51,6 +56,7 @@ impl oj_rc_factory::VehicleFactoryAdapter for Factory {
match self { match self {
Self::Arc(x) => x.purchase(id).await, Self::Arc(x) => x.purchase(id).await,
Self::Primary(x) => x.purchase(id).await, Self::Primary(x) => x.purchase(id).await,
Self::Web(x) => x.purchase(id).await,
Self::Custom(x) => x.purchase(id).await, Self::Custom(x) => x.purchase(id).await,
Self::None => Ok(()), Self::None => Ok(()),
} }
@@ -60,6 +66,7 @@ impl oj_rc_factory::VehicleFactoryAdapter for Factory {
match self { match self {
Self::Arc(x) => x.update_vehicle(id, cube_data, colour_data).await, 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::Primary(x) => x.update_vehicle(id, cube_data, colour_data).await,
Self::Web(x) => x.update_vehicle(id, cube_data, colour_data).await,
Self::Custom(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(()), Self::None => Ok(()),
} }
@@ -69,6 +76,7 @@ impl oj_rc_factory::VehicleFactoryAdapter for Factory {
match self { match self {
Self::Arc(x) => x.remove_vehicle(id, user_id).await, Self::Arc(x) => x.remove_vehicle(id, user_id).await,
Self::Primary(x) => x.remove_vehicle(id, user_id).await, Self::Primary(x) => x.remove_vehicle(id, user_id).await,
Self::Web(x) => x.remove_vehicle(id, user_id).await,
Self::Custom(x) => x.remove_vehicle(id, user_id).await, Self::Custom(x) => x.remove_vehicle(id, user_id).await,
Self::None => Ok(()), Self::None => Ok(()),
} }
@@ -78,6 +86,7 @@ impl oj_rc_factory::VehicleFactoryAdapter for Factory {
match self { match self {
Self::Arc(x) => x.set_featured(id, is_featured).await, Self::Arc(x) => x.set_featured(id, is_featured).await,
Self::Primary(x) => x.set_featured(id, is_featured).await, Self::Primary(x) => x.set_featured(id, is_featured).await,
Self::Web(x) => x.set_featured(id, is_featured).await,
Self::Custom(x) => x.set_featured(id, is_featured).await, Self::Custom(x) => x.set_featured(id, is_featured).await,
Self::None => Ok(()), Self::None => Ok(()),
} }
@@ -89,6 +98,7 @@ impl Factory {
Ok(match &conf.adapter { 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::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::BuiltIn => Self::Primary(builtin_factory_provider()),
crate::persist::AdapterSettings::Web(x) => Self::Web(oj_rc_factory::web::WebAdapter::init(&x.url, &settings.auth_url).await?),
crate::persist::AdapterSettings::None => Self::None, crate::persist::AdapterSettings::None => Self::None,
}) })
} }

View File

@@ -25,6 +25,8 @@ pub enum AdapterSettings {
Arc(ArcFactorySettings), Arc(ArcFactorySettings),
#[serde(alias = "integrated")] #[serde(alias = "integrated")]
BuiltIn, BuiltIn,
#[serde(alias = "online")]
Web(WebFactorySettings),
None, None,
} }
@@ -42,3 +44,8 @@ pub struct ArcFactorySettings {
fn default_true() -> bool { fn default_true() -> bool {
true true
} }
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct WebFactorySettings {
pub url: String,
}

View File

@@ -1,4 +1,5 @@
pub mod arc; pub mod arc;
pub mod web;
mod traits; mod traits;
pub use traits::{VehicleFactoryAdapter, VehicleInfo, VehicleQueryInfo, VehicleUploadInfo, VehicleThumbnailInfo}; pub use traits::{VehicleFactoryAdapter, VehicleInfo, VehicleQueryInfo, VehicleUploadInfo, VehicleThumbnailInfo};

View File

@@ -0,0 +1,151 @@
pub struct WebAdapter {
api: libfj::robocraft::FactoryAPI,
}
// TODO do some real authentication
struct FederatedFactoryAuthProvider {
target_domain: String,
my_auth_url: String,
}
impl libfj::robocraft::ITokenProvider for FederatedFactoryAuthProvider {
fn token(&self) -> Result<String, ()> {
Ok(format!("{},{}", self.target_domain, self.my_auth_url))
}
fn scheme(&self) -> &'static str {
"OJWeb"
}
}
impl WebAdapter {
pub async fn init(url: &str, auth_url: &str) -> Result<Self, Box<dyn std::error::Error + 'static>> {
Ok(Self {
api: libfj::robocraft::FactoryAPI::with_auth(FederatedFactoryAuthProvider {
target_domain: url.to_owned(),
my_auth_url: auth_url.to_owned(),
})
.with_domain(url)?,
})
}
}
#[async_trait::async_trait]
impl crate::VehicleFactoryAdapter for WebAdapter {
async fn vehicle(&self, id: i32) -> Result<Option<(crate::VehicleInfo, crate::VehicleQueryInfo)>, Box<dyn std::error::Error>> {
use base64::Engine;
let id_usize: usize = id.try_into()?;
let response = self.api.get(id_usize).await?;
let response_data = response.response;
Ok(Some((
crate::VehicleInfo {
id,
cube_data: base64::prelude::BASE64_STANDARD.decode(response_data.cube_data.as_bytes()).unwrap_or_default(),
colour_data: base64::prelude::BASE64_STANDARD.decode(response_data.colour_data.as_bytes()).unwrap_or_default(),
},
crate::VehicleQueryInfo {
id: response_data.item_id as _,
name: response_data.item_name,
description: response_data.item_description,
thumbnail: response_data.thumbnail,
added_by: response_data.added_by,
added_by_display_name: response_data.added_by_display_name,
added_date: crate::traits::parse_rc_date(&response_data.added_date).unwrap_or_default(),
expiry_date: crate::traits::parse_rc_date(&response_data.expiry_date).unwrap_or_default(),
cpu: response_data.cpu as _,
total_robot_ranking: response_data.total_robot_ranking as _,
rent_count: response_data.rent_count as _,
buy_count: response_data.buy_count as _,
buyable: response_data.buyable,
removed_date: response_data.removed_date.and_then(|x| crate::traits::parse_rc_date(&x).ok()),
ban_date: response_data.ban_date.and_then(|x| crate::traits::parse_rc_date(&x).ok()),
featured: response_data.featured,
banner_message: response_data.banner_message,
combat_rating: response_data.combat_rating as _,
cosmetic_rating: response_data.cosmetic_rating as _,
cube_amounts: serde_json::from_str(&response_data.cube_amounts).unwrap_or_default(),
}
)))
}
async fn list(&self, query: libfj::robocraft::ListQuery) -> Result<Vec<crate::VehicleQueryInfo>, Box<dyn std::error::Error>> {
let response = if query.default_page {
self.api.list().await?
} else {
self.api.list_builder()
.page(query.page as _)
.items_per_page(query.page_size as _)
.order(query.order)
.movement_raw(concat_u32_enums_to_str(&query.movement_filter))
.weapon_raw(concat_u32_enums_to_str(&query.weapon_filter))
.min_cpu(query.minimum_cpu as _)
.max_cpu(query.maximum_cpu as _)
.text(query.text_filter)
.text_search_type(if query.player_filter { libfj::robocraft::FactoryTextSearchField::Player } else { query.text_search_field })
.buyable(query.buyable)
.prepend_featured(query.prepend_featured_robot)
.default_page(query.default_page)
.send().await?
};
Ok(response.response.roboshop_items.into_iter().map(|response_data| crate::VehicleQueryInfo {
id: response_data.item_id as _,
name: response_data.item_name,
description: response_data.item_description,
thumbnail: response_data.thumbnail,
added_by: response_data.added_by,
added_by_display_name: response_data.added_by_display_name,
added_date: crate::traits::parse_rc_date(&response_data.added_date).unwrap_or_default(),
expiry_date: crate::traits::parse_rc_date(&response_data.expiry_date).unwrap_or_default(),
cpu: response_data.cpu as _,
total_robot_ranking: response_data.total_robot_ranking as _,
rent_count: response_data.rent_count as _,
buy_count: response_data.buy_count as _,
buyable: response_data.buyable,
removed_date: response_data.removed_date.and_then(|x| crate::traits::parse_rc_date(&x).ok()),
ban_date: response_data.ban_date.and_then(|x| crate::traits::parse_rc_date(&x).ok()),
featured: response_data.featured,
banner_message: response_data.banner_message,
combat_rating: response_data.combat_rating as _,
cosmetic_rating: response_data.cosmetic_rating as _,
cube_amounts: serde_json::from_str(&response_data.cube_amounts).unwrap_or_default(),
}).collect())
}
async fn upload(&self, _vehicle: crate::VehicleUploadInfo) -> Result<crate::VehicleThumbnailInfo, Box<dyn std::error::Error>>{
Err("Uploading is not supported in the web adapter".into())
}
async fn rate_vehicle(&self, _id: i32, _combat: i32, _cosmetic: i32) -> Result<(), Box<dyn std::error::Error>> {
Err("Rating is not supported in the web adapter".into())
}
/// Just update any purchase trackers
async fn purchase(&self, _id: i32) -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
async fn update_vehicle(&self, _id: i32, _cube_data: Option<Vec<u8>>, _colour_data: Option<Vec<u8>>) -> Result<(), Box<dyn std::error::Error>> {
Err("Updating vehicles is not supported in the web adapter".into())
}
async fn remove_vehicle(&self, _id: i32, _user_id: i32) -> Result<(), Box<dyn std::error::Error>> {
Err("Removing vehicles is not supported in the web adapter".into())
}
async fn set_featured(&self, _id: i32, _is_featured: bool) -> Result<(), Box<dyn std::error::Error>> {
Err("Featuring vehicles is not supported in the web adapter".into())
}
}
pub fn concat_u32_enums_to_str(enums: &[u32]) -> String {
let mut out = String::new();
for num in enums.iter() {
if out.is_empty() {
out += &num.to_string();
} else {
out += ",";
out += &num.to_string();
}
}
out
}

View File

@@ -0,0 +1,4 @@
//! Web client adapter for the factory.
//! Not to be confused with rc_factory_web, which provides the server part of this API.
mod adapter;
pub use adapter::WebAdapter;