//! 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, 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, 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; } /// 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 { client: C, } impl RedisVGarageRepository { pub fn new(client: C) -> Self { Self { client } } } impl VGarageRepository for RedisVGarageRepository { 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, 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 = 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::(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, 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::>(&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 { let redis_key = format!("owned:garage:{}", uid); self.client.key_exists(redis_key) } }