forge/lib/repositories/src/v_garage.rs
Jacob Schmidt 45a4f7460a Integrate task contracts and CAD UI pipeline
- 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
2026-03-28 02:20:34 -05:00

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)
}
}