- add the imported server task addon to the current framework with task ownership, task catalog, mission-manager attack generation, org-owned reward routing, participant notifications, and reputation syncing - restructure org persistence so core org data, assets, fleet, and members are handled through the current Redis/extension model with matching Rust repository and service updates - wire the client CAD addon into the framework, actor device action, shared web UI bridge pattern, and task listing/acceptance flow - add a source-driven CAD web UI layout with ui.config.mjs and extend the shared web UI builder to support custom HTML template pages for multi-surface UIs
146 lines
5.4 KiB
Rust
146 lines
5.4 KiB
Rust
//! Virtual garage repository implementation for item data persistence operations.
|
|
//!
|
|
//! This module provides the data access layer for virtual garage management.
|
|
//! Each player's virtual garage is stored as a Redis hash with six fields:
|
|
//! - cars: JSON array of car classnames
|
|
//! - armor: JSON array of armor classnames
|
|
//! - helis: JSON array of helis classnames
|
|
//! - planes: JSON array of plane classnames
|
|
//! - naval: JSON array of naval classnames
|
|
//! - other: JSON array of other classnames
|
|
|
|
use forge_models::VGarage;
|
|
use forge_shared::{RedisClient, parse_json_value, parse_redis_value};
|
|
|
|
/// Repository trait defining the contract for virtual garage data operations.
|
|
pub trait VGarageRepository: Send + Sync {
|
|
/// Creates a new virtual garage for a player
|
|
fn create(&self, uid: &str, garage: &VGarage) -> Result<(), String>;
|
|
|
|
/// Updates an existing virtual garage with new item data
|
|
fn update(&self, uid: &str, garage: &VGarage) -> Result<(), String>;
|
|
|
|
/// Retrieves a player's virtual garage
|
|
fn fetch(&self, uid: &str) -> Result<Option<VGarage>, String>;
|
|
|
|
/// Retrieves a specific field from a player's virtual garage
|
|
/// Fields: "cars", "armor", "helis", "planes", "naval", "other"
|
|
fn get(&self, uid: &str, field: &str) -> Result<Vec<String>, String>;
|
|
|
|
/// Deletes a player's virtual garage (all items)
|
|
fn delete(&self, uid: &str) -> Result<(), String>;
|
|
|
|
/// Checks if a player has a virtual garage
|
|
fn exists(&self, uid: &str) -> Result<bool, String>;
|
|
}
|
|
|
|
/// Redis-based implementation of the VGarageRepository trait.
|
|
///
|
|
/// Stores each player's virtual garage as a Redis hash with six fields:
|
|
/// - cars: JSON array of car classnames
|
|
/// - armor: JSON array of armor classnames
|
|
/// - helis: JSON array of helis classnames
|
|
/// - planes: JSON array of plane classnames
|
|
/// - naval: JSON array of naval classnames
|
|
/// - other: JSON array of other classnames
|
|
///
|
|
/// Key format: `vgarage:{uid}`
|
|
pub struct RedisVGarageRepository<C: RedisClient> {
|
|
client: C,
|
|
}
|
|
|
|
impl<C: RedisClient> RedisVGarageRepository<C> {
|
|
pub fn new(client: C) -> Self {
|
|
Self { client }
|
|
}
|
|
}
|
|
|
|
impl<C: RedisClient> VGarageRepository for RedisVGarageRepository<C> {
|
|
fn create(&self, uid: &str, garage: &VGarage) -> Result<(), String> {
|
|
let redis_key = format!("owned:garage:{}", uid);
|
|
|
|
// Serialize locker to JSON string
|
|
let garage_json = serde_json::to_string(garage)
|
|
.map_err(|e| format!("Failed to serialize garage: {}", e))?;
|
|
|
|
// Parse JSON string back to Value for field extraction
|
|
let json_value: serde_json::Value = serde_json::from_str(&garage_json)
|
|
.map_err(|e| format!("Failed to parse garage JSON: {}", e))?;
|
|
|
|
// Extract fields from JSON object
|
|
if let serde_json::Value::Object(garage_map) = json_value {
|
|
// Convert each field to Redis-compatible format
|
|
let fields: Vec<(String, String)> = garage_map
|
|
.into_iter()
|
|
.map(|(field, value)| (field, parse_json_value(&value)))
|
|
.collect();
|
|
|
|
// Store all fields atomically using Redis HMSET
|
|
self.client.hash_mset(redis_key, fields)
|
|
} else {
|
|
Err("Failed to convert garage to object".to_string())
|
|
}
|
|
}
|
|
|
|
fn update(&self, uid: &str, garage: &VGarage) -> Result<(), String> {
|
|
// For a hash, update is the same as create - it's atomic
|
|
self.create(uid, garage)
|
|
}
|
|
|
|
fn fetch(&self, uid: &str) -> Result<Option<VGarage>, String> {
|
|
let redis_key = format!("owned:garage:{}", uid);
|
|
|
|
// Retrieve all hash fields from Redis
|
|
let garage_string = self.client.hash_get_all(redis_key)?;
|
|
|
|
// Return None if no data found (garage doesn't exist)
|
|
if garage_string.is_empty() {
|
|
return Ok(None);
|
|
}
|
|
|
|
let redis_map: std::collections::HashMap<String, String> =
|
|
serde_json::from_str(&garage_string)
|
|
.map_err(|e| format!("Failed to parse virtual garage hash response: {}", e))?;
|
|
let mut json_map = serde_json::Map::new();
|
|
|
|
for (key, value) in redis_map {
|
|
let json_value = parse_redis_value(&value);
|
|
json_map.insert(key, json_value);
|
|
}
|
|
|
|
// Reconstruct VLocker from JSON object
|
|
let json_obj = serde_json::Value::Object(json_map);
|
|
match serde_json::from_value::<VGarage>(json_obj) {
|
|
Ok(garage) => Ok(Some(garage)),
|
|
// Return None for any deserialization errors (corrupted data)
|
|
Err(_) => Ok(None),
|
|
}
|
|
}
|
|
|
|
fn get(&self, uid: &str, field: &str) -> Result<Vec<String>, String> {
|
|
let redis_key = format!("owned:garage:{}", uid);
|
|
|
|
// Retrieve the specific field from the hash
|
|
let field_json = self.client.hash_get(redis_key, field.to_string())?;
|
|
|
|
// Return empty vector if field is empty
|
|
if field_json.is_empty() {
|
|
return Ok(Vec::new());
|
|
}
|
|
|
|
// Deserialize the JSON array
|
|
serde_json::from_str::<Vec<String>>(&field_json)
|
|
.map_err(|e| format!("Failed to deserialize field '{}': {}", field, e))
|
|
}
|
|
|
|
fn delete(&self, uid: &str) -> Result<(), String> {
|
|
let redis_key = format!("owned:garage:{}", uid);
|
|
self.client.delete_key(redis_key)
|
|
}
|
|
|
|
fn exists(&self, uid: &str) -> Result<bool, String> {
|
|
let redis_key = format!("owned:garage:{}", uid);
|
|
self.client.key_exists(redis_key)
|
|
}
|
|
}
|