use std::fs; use std::path::Path; use serde::{Serialize, Deserialize}; use rust_decimal::Decimal; use rust_decimal_macros::dec; use crate::models::user::Transaction; use std::collections::HashMap; use std::sync::{Arc, RwLock}; use futures_util::lock::Mutex; use lazy_static::lazy_static; use chrono::{Utc, DateTime}; use uuid::Uuid; use std::time::Instant; /// Namespace type for user persistence utilities and methods. pub struct UserPersistence; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] pub struct UserPersistentData { pub user_email: String, pub wallet_balance_usd: Decimal, pub transactions: Vec, pub staked_amount_usd: Decimal, pub pool_positions: HashMap, // Profile information pub name: Option, pub country: Option, pub timezone: Option, // Password information (hashed) pub password_hash: Option, // Service provider data pub services: Vec, pub service_requests: Vec, // Customer service bookings #[serde(default)] pub service_bookings: Vec, // Availability settings pub availability: Option, // PHASE 3 FIX: SLA Management storage pub slas: Vec, // App provider data pub apps: Vec, pub app_deployments: Vec, // Account deletion tracking pub deleted: Option, pub deleted_at: Option, pub deletion_reason: Option, // ResourceProvider-specific data pub nodes: Vec, pub resource_provider_earnings: Vec, pub resource_provider_settings: Option, #[serde(default)] pub slice_products: Vec, // User activity tracking pub user_activities: Vec, pub user_preferences: Option, pub usage_statistics: Option, // Order history #[serde(default)] pub orders: Vec, // Node rental data #[serde(default)] pub active_product_rentals: Vec, #[serde(default)] pub resource_provider_rental_earnings: Vec, #[serde(default)] pub node_rentals: Vec, // Node groups for resource_provider organization #[serde(default)] pub node_groups: Vec, // NEW: Slice rental tracking for users #[serde(default)] pub slice_rentals: Vec, // Slice assignment tracking for users #[serde(default)] pub slice_assignments: Vec, // Currency preferences for OpenRouter-style enhancements #[serde(default)] pub display_currency: Option, // "USD", "CAD", "EUR", etc. #[serde(default)] pub quick_topup_amounts: Option>, // Customizable preset amounts // Auto top-up settings #[serde(default)] pub auto_topup_settings: Option, // User-created products (applications/services for marketplace) #[serde(default)] pub products: Vec, // Owned products tracking #[serde(default)] pub owned_products: Vec, #[serde(default)] pub owned_product_ids: Vec, // SSH key management #[serde(default)] pub ssh_keys: Vec, // Messaging system #[serde(default)] pub message_threads: Option>, #[serde(default)] pub messages: Option>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AutoTopUpSettings { pub enabled: bool, pub threshold_amount_usd: Decimal, pub topup_amount_usd: Decimal, pub payment_method_id: String, pub daily_limit_usd: Option, pub monthly_limit_usd: Option, pub created_at: DateTime, pub updated_at: DateTime, } impl AutoTopUpSettings { pub fn builder() -> crate::models::builders::AutoTopUpSettingsBuilder { crate::models::builders::AutoTopUpSettingsBuilder::new() } } impl Default for UserPersistentData { fn default() -> Self { Self { user_email: String::new(), wallet_balance_usd: Decimal::ZERO, transactions: Vec::new(), staked_amount_usd: Decimal::ZERO, pool_positions: HashMap::new(), name: None, country: None, timezone: None, password_hash: None, services: Vec::new(), service_requests: Vec::new(), service_bookings: Vec::new(), availability: None, slas: Vec::new(), apps: Vec::new(), app_deployments: Vec::new(), deleted: None, deleted_at: None, deletion_reason: None, nodes: Vec::new(), resource_provider_earnings: Vec::new(), resource_provider_settings: None, slice_products: Vec::new(), user_activities: Vec::new(), user_preferences: None, usage_statistics: None, orders: Vec::new(), active_product_rentals: Vec::new(), resource_provider_rental_earnings: Vec::new(), node_rentals: Vec::new(), node_groups: Vec::new(), slice_rentals: Vec::new(), slice_assignments: Vec::new(), display_currency: Some("USD".to_string()), // Default to USD for new users quick_topup_amounts: Some(vec![dec!(10), dec!(25), dec!(50), dec!(100)]), // USD amounts auto_topup_settings: None, // User can configure later products: Vec::new(), // Initialize empty products list owned_products: Vec::new(), // Initialize empty owned products list owned_product_ids: Vec::new(), // Initialize empty owned product IDs list ssh_keys: Vec::new(), message_threads: None, messages: None, // Initialize empty SSH keys list } } } /// Product rental record for users #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProductRental { pub id: String, pub rental_id: String, // Explicit rental identifier pub product_id: String, pub product_name: String, pub rental_type: String, // "slice", "full_node", "service", "app" pub monthly_cost: Decimal, pub start_date: String, pub end_date: String, pub status: String, // "Active", "Expired", "Cancelled" pub provider_email: String, pub customer_email: String, pub metadata: HashMap, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AvailabilitySettings { pub available: bool, pub weekly_hours: i32, pub updated_at: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PoolPosition { pub pool_id: String, pub amount: Decimal, pub entry_rate: Decimal, pub timestamp: chrono::DateTime, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServiceLevelAgreement { pub id: String, pub name: String, pub description: String, pub service_id: Option, // Associated service pub response_time_hours: i32, pub resolution_time_hours: i32, pub availability_percentage: f32, pub support_hours: String, // e.g., "24/7", "Business Hours" pub escalation_procedure: String, pub penalties: Vec, pub created_at: String, pub status: String, // Active, Inactive, Draft } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SLAPenalty { pub breach_type: String, // Response Time, Resolution Time, Availability pub threshold: String, pub penalty_amount: i32, pub penalty_type: String, // Credit, Refund, Discount } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AppDeployment { pub id: String, pub app_id: String, pub app_name: String, pub customer_name: String, pub customer_email: String, pub deployed_date: String, pub created_at: String, pub status: String, // Active, Inactive, Error, Maintenance pub health_score: f32, pub region: String, pub instances: i32, pub resource_usage: crate::models::user::ResourceUtilization, pub monthly_revenue: i32, pub last_updated: String, #[serde(default)] pub auto_healing: Option, } impl UserPersistence { const DATA_DIR: &'static str = "user_data"; // Per-user async lock map to serialize modifications per user } // Global map of user email -> async mutex lazy_static! { static ref USER_LOCKS: RwLock>>> = RwLock::new(HashMap::new()); } impl UserPersistence { /// Get or create a per-user async lock. Use as: /// let lock = UserPersistence::get_user_lock(email); /// let _guard = lock.lock().await; // hold for critical section pub fn get_user_lock(user_email: &str) -> Arc> { // Fast path: try read lock first if let Some(lock) = USER_LOCKS.read().unwrap().get(user_email).cloned() { return lock; } // Insert if missing with write lock let mut map = USER_LOCKS.write().unwrap(); map.entry(user_email.to_string()) .or_insert_with(|| Arc::new(Mutex::new(()))) .clone() } fn ensure_data_dir() -> Result<(), Box> { if !Path::new(Self::DATA_DIR).exists() { fs::create_dir_all(Self::DATA_DIR)?; } Ok(()) } fn get_user_file_path(user_email: &str) -> String { // Sanitize email for filename let sanitized = user_email.replace("@", "_at_").replace(".", "_"); format!("{}/{}.json", Self::DATA_DIR, sanitized) } pub fn save_user_data(data: &UserPersistentData) -> Result<(), Box> { let start_total = Instant::now(); Self::ensure_data_dir()?; let file_path = Self::get_user_file_path(&data.user_email); let tmp_path = format!("{}.tmp.{}", &file_path, Uuid::new_v4()); let json_data = serde_json::to_string_pretty(data)?; log::info!( target: "user_persistence", "save_user_data:start path={} email={} sizes:transactions={} services={} requests={} bookings={}", file_path, data.user_email, data.transactions.len(), data.services.len(), data.service_requests.len(), data.service_bookings.len() ); // Write to a unique temp file first { let mut f = std::fs::OpenOptions::new() .create_new(true) .write(true) .open(&tmp_path)?; use std::io::Write as _; f.write_all(json_data.as_bytes())?; f.sync_all()?; } // Atomically rename temp file to final path fs::rename(&tmp_path, &file_path)?; // fsync the directory to ensure the rename is durable on disk if let Some(parent) = Path::new(&file_path).parent() { if let Ok(dir_file) = std::fs::File::open(parent) { let _ = dir_file.sync_all(); } } log::info!( target: "user_persistence", "save_user_data:success path={} email={} total_ms={}", file_path, data.user_email, start_total.elapsed().as_millis() ); Ok(()) } pub fn load_user_data(user_email: &str) -> Option { let start_total = Instant::now(); let file_path = Self::get_user_file_path(user_email); if Path::new(&file_path).exists() { match fs::read_to_string(&file_path) { Ok(json_data) => { match serde_json::from_str::(&json_data) { Ok(data) => { log::debug!( target: "user_persistence", "load_user_data:success path={} email={} total_ms={}", file_path, user_email, start_total.elapsed().as_millis() ); Some(data) }, Err(e) => { // CRITICAL: Don't fall back to mock data for existing files! // This would cause data loss. The #[serde(default)] should handle missing fields. log::error!( target: "user_persistence", "load_user_data:parse_error path={} email={} err={}", file_path, user_email, e ); None } } }, Err(e) => { log::error!( target: "user_persistence", "load_user_data:read_error path={} email={} err={}", file_path, user_email, e ); None } } } else { log::debug!( target: "user_persistence", "load_user_data:not_found path={} email={} total_ms={}", file_path, user_email, start_total.elapsed().as_millis() ); None } } /// Async wrapper that acquires the per-user lock before loading, with structured logging pub async fn load_user_data_locked(user_email: &str, req_id: Option<&str>) -> Option { let lock = Self::get_user_lock(user_email); let wait_start = Instant::now(); log::debug!( target: "user_persistence", "load_user_data_locked:waiting_for_lock email={}{}", user_email, req_id.map(|r| format!(" req_id={}", r)).unwrap_or_default() ); let _g = lock.lock().await; let wait_ms = wait_start.elapsed().as_millis(); log::info!( target: "user_persistence", "load_user_data_locked:lock_acquired email={} wait_ms={}{}", user_email, wait_ms, req_id.map(|r| format!(" req_id={}", r)).unwrap_or_default() ); Self::load_user_data(user_email) } /// Async wrapper that acquires the per-user lock before saving, with structured logging pub async fn save_user_data_locked(data: &UserPersistentData, req_id: Option<&str>) -> Result<(), Box> { let lock = Self::get_user_lock(&data.user_email); let wait_start = Instant::now(); log::debug!( target: "user_persistence", "save_user_data_locked:waiting_for_lock email={}{}", data.user_email, req_id.map(|r| format!(" req_id={}", r)).unwrap_or_default() ); let _g = lock.lock().await; let wait_ms = wait_start.elapsed().as_millis(); log::info!( target: "user_persistence", "save_user_data_locked:lock_acquired email={} wait_ms={}{}", data.user_email, wait_ms, req_id.map(|r| format!(" req_id={}", r)).unwrap_or_default() ); Self::save_user_data(data) } fn initialize_new_user_with_defaults(user_email: &str) -> Option { // Use the existing create_default_user_data method for consistency let initial_data = Self::create_default_user_data(user_email); // Save the initial data if let Err(e) = Self::save_user_data(&initial_data) { return None; } Some(initial_data) } // Keep the old function for backward compatibility where mock data is still needed // (e.g., for incomplete features like mycelium gateways) fn initialize_user_with_mock_balance(user_email: &str) -> Option { // Initialize with clean persistent data - no mock dependencies let initial_data = crate::models::builders::SessionDataBuilder::new_user(user_email); // Save the initial data if let Err(e) = Self::save_user_data(&initial_data) { return None; } Some(initial_data) } pub fn update_user_profile( user_email: &str, name: Option, country: Option, timezone: Option ) -> Result<(), Box> { // Load existing data or create new let mut data = crate::models::builders::SessionDataBuilder::load_or_create(user_email); // Update profile fields if let Some(n) = name { data.name = Some(n); } if let Some(c) = country { data.country = Some(c); } if let Some(t) = timezone { data.timezone = Some(t); } // Save updated data Self::save_user_data(&data)?; Ok(()) } pub fn update_user_password( user_email: &str, password_hash: String ) -> Result<(), Box> { // Load existing data or create new let mut data = crate::models::builders::SessionDataBuilder::load_or_create(user_email); // Update password hash data.password_hash = Some(password_hash); // Save updated data Self::save_user_data(&data)?; Ok(()) } pub fn update_notification_settings( user_email: &str, notification_settings: crate::models::user::NotificationSettings ) -> Result<(), Box> { // Load existing data or create new let mut data = crate::models::builders::SessionDataBuilder::load_or_create(user_email); // Initialize user_preferences if it doesn't exist if data.user_preferences.is_none() { data.user_preferences = Some(crate::models::user::UserPreferences::default()); } // Update notification settings within user_preferences if let Some(ref mut prefs) = data.user_preferences { prefs.notification_settings = Some(notification_settings); } // Save updated data Self::save_user_data(&data)?; Ok(()) } pub fn delete_user_data(user_email: &str) -> Result<(), Box> { let file_path = Self::get_user_file_path(user_email); if Path::new(&file_path).exists() { fs::remove_file(file_path)?; } Ok(()) } /// Soft delete user account - marks as deleted instead of removing data pub fn soft_delete_user_account( user_email: &str, deletion_reason: Option ) -> Result<(), Box> { let data = match Self::load_user_data(user_email) { Some(mut data) => { // Mark account as deleted data.deleted = Some(true); data.deleted_at = Some(Utc::now().to_rfc3339()); data.deletion_reason = deletion_reason; data }, None => { // Create minimal data for deletion record using centralized builder let mut data = crate::models::builders::SessionDataBuilder::new_user(user_email); data.deleted = Some(true); data.deleted_at = Some(Utc::now().to_rfc3339()); data.deletion_reason = deletion_reason; data } }; Self::save_user_data(&data)?; Ok(()) } /// Check if user account is deleted pub fn is_user_deleted(user_email: &str) -> bool { if let Some(data) = Self::load_user_data(user_email) { data.deleted.unwrap_or(false) } else { false } } /// Restore deleted user account (for recovery purposes) pub fn restore_user_account(user_email: &str) -> Result> { let mut data = match Self::load_user_data(user_email) { Some(data) => data, None => return Ok(false), // No data means no account to restore }; if data.deleted.unwrap_or(false) { data.deleted = None; data.deleted_at = None; data.deletion_reason = None; Self::save_user_data(&data)?; Ok(true) } else { Ok(false) // Account was not deleted } } /// Add a service to user's persistent data pub fn add_user_service( user_email: &str, service: crate::models::user::Service ) -> Result<(), Box> { // Load existing data or create new using centralized builder let mut data = crate::models::builders::SessionDataBuilder::load_or_create(user_email); // Add service if not already present if !data.services.iter().any(|s| s.id == service.id) { data.services.push(service.clone()); } else { } // Save updated data Self::save_user_data(&data)?; Ok(()) } /// Get all services for a user pub fn get_user_services(user_email: &str) -> Vec { if let Some(data) = Self::load_user_data(user_email) { data.services } else { Vec::default() } } /// Remove a service from user's persistent data pub fn remove_user_service( user_email: &str, service_id: &str ) -> Result> { let mut data = match Self::load_user_data(user_email) { Some(data) => data, None => return Ok(false), // No data means no service to remove }; let initial_len = data.services.len(); data.services.retain(|s| s.id != service_id); let removed = data.services.len() < initial_len; if removed { Self::save_user_data(&data)?; } Ok(removed) } /// Get all services from all users for marketplace aggregation (excludes deleted users) pub fn get_all_users_services() -> Vec { let mut all_services = Vec::default(); // Get all user data files if let Ok(entries) = fs::read_dir(Self::DATA_DIR) { for entry in entries.flatten() { if let Some(file_name) = entry.file_name().to_str() { // Only process per-user files, e.g. user1_at_example_com.json let is_user_file = file_name.ends_with(".json") && file_name.contains("_at_") && !file_name.contains("_cart"); if is_user_file { // Extract email from filename let email = file_name .trim_end_matches(".json") .replace("_at_", "@") .replace("_", "."); // Skip deleted users if Self::is_user_deleted(&email) { continue; } // Load user services let user_services = Self::get_user_services(&email); let service_count = user_services.len(); all_services.extend(user_services); if service_count > 0 { } } } } } all_services } /// Get all apps from all users for marketplace aggregation (excludes deleted users) pub fn get_all_users_apps() -> Vec { let mut all_apps = Vec::default(); // Get all user data files if let Ok(entries) = fs::read_dir(Self::DATA_DIR) { for entry in entries.flatten() { if let Some(file_name) = entry.file_name().to_str() { // Only process per-user files, e.g. user1_at_example_com.json let is_user_file = file_name.ends_with(".json") && file_name.contains("_at_") && !file_name.contains("_cart"); if is_user_file { // Extract email from filename let email = file_name .trim_end_matches(".json") .replace("_at_", "@") .replace("_", "."); // Skip deleted users if Self::is_user_deleted(&email) { continue; } // Load user apps let user_apps = Self::get_user_apps(&email); let app_count = user_apps.len(); all_apps.extend(user_apps); if app_count > 0 { } } } } } all_apps } /// Add a product to user's persistent data pub fn add_user_product( user_email: &str, product: crate::models::product::Product ) -> Result<(), Box> { // Load existing data or create new using centralized builder let mut data = crate::models::builders::SessionDataBuilder::load_or_create(user_email); // Add product if not already present if !data.products.iter().any(|p| p.id == product.id) { data.products.push(product.clone()); } // Save updated data Self::save_user_data(&data)?; Ok(()) } /// Get all products for a user pub fn get_user_products(user_email: &str) -> Vec { if let Some(data) = Self::load_user_data(user_email) { data.products } else { Vec::default() } } /// Remove a product from user's persistent data pub fn remove_user_product( user_email: &str, product_id: &str ) -> Result> { let mut data = match Self::load_user_data(user_email) { Some(data) => data, None => return Ok(false), // No data means no product to remove }; let initial_len = data.products.len(); data.products.retain(|p| p.id != product_id); let removed = data.products.len() < initial_len; if removed { Self::save_user_data(&data)?; } Ok(removed) } /// Get all products from all users for marketplace aggregation (excludes deleted users) pub fn get_all_users_products() -> Vec { let mut all_products = Vec::default(); println!("🔍 USER PERSISTENCE: Looking for user data files in {}", Self::DATA_DIR); // Get all user data files if let Ok(entries) = fs::read_dir(Self::DATA_DIR) { let mut file_count = 0; for entry in entries.flatten() { if let Some(file_name) = entry.file_name().to_str() { // Only consider actual per-user files, e.g. user1_at_example_com.json let is_user_file = file_name.ends_with(".json") && file_name.contains("_at_") && !file_name.contains("_cart"); if is_user_file { file_count += 1; // Extract email from filename let email = file_name .trim_end_matches(".json") .replace("_at_", "@") .replace("_", "."); println!( "🔍 USER PERSISTENCE: Processing user file: {} -> email: {}", file_name, email ); // Skip deleted users if Self::is_user_deleted(&email) { println!("🔍 USER PERSISTENCE: Skipping deleted user: {}", email); continue; } // Load user products let user_products = Self::get_user_products(&email); println!( "🔍 USER PERSISTENCE: User {} has {} products", email, user_products.len() ); all_products.extend(user_products); } } } println!("🔍 USER PERSISTENCE: Processed {} JSON files, found {} total products", file_count, all_products.len()); } else { println!("🔍 USER PERSISTENCE: Could not read directory {}", Self::DATA_DIR); } all_products } /// Update user availability settings pub fn update_user_availability( user_email: &str, availability: AvailabilitySettings ) -> Result<(), Box> { // Load existing data or create new using centralized builder let mut data = crate::models::builders::SessionDataBuilder::load_or_create(user_email); // Update availability settings data.availability = Some(availability); // Save updated data Self::save_user_data(&data)?; Ok(()) } /// Get user availability settings pub fn get_user_availability(user_email: &str) -> Option { if let Some(data) = Self::load_user_data(user_email) { data.availability } else { None } } /// Add a service request to user's persistent data pub fn add_user_service_request( user_email: &str, service_request: crate::models::user::ServiceRequest ) -> Result<(), Box> { let mut data = crate::models::builders::SessionDataBuilder::load_or_create(user_email); // Add service request if not already present if !data.service_requests.iter().any(|r| r.id == service_request.id) { data.service_requests.push(service_request.clone()); } else { } // Save updated data log::info!( target: "user_persistence", "add_user_service_request:before_save email={} total_requests={} added_request_id={}", user_email, data.service_requests.len(), service_request.id ); Self::save_user_data(&data)?; log::info!( target: "user_persistence", "add_user_service_request:after_save email={} total_requests={}", user_email, data.service_requests.len() ); Ok(()) } /// Get all service requests for a user pub fn get_user_service_requests(user_email: &str) -> Vec { if let Some(data) = Self::load_user_data(user_email) { data.service_requests } else { Vec::default() } } /// Update service request status pub fn update_service_request_status( user_email: &str, request_id: &str, new_status: &str ) -> Result> { let mut data = match Self::load_user_data(user_email) { Some(data) => data, None => return Ok(false), // No data means no request to update }; let mut updated = false; for request in &mut data.service_requests { if request.id == request_id { request.status = new_status.to_string(); // If marking as completed, set completed_date; otherwise leave as-is if new_status == "Completed" { request.completed_date = Some(chrono::Utc::now().format("%Y-%m-%d").to_string()); } updated = true; break; } } if updated { Self::save_user_data(&data)?; } Ok(updated) } /// Update service request progress and related data pub fn update_service_request_progress( user_email: &str, request_id: &str, progress: i32, priority: Option<&str>, hours_worked: Option, notes: Option<&str>, new_status: &str ) -> Result> { let mut data = match Self::load_user_data(user_email) { Some(data) => data, None => return Ok(false), // No data means no request to update }; let mut updated = false; for request in &mut data.service_requests { if request.id == request_id { // Update status request.status = new_status.to_string(); // Update progress field request.progress = Some(progress as f32); // Update priority if provided if let Some(priority) = priority { request.priority = priority.to_string(); } // Persist hours worked and notes when provided if let Some(hours) = hours_worked { request.hours_worked = Some(hours as i32); } if let Some(notes) = notes { request.notes = Some(notes.to_string()); } // If completed, set completed_date if new_status == "Completed" || progress >= 100 { request.completed_date = Some(chrono::Utc::now().format("%Y-%m-%d").to_string()); } updated = true; break; } } if updated { Self::save_user_data(&data)?; } Ok(updated) } /// Update a customer's service booking fields by booking id /// Returns Ok(true) if updated and saved, Ok(false) if booking not found or user has no data pub fn update_user_service_booking_fields( user_email: &str, booking_id: &str, status: Option<&str>, progress: Option, priority: Option<&str>, completed_date: Option<&str>, ) -> Result> { let mut data = match Self::load_user_data(user_email) { Some(data) => data, None => return Ok(false), }; let mut updated = false; for booking in &mut data.service_bookings { if booking.id == booking_id { if let Some(s) = status { booking.status = s.to_string(); } if let Some(p) = progress { booking.progress = Some(p as f32); } if let Some(pr) = priority { booking.priority = pr.to_string(); } if let Some(cd) = completed_date { booking.completed_date = Some(cd.to_string()); } updated = true; break; } } if updated { log::info!( target: "user_persistence", "update_user_service_booking_fields:before_save email={} booking_id={} status={:?} progress={:?} priority={:?}", user_email, booking_id, status, progress, priority ); Self::save_user_data(&data)?; log::info!( target: "user_persistence", "update_user_service_booking_fields:after_save email={} total_bookings={}", user_email, data.service_bookings.len() ); } Ok(updated) } /// Remove a service request from user's persistent data pub fn remove_user_service_request( user_email: &str, request_id: &str ) -> Result> { let mut data = match Self::load_user_data(user_email) { Some(data) => data, None => return Ok(false), // No data means no request to remove }; let initial_len = data.service_requests.len(); data.service_requests.retain(|r| r.id != request_id); let removed = data.service_requests.len() < initial_len; if removed { Self::save_user_data(&data)?; } Ok(removed) } // ============================================================================= // SERVICE BOOKING METHODS (Customer side of service purchases) // ============================================================================= /// Add a service booking to customer's data pub fn add_user_service_booking( user_email: &str, service_booking: crate::models::user::ServiceBooking ) -> Result<(), Box> { let mut data = Self::load_user_data(user_email) .unwrap_or_else(|| Self::create_default_user_data(user_email)); // Add service booking if not already present if !data.service_bookings.iter().any(|b| b.id == service_booking.id) { data.service_bookings.push(service_booking.clone()); } else { } log::info!( target: "user_persistence", "add_user_service_booking:before_save email={} total_bookings={} added_booking_id={}", user_email, data.service_bookings.len(), service_booking.id ); Self::save_user_data(&data)?; log::info!( target: "user_persistence", "add_user_service_booking:after_save email={} total_bookings={}", user_email, data.service_bookings.len() ); Ok(()) } /// Get customer's service bookings pub fn get_user_service_bookings(user_email: &str) -> Vec { if let Some(data) = Self::load_user_data(user_email) { data.service_bookings } else { Vec::new() } } /// Convert ServiceRequest to ServiceBooking for customer pub fn create_service_booking_from_request( request: &crate::models::user::ServiceRequest, customer_email: &str, provider_email: &str ) -> crate::models::user::ServiceBooking { crate::models::user::ServiceBooking::builder() .id(&request.id) .service_id(&format!("svc_{}", &request.id[4..])) // Extract service ID from request ID .service_name(&request.service_name) .provider_email(provider_email) .customer_email(customer_email) .budget(request.budget) .estimated_hours(request.estimated_hours.unwrap_or(0)) .status(&request.status) .requested_date(&request.requested_date) .priority(&request.priority) .description(request.description.clone()) .booking_date(&chrono::Utc::now().format("%Y-%m-%d").to_string()) .build() .unwrap() } /// PHASE 3 FIX: Add a SLA to user's persistent data pub fn add_user_sla( user_email: &str, sla: ServiceLevelAgreement ) -> Result<(), Box> { let mut data = crate::models::builders::SessionDataBuilder::load_or_create(user_email); // Add SLA if not already present if !data.slas.iter().any(|s| s.id == sla.id) { data.slas.push(sla.clone()); } else { } // Save updated data Self::save_user_data(&data)?; Ok(()) } /// PHASE 3 FIX: Get all SLAs for a user pub fn get_user_slas(user_email: &str) -> Vec { if let Some(data) = Self::load_user_data(user_email) { data.slas } else { Vec::default() } } /// PHASE 3 FIX: Update a SLA pub fn update_user_sla( user_email: &str, sla: ServiceLevelAgreement ) -> Result> { let mut data = match Self::load_user_data(user_email) { Some(data) => data, None => return Ok(false), // No data means no SLA to update }; let mut updated = false; for existing_sla in &mut data.slas { if existing_sla.id == sla.id { *existing_sla = sla.clone(); updated = true; break; } } if updated { Self::save_user_data(&data)?; } Ok(updated) } /// PHASE 3 FIX: Remove a SLA from user's persistent data pub fn remove_user_sla( user_email: &str, sla_id: &str ) -> Result> { let mut data = match Self::load_user_data(user_email) { Some(data) => data, None => return Ok(false), // No data means no SLA to remove }; let initial_len = data.slas.len(); data.slas.retain(|s| s.id != sla_id); let removed = data.slas.len() < initial_len; if removed { Self::save_user_data(&data)?; } Ok(removed) } /// PHASE 3 FIX: Get SLA by ID pub fn get_user_sla_by_id(user_email: &str, sla_id: &str) -> Option { if let Some(data) = Self::load_user_data(user_email) { data.slas.into_iter().find(|s| s.id == sla_id) } else { None } } // ======================================== // APP PROVIDER MANAGEMENT FUNCTIONS // ======================================== /// Get all apps for a user pub fn get_user_apps(user_email: &str) -> Vec { if let Some(data) = Self::load_user_data(user_email) { for app in &data.apps { } data.apps } else { Vec::default() } } /// Add a new app to user's persistent data pub fn add_user_app( user_email: &str, app: crate::models::user::PublishedApp ) -> Result<(), Box> { let mut data = crate::models::builders::SessionDataBuilder::load_or_create(user_email); data.apps.push(app.clone()); Self::save_user_data(&data)?; Ok(()) } /// Update an existing app in user's persistent data pub fn update_user_app( user_email: &str, updated_app: crate::models::user::PublishedApp ) -> Result> { let mut data = match Self::load_user_data(user_email) { Some(data) => data, None => return Ok(false), // No data means no app to update }; let mut updated = false; for app in &mut data.apps { if app.id == updated_app.id { *app = updated_app.clone(); updated = true; break; } } if updated { Self::save_user_data(&data)?; } Ok(updated) } /// Remove an app from user's persistent data pub fn remove_user_app( user_email: &str, app_id: &str ) -> Result> { let mut data = match Self::load_user_data(user_email) { Some(data) => data, None => return Ok(false), // No data means no app to remove }; let initial_len = data.apps.len(); data.apps.retain(|app| app.id != app_id); let removed = data.apps.len() < initial_len; if removed { Self::save_user_data(&data)?; } Ok(removed) } /// Get app deployments for a user pub fn get_user_app_deployments(user_email: &str) -> Vec { if let Some(data) = Self::load_user_data(user_email) { data.app_deployments } else { Vec::default() } } /// Add a new app deployment pub fn add_user_app_deployment( user_email: &str, deployment: AppDeployment ) -> Result<(), Box> { let mut data = crate::models::builders::SessionDataBuilder::load_or_create(user_email); data.app_deployments.push(deployment.clone()); Self::save_user_data(&data)?; Ok(()) } /// Load apps for user directory (for marketplace integration) pub fn load_apps_for_user(user_email: &str) -> Vec { // Check if user has an apps.json file let user_dir = format!("./user_data/{}", user_email); let apps_file = format!("{}/apps.json", user_dir); if Path::new(&apps_file).exists() { match fs::read_to_string(&apps_file) { Ok(content) => { match serde_json::from_str::>(&content) { Ok(apps) => { for app in &apps { } apps }, Err(e) => { Vec::default() } } }, Err(e) => { Vec::default() } } } else { Vec::default() } } /// Get all nodes for a user pub fn get_user_nodes(user_email: &str) -> Vec { if let Some(data) = Self::load_user_data(user_email) { data.nodes } else { Vec::default() } } /// Get resource_provider earnings for a user pub fn get_resource_provider_earnings(user_email: &str) -> Vec { if let Some(data) = Self::load_user_data(user_email) { data.resource_provider_earnings } else { Vec::default() } } /// Get resource_provider settings for a user pub fn get_resource_provider_settings(user_email: &str) -> Option { if let Some(data) = Self::load_user_data(user_email) { data.resource_provider_settings } else { None } } /// Get slice products for a user pub fn get_slice_products(user_email: &str) -> Vec { if let Some(data) = Self::load_user_data(user_email) { data.slice_products } else { Vec::default() } } /// Get user slice products (alias for consistency) pub fn get_user_slice_products(user_email: &str) -> Vec { Self::get_slice_products(user_email) } /// Save multiple slice products pub fn save_user_slice_products(user_email: &str, products: &[crate::models::product::Product]) -> Result<(), String> { let mut data = Self::load_user_data(user_email).unwrap_or_else(|| { Self::create_default_user_data(user_email) }); data.slice_products = products.to_vec(); Self::save_user_data(&data).map_err(|e| e.to_string()) } /// Create default user data with industry-standard factory method pub fn create_default_user_data(user_email: &str) -> UserPersistentData { UserPersistentData { user_email: user_email.to_string(), wallet_balance_usd: dec!(0), display_currency: Some("USD".to_string()), quick_topup_amounts: Some(vec![dec!(10), dec!(25), dec!(50), dec!(100)]), ..Default::default() } } /// Create user data with specific balance pub fn create_user_with_balance(user_email: &str, balance: Decimal) -> UserPersistentData { UserPersistentData { user_email: user_email.to_string(), wallet_balance_usd: balance, display_currency: Some("USD".to_string()), quick_topup_amounts: Some(vec![dec!(10), dec!(25), dec!(50), dec!(100)]), ..Default::default() } } /// Load user data or create default if not found pub fn load_or_create_user(user_email: &str) -> UserPersistentData { Self::load_user_data(user_email) .unwrap_or_else(|| Self::create_default_user_data(user_email)) } /// Save slice product pub fn save_slice_product(user_email: &str, product: crate::models::product::Product) -> Result<(), String> { let mut data = Self::load_user_data(user_email).unwrap_or_else(|| { Self::create_default_user_data(user_email) }); // Check if product already exists and update, otherwise add new if let Some(existing_index) = data.slice_products.iter().position(|p| p.id == product.id) { data.slice_products[existing_index] = product; } else { data.slice_products.push(product); } Self::save_user_data(&data).map_err(|e| e.to_string()) } /// Delete slice product pub fn delete_slice_product(user_email: &str, product_id: &str) -> Result<(), String> { let mut data = Self::load_user_data(user_email).unwrap_or_else(|| { Self::create_default_user_data(user_email) }); data.slice_products.retain(|p| p.id != product_id); Self::save_user_data(&data).map_err(|e| e.to_string()) } /// Get user activities pub fn get_user_activities(user_email: &str, limit: Option) -> Vec { if let Some(data) = Self::load_user_data(user_email) { let mut activities = data.user_activities; if let Some(limit) = limit { activities.truncate(limit); } activities } else { Vec::default() } } /// Get user statistics pub fn get_user_statistics(user_email: &str) -> Option { if let Some(data) = Self::load_user_data(user_email) { data.usage_statistics } else { None } } /// Calculate user statistics - simplified version pub fn calculate_user_statistics(_user_email: &str) -> Result> { // Return a basic statistics object using existing fields Ok(crate::models::user::UsageStatistics { cpu_usage: 0.0, memory_usage: 0.0, storage_usage: 0.0, network_usage: 0.0, active_services: 0, total_spent: rust_decimal_macros::dec!(0), favorite_categories: Vec::new(), usage_trends: Vec::new(), login_frequency: 1.0, total_deployments: 0, preferred_regions: Vec::new(), account_age_days: 30, last_activity: chrono::Utc::now(), }) } /// Add a node to user's persistent data pub fn add_user_node( user_email: &str, node: crate::models::user::FarmNode ) -> Result<(), Box> { let mut data = crate::models::builders::SessionDataBuilder::load_or_create(user_email); // Check for duplicates by both node ID and grid node ID let node_exists = data.nodes.iter().any(|n| { n.id == node.id || (n.grid_node_id.is_some() && node.grid_node_id.is_some() && n.grid_node_id == node.grid_node_id) }); if !node_exists { data.nodes.push(node.clone()); // Save updated data immediately Self::save_user_data(&data)?; } else { return Err("Node already exists".into()); } Ok(()) } /// Remove a node from user's persistent data pub fn remove_user_node( user_email: &str, node_id: &str ) -> Result<(), Box> { let mut data = Self::load_user_data(user_email) .ok_or("User data not found")?; // Find and remove the node let initial_count = data.nodes.len(); data.nodes.retain(|node| node.id != node_id); if data.nodes.len() < initial_count { // Save updated data Self::save_user_data(&data)?; Ok(()) } else { Err(format!("Node '{}' not found for user: {}", node_id, user_email).into()) } } /// Add a user activity pub fn add_user_activity( user_email: &str, activity: crate::models::user::UserActivity ) -> Result<(), Box> { let mut data = crate::models::builders::SessionDataBuilder::load_or_create(user_email); // Add activity data.user_activities.push(activity); // Keep only the last 100 activities to prevent unlimited growth if data.user_activities.len() > 100 { data.user_activities.drain(0..data.user_activities.len() - 100); } // Save updated data Self::save_user_data(&data)?; Ok(()) } }