use tera::Tera; use actix_web::{web, Result, Responder}; use crate::utils::response_builder::ResponseBuilder; use crate::utils::render_template; use crate::config::get_app_config; use actix_session::Session; use std::str::FromStr; use crate::models::user::User; use crate::models::builders::NodeStakingOptionsBuilder; use crate::services::session_manager::SessionManager; use rust_decimal::prelude::ToPrimitive; use crate::services::user_persistence::UserPersistence; use crate::services::user_service::UserService; use serde::Deserialize; use rust_decimal::Decimal; use uuid::Uuid; use chrono::Utc; use crate::utils::DataCleanup; use bcrypt::verify; use std::time::Instant; /// Controller for handling dashboard-related routes pub struct DashboardController; impl DashboardController { /// Check if user is authenticated, redirect to register if not fn check_authentication(session: &Session) -> Result<(), actix_web::HttpResponse> { match session.get::("user") { Ok(Some(_)) => Ok(()), _ => Err(ResponseBuilder::redirect("/register").build()?) } } /// Helper function to load user with persistent data from session fn load_user_with_persistent_data(session: &Session) -> Option { // Get basic user data let user_json = session.get::("user").ok()??; let mut user: User = serde_json::from_str(&user_json).ok()?; // Load fresh mock data based on user email let user_email = session.get::("user_email").ok()??; // Load persistent user data instead of mock data if let Some(user_data) = crate::services::user_persistence::UserPersistence::load_user_data(&user_email) { // Update user object with persistent data user.name = user_data.name.unwrap_or_else(|| user_email.clone()); } else { // Create default user data if none exists let _default_data = crate::services::user_persistence::UserPersistence::create_default_user_data(&user_email); } // Apply session data using SessionManager if let Some(session_data) = SessionManager::load_user_session_data(session) { SessionManager::apply_session_to_user(&mut user, &session_data); } // PHASE 1 FIX: Enhanced persistent data loading with better consistency if let Some(persistent_data) = UserPersistence::load_user_data(&user_email) { for service in &persistent_data.services { } if let Some(name) = persistent_data.name { user.name = name; } if let Some(country) = persistent_data.country { user.country = Some(country.clone()); } if let Some(timezone) = persistent_data.timezone { user.timezone = Some(timezone.clone()); } // Apply persistent services data - removed mock_data dependency // Service provider data now comes from UserPersistence directly // All data is accessible through persistent_data without mock layer } // All user data is now accessible through persistent_data without mock layer // Function clean end Some(user) } /// Helper function to count deployments across all users for a specific application provider fn count_cross_user_deployments(application_provider_email: &str) -> std::collections::HashMap { use std::collections::HashMap; let mut deployment_counts: HashMap = HashMap::new(); // Get all user data files let user_data_dir = std::path::Path::new("user_data"); if !user_data_dir.exists() { return deployment_counts; } // Read all user files if let Ok(entries) = std::fs::read_dir(user_data_dir) { for entry in entries.flatten() { if let Some(file_name) = entry.file_name().to_str() { if file_name.ends_with(".json") && file_name.contains("_at_") && !file_name.contains("_cart") && file_name != "session_data.json" { let file_path = entry.path(); // Read and parse user data if let Ok(content) = std::fs::read_to_string(&file_path) { if let Ok(user_data) = serde_json::from_str::(&content) { // Count deployments for this app provider's apps for deployment in &user_data.application_deployments { // Check if this deployment belongs to an app from our app provider // We need to get the application provider's apps to match let provider_apps = UserPersistence::get_user_apps(application_provider_email); for provider_app in &provider_apps { if deployment.app_id == provider_app.id && deployment.status == "Active" { *deployment_counts.entry(provider_app.id.clone()).or_insert(0) += 1; } } } } } } } } } deployment_counts } /// Renders the dashboard home page (shows login prompt if not authenticated) pub async fn index(tmpl: web::Data, session: Session) -> Result { let mut ctx = crate::models::builders::ContextBuilder::new() .active_page("dashboard") .active_section("home") .gitea_enabled(get_app_config().is_gitea_enabled()) .build(); // Inject currency display variables { let currency_service = match crate::services::currency::CurrencyService::builder().build() { Ok(svc) => svc, Err(_) => crate::services::currency::CurrencyService::new(), }; let display_currency = currency_service.get_user_preferred_currency(&session); let currency_symbol = currency_service .get_currency(&display_currency) .map(|c| c.symbol) .unwrap_or_else(|| "$".to_string()); ctx.insert("display_currency", &display_currency); ctx.insert("currency_symbol", ¤cy_symbol); } // Check if user is logged in and load with persistent data if let Some(user) = Self::load_user_with_persistent_data(&session) { // User is logged in if let Ok(Some(user_json)) = session.get::("user") { ctx.insert("user_json", &user_json); } // Load persistent dashboard data for templates if let Ok(Some(user_email)) = session.get::("user_email") { ctx.insert("user_email", &user_email); if let Ok(user_service) = UserService::builder().build() { let user_metrics = user_service.calculate_user_metrics(&user_email); ctx.insert("user_metrics", &user_metrics); let compute_resources = user_service.get_user_compute_resources(&user_email); let active_compute_resources_count = compute_resources.len(); ctx.insert("compute_resources", &compute_resources); ctx.insert("active_compute_resources_count", &active_compute_resources_count); let recent_activities = user_service.get_user_activities(&user_email, Some(10)); // Map activities to template-friendly fields expected by dashboard/index.html let recent_activities_table: Vec = recent_activities .iter() .map(|a| { let action = match a.activity_type { crate::models::user::ActivityType::Login => "Login", crate::models::user::ActivityType::Logout => "Logout", crate::models::user::ActivityType::PasswordChange => "Password Change", crate::models::user::ActivityType::CreditsTopup => "Credits Top-up", crate::models::user::ActivityType::NodeDeployment => "Node Deployment", crate::models::user::ActivityType::AppDeployment => "App Deployment", crate::models::user::ActivityType::SliceRental => "Slice Rental", crate::models::user::ActivityType::Purchase => "Purchase", crate::models::user::ActivityType::Deployment => "Deployment", crate::models::user::ActivityType::ServiceCreated => "Service Created", crate::models::user::ActivityType::AppPublished => "App Published", crate::models::user::ActivityType::NodeAdded => "Node Added", crate::models::user::ActivityType::NodeUpdated => "Node Updated", crate::models::user::ActivityType::ServiceRequested => "Service Requested", crate::models::user::ActivityType::WalletTopup => "Wallet Top-up", crate::models::user::ActivityType::ServiceCompleted => "Service Completed", crate::models::user::ActivityType::WalletTransaction => "Wallet Transaction", crate::models::user::ActivityType::ProfileUpdate => "Profile Update", crate::models::user::ActivityType::SettingsChange => "Settings Change", crate::models::user::ActivityType::MarketplaceView => "Marketplace View", crate::models::user::ActivityType::SliceCreated => "Slice Created", crate::models::user::ActivityType::SliceAllocated => "Slice Allocated", crate::models::user::ActivityType::SliceReleased => "Slice Released", crate::models::user::ActivityType::SliceRentalStarted => "Slice Rental Started", crate::models::user::ActivityType::SliceRentalStopped => "Slice Rental Stopped", crate::models::user::ActivityType::SliceRentalRestarted => "Slice Rental Restarted", crate::models::user::ActivityType::SliceRentalCancelled => "Slice Rental Cancelled", }; let status: String = a .metadata .as_ref() .and_then(|meta| meta.get("status")) .and_then(|v| v.as_str()) .unwrap_or_else(|| match a.activity_type { crate::models::user::ActivityType::Deployment => "Active", _ => "Completed", }) .to_string(); serde_json::json!({ "date": a.timestamp.format("%Y-%m-%d %H:%M").to_string(), "action": action, "status": status, "details": a.description }) }) .collect(); ctx.insert("recent_activities", &recent_activities_table); // Sum monthly cost across active compute resources let total_monthly_cost: rust_decimal::Decimal = compute_resources .iter() .map(|r| r.monthly_cost) .sum(); ctx.insert("total_monthly_cost", &total_monthly_cost); } } ctx.insert("user", &user); // Render the dashboard home page for authenticated users match render_template(&tmpl, "dashboard/index.html", &ctx) { Ok(response) => { Ok(response) }, Err(_e) => { ResponseBuilder::internal_error() .body("Template rendering failed").build() } } } else { // User is not logged in, render the welcome page (this is the nice UX page) render_template(&tmpl, "dashboard/welcome.html", &ctx) } } /// Renders the user section of the dashboard pub async fn user_section(tmpl: web::Data, session: Session) -> Result { let mut ctx = crate::models::builders::ContextBuilder::new() .active_page("dashboard") .active_section("user") .gitea_enabled(get_app_config().is_gitea_enabled()) .build(); // Inject currency display variables { let currency_service = match crate::services::currency::CurrencyService::builder().build() { Ok(svc) => svc, Err(_) => crate::services::currency::CurrencyService::new(), }; let display_currency = currency_service.get_user_preferred_currency(&session); let currency_symbol = currency_service .get_currency(&display_currency) .map(|c| c.symbol) .unwrap_or_else(|| "$".to_string()); ctx.insert("display_currency", &display_currency); ctx.insert("currency_symbol", ¤cy_symbol); } // Check if user is logged in if session.get::("user").unwrap_or(None).is_none() { // User is not logged in, show welcome page with login/register options return render_template(&tmpl, "dashboard/welcome.html", &ctx); } // Load user with real data (no mock data) if let Ok(Some(user_email)) = session.get::("user_email") { ctx.insert("user_email", &user_email); // Build user service for comprehensive data loading if let Ok(user_service) = crate::services::user_service::UserService::builder().build() { // Load user applications (purchased/deployed apps) let user_applications = user_service.get_user_applications(&user_email); ctx.insert("user_applications", &user_applications); let active_applications_count = user_applications.len(); ctx.insert("active_applications_count", &active_applications_count); // Load user services (purchased services) let user_services = user_service.get_user_purchased_services(&user_email); ctx.insert("user_services", &user_services); // Load user compute resources (slice rentals) let compute_resources = user_service.get_user_compute_resources(&user_email); let active_compute_resources_count = compute_resources.len(); ctx.insert("compute_resources", &compute_resources); ctx.insert("active_compute_resources_count", &active_compute_resources_count); let total_monthly_cost: rust_decimal::Decimal = compute_resources.iter().map(|r| r.monthly_cost).sum(); ctx.insert("total_monthly_cost", &total_monthly_cost); // Calculate average SLA percentage across compute resources (fallback to 99.0 if unavailable) let mut sla_sum: f64 = 0.0; let mut sla_count: u32 = 0; for res in &compute_resources { let s = res.sla.trim(); if !s.is_empty() && s != "Unknown" { // Keep only digits and decimal point (handles values like "99.9%" or "SLA 99.9%") let cleaned: String = s.chars().filter(|c| c.is_ascii_digit() || *c == '.').collect(); if let Ok(v) = cleaned.parse::() { if v > 0.0 && v <= 100.0 { sla_sum += v; sla_count += 1; } } } } let average_sla_percentage: f64 = if sla_count > 0 { sla_sum / sla_count as f64 } else { 99.0 }; ctx.insert("average_sla_percentage", &average_sla_percentage); // Load user activities let recent_activities = user_service.get_user_activities(&user_email, Some(10)); ctx.insert("recent_activities", &recent_activities); // Calculate user metrics let user_metrics = user_service.calculate_user_metrics(&user_email); ctx.insert("user_metrics", &user_metrics); } // Load user data from session (without mock data override) if let Ok(Some(user_json)) = session.get::("user") { if let Ok(mut user) = serde_json::from_str::(&user_json) { // Apply session data if available if let Some(session_data) = SessionManager::load_user_session_data(&session) { SessionManager::apply_session_to_user(&mut user, &session_data); } // Apply persistent data (profile info) but keep user data separate if let Some(persistent_data) = UserPersistence::load_user_data(&user_email) { if let Some(name) = persistent_data.name { user.name = name; } if let Some(country) = persistent_data.country { user.country = Some(country); } if let Some(timezone) = persistent_data.timezone { user.timezone = Some(timezone); } // Add slice rental data ctx.insert("slice_rentals", &persistent_data.slice_rentals); ctx.insert("wallet_balance", &persistent_data.wallet_balance_usd); } ctx.insert("user_json", &user_json); ctx.insert("user", &user); } } // Load slice rental service to get additional statistics if let Ok(slice_rental_service) = crate::services::slice_rental::SliceRentalService::builder().build() { let user_slice_rentals = slice_rental_service.get_user_slice_rentals(&user_email); ctx.insert("active_slice_rentals", &user_slice_rentals); // Calculate slice rental statistics with deployment type breakdown let total_active_rentals = user_slice_rentals.len(); let total_monthly_cost: rust_decimal::Decimal = user_slice_rentals.iter() .map(|rental| rental.slice_allocation.monthly_cost) .sum(); // Count deployment types let mut vm_count = 0; let k8s_count = 0; for _rental in &user_slice_rentals { // For now, we'll assume all rentals are VM deployments // In a real implementation, this would be stored in the rental metadata vm_count += 1; } ctx.insert("slice_rental_stats", &serde_json::json!({ "total_active_rentals": total_active_rentals, "total_monthly_cost": total_monthly_cost, "vm_deployments": vm_count, "kubernetes_deployments": k8s_count })); } } render_template(&tmpl, "dashboard/user.html", &ctx) } /// Renders the resource provider section of the dashboard pub async fn resource_provider_section(tmpl: web::Data, session: Session) -> Result { let mut ctx = crate::models::builders::ContextBuilder::new() .active_page("dashboard") .active_section("resource_provider") .gitea_enabled(get_app_config().is_gitea_enabled()) .build(); // Check if user is logged in if session.get::("user").unwrap_or(None).is_none() { // User is not logged in, show welcome page with login/register options return render_template(&tmpl, "dashboard/welcome.html", &ctx); } // RESOURCE PROVIDER FIX: Use persistent data only, no mock data for resource provider dashboard if let Ok(Some(user_email)) = session.get::("user_email") { ctx.insert("user_email", &user_email); // Initialize resource provider service with slice calculator if let Ok(resource_provider_service) = crate::services::resource_provider::ResourceProviderService::builder().build() { // Repair node-group data consistency when resource provider dashboard loads if let Err(_e) = resource_provider_service.repair_node_group_consistency(&user_email) { } // Repair missing marketplace SLA data for existing nodes if let Err(_e) = resource_provider_service.repair_missing_marketplace_sla( &user_email, 99.8, // default uptime 100, // default bandwidth rust_decimal::Decimal::from_str("0.5").unwrap_or_default() // default price ) { } // Get resource provider nodes with updated slice calculations let resource_provider_nodes = resource_provider_service.get_resource_provider_nodes(&user_email); // Calculate resource_provider statistics from nodes let total_nodes = resource_provider_nodes.len() as u32; let mut online_nodes = 0u32; let mut total_base_slices = 0u32; let mut allocated_base_slices = 0u32; let mut total_monthly_earnings = rust_decimal::Decimal::ZERO; let mut average_uptime = 0.0f32; for node in &resource_provider_nodes { if matches!(node.status, crate::models::user::NodeStatus::Online) { online_nodes += 1; } total_base_slices += node.total_base_slices as u32; allocated_base_slices += node.allocated_base_slices as u32; total_monthly_earnings += node.earnings_today_usd * rust_decimal::Decimal::from(30); // Estimate monthly average_uptime += node.uptime_percentage; } if total_nodes > 0 { average_uptime /= total_nodes as f32; } // Create resource_provider statistics for the dashboard let resource_provider_stats = serde_json::json!({ "total_nodes": total_nodes, "online_nodes": online_nodes, "total_base_slices": total_base_slices, "allocated_base_slices": allocated_base_slices, "available_base_slices": total_base_slices - allocated_base_slices, "average_uptime": average_uptime, "monthly_earnings": total_monthly_earnings, "slice_utilization_percentage": if total_base_slices > 0 { (allocated_base_slices as f32 / total_base_slices as f32) * 100.0 } else { 0.0 } }); ctx.insert("resource_provider_stats", &resource_provider_stats); ctx.insert("resource_provider_nodes", &resource_provider_nodes); } // Load user data from session (without mock data override) if let Ok(Some(user_json)) = session.get::("user") { if let Ok(mut user) = serde_json::from_str::(&user_json) { // Apply session data if available if let Some(session_data) = SessionManager::load_user_session_data(&session) { SessionManager::apply_session_to_user(&mut user, &session_data); } // Apply persistent data (profile info) if let Some(persistent_data) = UserPersistence::load_user_data(&user_email) { if let Some(name) = persistent_data.name { user.name = name; } if let Some(country) = persistent_data.country { user.country = Some(country); } if let Some(timezone) = persistent_data.timezone { user.timezone = Some(timezone); } ctx.insert("wallet_balance", &persistent_data.wallet_balance_usd); ctx.insert("resource_provider_earnings", &persistent_data.resource_provider_earnings); } ctx.insert("user_json", &user_json); ctx.insert("user", &user); } } // Load slice rental service to get resource_provider slice rental statistics if let Ok(slice_rental_service) = crate::services::slice_rental::SliceRentalService::builder().build() { let resource_provider_slice_stats = slice_rental_service.get_resource_provider_slice_statistics(&user_email); ctx.insert("slice_rental_statistics", &resource_provider_slice_stats); // Release any expired rentals if let Err(_e) = slice_rental_service.release_expired_rentals(&user_email) { } } } render_template(&tmpl, "dashboard/resource_provider.html", &ctx) } /// Renders the application provider section of the dashboard pub async fn application_provider_section(tmpl: web::Data, session: Session) -> Result { let mut ctx = crate::models::builders::ContextBuilder::new() .active_page("dashboard") .build(); ctx.insert("active_section", "application_provider"); let config = get_app_config(); ctx.insert("gitea_enabled", &config.is_gitea_enabled()); // Add user to context if available if let Some(user) = Self::load_user_with_persistent_data(&session) { if let Ok(Some(user_json)) = session.get::("user") { ctx.insert("user_json", &user_json); } ctx.insert("user", &user); // Add user_email for messaging system if let Ok(Some(user_email)) = session.get::("user_email") { ctx.insert("user_email", &user_email); } // Build persistent app provider data for hydration and summary cards let email = user.email.clone(); // Load fresh persistent apps and adjust deployment counts across all users let mut fresh_apps = crate::services::user_persistence::UserPersistence::get_user_apps(&email); let cross_user_deployment_counts = Self::count_cross_user_deployments(&email); for app in &mut fresh_apps { if let Some(&count) = cross_user_deployment_counts.get(&app.id) { app.deployments = count; } } // Load fresh persistent deployments let fresh_deployments = crate::services::user_persistence::UserPersistence::get_user_application_deployments(&email); // Only count deployments for apps published by this user let user_published_app_ids: std::collections::HashSet = fresh_apps.iter().map(|a| a.id.clone()).collect(); let active_deployments = fresh_deployments .iter() .filter(|d| d.status == "Active" && user_published_app_ids.contains(&d.app_id)) .count() as i32; let monthly_revenue_usd = fresh_apps.iter().map(|a| { use std::str::FromStr; let decimal_val: rust_decimal::Decimal = a.monthly_revenue_usd; i32::from_str(&decimal_val.to_string()).unwrap_or(0) }).sum::(); let total_revenue_usd = monthly_revenue_usd * 12; // Simple estimate // Build deployment stats enriched with auto_healing let deployment_stats: Vec = fresh_deployments .iter() .filter(|d| user_published_app_ids.contains(&d.app_id)) .map(|d| { let auto_healing = d.auto_healing.unwrap_or_else(|| { fresh_apps .iter() .find(|app| app.id == d.app_id) .and_then(|app| app.auto_healing) .unwrap_or(false) }); crate::models::user::DeploymentStat { app_name: d.app_name.clone(), region: d.region.clone(), active_instances: d.instances, total_instances: d.instances, avg_response_time_ms: Some(100.0), // Default response time uptime_percentage: Some(99.5), // Default uptime status: d.status.clone(), instances: d.instances, resource_usage: Some(serde_json::to_string(&d.resource_usage).unwrap_or_default()), customer_name: Some(d.customer_name.clone()), deployed_date: { use chrono::DateTime; DateTime::parse_from_rfc3339(&d.deployed_date) .or_else(|_| DateTime::parse_from_str(&d.deployed_date, "%Y-%m-%d %H:%M:%S")) .map(|dt| dt.with_timezone(&chrono::Utc)) .ok() }, deployment_id: Some(d.id.clone()), last_deployment: { use chrono::DateTime; DateTime::parse_from_rfc3339(&d.deployed_date) .or_else(|_| DateTime::parse_from_str(&d.deployed_date, "%Y-%m-%d %H:%M:%S")) .map(|dt| dt.with_timezone(&chrono::Utc)) .ok() }, auto_healing: Some(auto_healing), } }) .collect(); let application_provider_data = crate::models::user::AppProviderData { published_apps: fresh_apps.len() as i32, total_deployments: fresh_apps.iter().map(|a| a.deployments).sum::(), active_deployments, monthly_revenue_usd, total_revenue_usd, apps: fresh_apps, deployment_stats, revenue_history: Vec::new(), }; ctx.insert("application_provider_data", &application_provider_data); } else { // Ensure template always has a defined structure even without a logged-in user let empty: crate::models::user::AppProviderData = crate::models::user::AppProviderData { published_apps: 0, total_deployments: 0, active_deployments: 0, monthly_revenue_usd: 0, total_revenue_usd: 0, apps: Vec::new(), deployment_stats: Vec::new(), revenue_history: Vec::new(), }; ctx.insert("application_provider_data", &empty); } render_template(&tmpl, "dashboard/application_provider.html", &ctx) } /// Renders the service provider section of the dashboard pub async fn service_provider_section(tmpl: web::Data, session: Session) -> Result { let mut ctx = crate::models::builders::ContextBuilder::new() .active_page("dashboard") .build(); ctx.insert("active_section", "service_provider"); let config = get_app_config(); ctx.insert("gitea_enabled", &config.is_gitea_enabled()); // Add user to context if available if let Some(user) = Self::load_user_with_persistent_data(&session) { if let Ok(Some(user_json)) = session.get::("user") { ctx.insert("user_json", &user_json); } ctx.insert("user", &user); // Add user_email for messaging system if let Ok(Some(user_email)) = session.get::("user_email") { ctx.insert("user_email", &user_email); } // Build persistent service provider data for hydration and summary cards let email = user.email.clone(); let fresh_services = crate::services::user_persistence::UserPersistence::get_user_services(&email); let fresh_requests = crate::services::user_persistence::UserPersistence::get_user_service_requests(&email); let service_provider_data = crate::models::user::ServiceProviderData { active_services: fresh_services.len() as i32, total_clients: fresh_services.iter().map(|s| s.clients).sum::(), monthly_revenue_usd: fresh_services .iter() .map(|s| s.price_per_hour_usd * s.total_hours.unwrap_or(0)) .sum::(), total_revenue_usd: fresh_services .iter() .map(|s| s.price_per_hour_usd * s.total_hours.unwrap_or(0) * 2) .sum::(), // Simple estimate service_rating: if fresh_services.is_empty() { 0.0 } else { fresh_services.iter().map(|s| s.rating).sum::() / fresh_services.len() as f32 }, services: fresh_services, client_requests: fresh_requests, availability: Some(true), // Default to available hourly_rate_range: Some("$50-$150".to_string()), // Default range last_payment_method: Some("Credit Card".to_string()), // Default payment method revenue_history: Vec::new(), }; ctx.insert("service_provider_data", &service_provider_data); } else { // Ensure template always has a defined structure even without a logged-in user let empty = serde_json::json!({ "active_services": 0, "total_clients": 0, "monthly_revenue_usd": 0, "total_revenue_usd": 0, "service_rating": 0.0, "services": [], "client_requests": [], "revenue_history": [] }); ctx.insert("service_provider_data", &empty); } render_template(&tmpl, "dashboard/service_provider.html", &ctx) } /// Renders the settings page pub async fn settings(tmpl: web::Data, session: Session) -> Result { let mut ctx = crate::models::builders::ContextBuilder::new() .active_page("dashboard") .build(); ctx.insert("active_section", "settings"); let config = get_app_config(); ctx.insert("gitea_enabled", &config.is_gitea_enabled()); // Check if user is logged in if session.get::("user").unwrap_or(None).is_none() { // User is not logged in, show welcome page with login/register options return render_template(&tmpl, "dashboard/welcome.html", &ctx); } // Add user to context if available if let Some(user) = Self::load_user_with_persistent_data(&session) { if let Ok(Some(user_json)) = session.get::("user") { ctx.insert("user_json", &user_json); } ctx.insert("user", &user); // Load user's currency preferences and SSH keys from persistent data if let Ok(Some(user_email)) = session.get::("user_email") { ctx.insert("user_email", &user_email); if let Some(user_data) = crate::services::user_persistence::UserPersistence::load_user_data(&user_email) { ctx.insert("user_display_currency", &user_data.display_currency.unwrap_or_else(|| "USD".to_string())); ctx.insert("user_quick_topup_amounts", &user_data.quick_topup_amounts.unwrap_or_else(|| vec![ rust_decimal_macros::dec!(10), rust_decimal_macros::dec!(25), rust_decimal_macros::dec!(50), rust_decimal_macros::dec!(100) ])); } else { // Default values if no persistent data found ctx.insert("user_display_currency", &"USD".to_string()); ctx.insert("user_quick_topup_amounts", &vec![ rust_decimal_macros::dec!(10), rust_decimal_macros::dec!(25), rust_decimal_macros::dec!(50), rust_decimal_macros::dec!(100) ]); } // Load SSH keys for the settings page if let Ok(ssh_service) = crate::services::ssh_key_service::SSHKeyService::builder().build() { let req_id = uuid::Uuid::new_v4().to_string(); let ssh_keys = ssh_service.get_user_ssh_keys_async(&user_email, Some(&req_id)).await; ctx.insert("ssh_keys", &ssh_keys); // Convert to JSON string for CSP-compliant hydration match serde_json::to_string(&ssh_keys) { Ok(ssh_keys_json) => { ctx.insert("ssh_keys_json", &ssh_keys_json); } Err(e) => { log::error!("Failed to serialize SSH keys for user {}: {}", user_email, e); ctx.insert("ssh_keys_json", "[]"); } } } else { log::error!("Failed to build SSH key service for settings page"); ctx.insert("ssh_keys", &Vec::::new()); ctx.insert("ssh_keys_json", "[]"); } } } render_template(&tmpl, "dashboard/settings.html", &ctx) } /// Renders the messages page pub async fn messages_page(tmpl: web::Data, session: Session) -> Result { let mut ctx = crate::models::builders::ContextBuilder::new() .active_page("dashboard") .build(); ctx.insert("active_section", "messages"); let config = get_app_config(); ctx.insert("gitea_enabled", &config.is_gitea_enabled()); // Check if user is logged in if session.get::("user").unwrap_or(None).is_none() { // User is not logged in, show welcome page with login/register options return render_template(&tmpl, "dashboard/welcome.html", &ctx); } // Add user to context if available if let Some(user) = Self::load_user_with_persistent_data(&session) { if let Ok(Some(user_json)) = session.get::("user") { ctx.insert("user_json", &user_json); } ctx.insert("user", &user); // Add user email for messaging system if let Ok(Some(user_email)) = session.get::("user_email") { ctx.insert("user_email", &user_email); } } render_template(&tmpl, "dashboard/messages.html", &ctx) } /// Renders the MC Credits pools page pub async fn pools(tmpl: web::Data, session: Session) -> Result { let mut ctx = crate::models::builders::ContextBuilder::new() .active_page("dashboard") .build(); ctx.insert("active_section", "pools"); let config = get_app_config(); ctx.insert("gitea_enabled", &config.is_gitea_enabled()); // Add user to context if available if let Some(user) = Self::load_user_with_persistent_data(&session) { if let Ok(Some(user_json)) = session.get::("user") { ctx.insert("user_json", &user_json); } ctx.insert("user", &user); // Add user_email for messaging system if let Ok(Some(user_email)) = session.get::("user_email") { ctx.insert("user_email", &user_email); } } render_template(&tmpl, "dashboard/pools.html", &ctx) } /// API endpoint to return resource_provider dashboard data as JSON pub async fn resource_provider_data_api(session: Session) -> Result { let user_email = session.get::("user_email").unwrap_or_default().unwrap_or_default(); // RESOURCE_PROVIDER FIX: Use resource_provider service to ensure data consistency let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(_e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Service initialization failed" })).build()); } }; // Repair data consistency before loading if let Err(_e) = resource_provider_service.repair_node_group_consistency(&user_email) { } // Load real resource_provider data from persistence using resource_provider service let nodes = resource_provider_service.get_resource_provider_nodes(&user_email); let earnings = resource_provider_service.get_resource_provider_earnings(&user_email); let stats = resource_provider_service.get_resource_provider_statistics(&user_email); // Always use persistent data - no fallback to mock data for resource_provider dashboard // If no data exists, return empty but valid resource_provider data structure if nodes.is_empty() { return Ok(ResponseBuilder::ok() .json(serde_json::json!({ "total_nodes": 0, "online_nodes": 0, "total_capacity": { "cpu_cores": 0, "memory_gb": 0, "storage_gb": 0, "network_gb": 0 }, "monthly_earnings": { "tft": 0, "usd": 0 }, "nodes": [], "earnings_history": [], "last_updated": chrono::Utc::now().to_rfc3339() })) .build()); } // Load slice products for this resource_provider let slice_products = crate::services::user_persistence::UserPersistence::get_slice_products(&user_email); let active_slices = slice_products.len() as i32; // Build comprehensive resource_provider data using statistics from resource_provider service let resource_provider_data = serde_json::json!({ "total_nodes": stats.total_nodes, "online_nodes": stats.online_nodes, "total_capacity": stats.total_capacity, "used_capacity": stats.used_capacity, "monthly_earnings": stats.monthly_earnings, "total_earnings": stats.monthly_earnings * 3, // Estimate "uptime_percentage": stats.uptime_percentage, "nodes": nodes, "earnings_history": earnings, "active_slices": active_slices, "slice_products": slice_products }); Ok(ResponseBuilder::ok() .json(resource_provider_data) .build()) } /// Enhanced resource_provider dashboard with node management pub async fn resource_provider_dashboard_enhanced(tmpl: web::Data, session: Session) -> Result { let resource_provider_service = crate::services::resource_provider::ResourceProviderService::builder() .auto_sync_enabled(true) .metrics_collection(true) .build() .map_err(|_e| { actix_web::error::ErrorInternalServerError("Service initialization failed") })?; let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); // Load resource_provider data using the service let nodes = resource_provider_service.get_resource_provider_nodes(&user_email); let earnings = resource_provider_service.get_resource_provider_earnings(&user_email); let stats = resource_provider_service.get_resource_provider_statistics(&user_email); let mut ctx = crate::models::builders::ContextBuilder::new() .active_page("dashboard") .active_section("resource_provider") .build(); ctx.insert("nodes", &nodes); ctx.insert("earnings", &earnings); ctx.insert("stats", &stats); // Add user to context if available if let Some(user) = Self::load_user_with_persistent_data(&session) { if let Ok(Some(user_json)) = session.get::("user") { ctx.insert("user_json", &user_json); } ctx.insert("user", &user); } render_template(&tmpl, "dashboard/resource_provider.html", &ctx) } /// API endpoint to add a new farm node using ResourceProviderService pub async fn add_farm_node_enhanced(session: Session, form: web::Json) -> Result { let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "error": "User not authenticated" })).build()); } // Initialize resource_provider service let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(_e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Service initialization failed" })).build()); } }; // Extract node data from form using builder pattern let node_data_json = form.into_inner(); // Extract slice formats array let slice_formats = node_data_json.get("slice_formats") .and_then(|v| v.as_array()) .map(|arr| { arr.iter() .filter_map(|v| v.as_str()) .map(|s| s.to_string()) .collect::>() }); // Extract rental configuration let slice_rental_enabled = node_data_json.get("slice_rental_enabled") .and_then(|v| v.as_bool()) .unwrap_or(true); // Always true by default let full_node_rental_enabled = node_data_json.get("full_node_rental_enabled") .and_then(|v| v.as_bool()) .unwrap_or(false); let full_node_price = if full_node_rental_enabled { node_data_json.get("full_node_price") .and_then(|v| v.as_f64()) .map(|p| Decimal::from_f64_retain(p).unwrap_or(Decimal::ZERO)) } else { None }; let minimum_rental_days = node_data_json.get("minimum_rental_days") .and_then(|v| v.as_u64()) .unwrap_or(30) as u32; // Build rental options using builder pattern let rental_options = if slice_rental_enabled || full_node_rental_enabled { let mut builder = crate::models::builders::NodeRentalOptionsBuilder::new() .slice_rental_enabled(slice_rental_enabled) .full_node_rental_enabled(full_node_rental_enabled) .minimum_rental_days(minimum_rental_days) .auto_renewal_enabled(false); // Add legacy monthly price as basic pricing if provided if let Some(price) = full_node_price { if let Ok(pricing) = crate::models::builders::FullNodePricingBuilder::new() .monthly(price) .auto_calculate(false) .build() { builder = builder.full_node_pricing(pricing); } } builder.build().ok() } else { None }; // Extract slice prices let slice_prices = node_data_json.get("slice_prices") .and_then(|v| v.as_object()) .map(|obj| { let mut prices = std::collections::HashMap::new(); if let Some(basic) = obj.get("basic").and_then(|v| v.as_f64()) { prices.insert("basic".to_string(), Decimal::from_f64_retain(basic).unwrap_or(Decimal::from(25))); } if let Some(standard) = obj.get("standard").and_then(|v| v.as_f64()) { prices.insert("standard".to_string(), Decimal::from_f64_retain(standard).unwrap_or(Decimal::from(50))); } if let Some(performance) = obj.get("performance").and_then(|v| v.as_f64()) { prices.insert("performance".to_string(), Decimal::from_f64_retain(performance).unwrap_or(Decimal::from(100))); } prices }); // Build NodeCreationData using builder pattern let mut node_data_builder = crate::models::builders::NodeCreationDataBuilder::new() .name(node_data_json.get("name") .and_then(|v| v.as_str()) .unwrap_or("New Node")) .location(node_data_json.get("location") .and_then(|v| v.as_str()) .unwrap_or("Unknown")) .cpu_cores(node_data_json.get("cpu_cores") .and_then(|v| v.as_i64()) .unwrap_or(4) as i32) .memory_gb(node_data_json.get("memory_gb") .and_then(|v| v.as_i64()) .unwrap_or(8) as i32) .storage_gb(node_data_json.get("storage_gb") .and_then(|v| v.as_i64()) .unwrap_or(100) as i32) .bandwidth_mbps(node_data_json.get("bandwidth_mbps") .and_then(|v| v.as_i64()) .unwrap_or(100) as i32) .node_type("MyceliumNode"); // Always MyceliumNode - resource providers register MyceliumNodes to Mycelium Grid // Add optional fields if let Some(region) = node_data_json.get("region").and_then(|v| v.as_str()) { node_data_builder = node_data_builder.region(region); } if let Some(formats) = slice_formats { node_data_builder = node_data_builder.slice_formats(formats); } if let Some(options) = rental_options { node_data_builder = node_data_builder.rental_options(options); } if let Some(prices) = slice_prices { node_data_builder = node_data_builder.slice_prices(prices); } let node_data = node_data_builder.build().map_err(|e| { actix_web::error::ErrorBadRequest(format!("Invalid node data: {}", e)) })?; // Add node using resource_provider service match resource_provider_service.add_node(&user_email, node_data) { Ok(node) => { // Add activity record let activity = crate::models::builders::UserActivityBuilder::new() .activity_type(crate::models::user::ActivityType::NodeAdded) .description(format!("Added new farm node: {}", node.name)) .category("Farming".to_string()) .importance(crate::models::user::ActivityImportance::High) .build() .unwrap(); let _ = UserPersistence::add_user_activity(&user_email, activity); Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": "Farm node added successfully", "node": node })).build()) }, Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to add farm node", "details": e })).build()) } } } /// API endpoint to update node status pub async fn update_node_status(session: Session, path: web::Path, form: web::Json) -> Result { let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "error": "User not authenticated" })).build()); } let node_id = path.into_inner(); let status_str = form.get("status") .and_then(|v| v.as_str()) .unwrap_or("Offline"); let status = match status_str { "Online" => crate::models::user::NodeStatus::Online, "Offline" => crate::models::user::NodeStatus::Offline, "Maintenance" => crate::models::user::NodeStatus::Maintenance, "Error" => crate::models::user::NodeStatus::Offline, "Standby" => crate::models::user::NodeStatus::Standby, _ => crate::models::user::NodeStatus::Offline, }; let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Service initialization failed" })).build()); } }; match resource_provider_service.update_node_status(&user_email, &node_id, status) { Ok(()) => { Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": "Node status updated successfully" })).build()) }, Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to update node status", "details": e })).build()) } } } /// API endpoint to get node details by ID pub async fn get_node_details(session: Session, path: web::Path) -> Result { let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "error": "User not authenticated" })).build()); } let node_id = path.into_inner(); let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Service initialization failed" })).build()); } }; match resource_provider_service.get_node_by_id(&user_email, &node_id) { Some(mut node) => { // MARKETPLACE SLA FIX: Override grid data with marketplace SLA values when available if let Some(ref sla) = node.marketplace_sla { // Override capacity bandwidth with SLA value // Use uptime_guarantee as a proxy for bandwidth (temporary fix) node.capacity.bandwidth_mbps = (sla.uptime_guarantee * 100.0) as i32; // Override uptime with SLA value node.uptime_percentage = sla.uptime_guarantee_percentage; // Override slice pricing with SLA value if let Some(ref mut pricing) = node.slice_pricing { if let Some(pricing_obj) = pricing.as_object_mut() { pricing_obj.insert("base_price_per_hour".to_string(), serde_json::Value::from(sla.base_slice_price.to_f64().unwrap_or(0.0))); } } } else { } Ok(ResponseBuilder::ok().json(node).build()) }, None => { Ok(ResponseBuilder::not_found().json(serde_json::json!({ "error": "Node not found" })).build()) } } } /// API endpoint to get slice statistics pub async fn get_slice_statistics(session: Session) -> Result { let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "error": "User not authenticated" })).build()); } let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Service initialization failed" })).build()); } }; let stats = resource_provider_service.get_resource_provider_statistics(&user_email); Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "statistics": { "total_base_slices": stats.total_base_slices, "allocated_base_slices": stats.allocated_base_slices, "available_base_slices": stats.total_base_slices - stats.allocated_base_slices, "slice_utilization_percentage": if stats.total_base_slices > 0 { (stats.allocated_base_slices as f32 / stats.total_base_slices as f32) * 100.0 } else { 0.0 } } })).build()) } /// API endpoint to update node configuration pub async fn update_node_comprehensive(session: Session, path: web::Path, form: web::Json) -> Result { let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "error": "User not authenticated" })).build()); } let node_id = path.into_inner(); let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Service initialization failed" })).build()); } }; match resource_provider_service.update_node(&user_email, &node_id, form.into_inner()) { Ok(()) => { // Add activity record let activity = crate::models::builders::UserActivityBuilder::new() .activity_type(crate::models::user::ActivityType::NodeUpdated) .description(format!("Updated node configuration: {}", node_id)) .category("Farming".to_string()) .importance(crate::models::user::ActivityImportance::Medium) .build() .unwrap(); let _ = UserPersistence::add_user_activity(&user_email, activity); Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": "Node updated successfully" })).build()) }, Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to update node", "details": e })).build()) } } } /// API endpoint to get default slice formats pub async fn get_default_slice_formats(session: Session) -> Result { let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "error": "User not authenticated" })).build()); } let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Service initialization failed" })).build()); } }; // Get default formats with user customizations applied let default_format_ids = vec!["basic", "standard", "performance"]; let mut customized_formats = Vec::new(); for format_id in default_format_ids { if let Some(format) = resource_provider_service.get_default_slice_format_with_customizations(&user_email, format_id) { customized_formats.push(format); } } Ok(ResponseBuilder::ok().json(customized_formats).build()) } /// API endpoint to get node groups pub async fn get_node_groups(session: Session) -> Result { let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "error": "User not authenticated" })).build()); } let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Service initialization failed" })).build()); } }; let groups = resource_provider_service.get_node_groups(&user_email); Ok(ResponseBuilder::ok().json(groups).build()) } /// API endpoint to create custom node group pub async fn create_custom_node_group(session: Session, form: web::Json) -> Result { let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "error": "User not authenticated" })).build()); } let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Service initialization failed" })).build()); } }; let form_data = form.into_inner(); let name = form_data.get("name") .and_then(|v| v.as_str()) .unwrap_or("Custom Group") .to_string(); let description = form_data.get("description") .and_then(|v| v.as_str()) .map(|s| s.to_string()); match resource_provider_service.create_custom_node_group(&user_email, name, description, None) { Ok(group) => { Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": "Custom node group created successfully", "group": group })).build()) }, Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to create custom node group", "details": e })).build()) } } } /// API endpoint to assign node to group pub async fn assign_node_to_group(session: Session, form: web::Json) -> Result { let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "error": "User not authenticated" })).build()); } let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Service initialization failed" })).build()); } }; let form_data = form.into_inner(); let node_id = form_data.get("node_id") .and_then(|v| v.as_str()) .ok_or_else(|| actix_web::error::ErrorBadRequest("Node ID is required"))?; let group_id = form_data.get("group_id") .and_then(|v| v.as_str()) .map(|s| s.to_string()); match resource_provider_service.update_node_group_assignment(&user_email, node_id, group_id) { Ok(group_name) => { // RESOURCE_PROVIDER FIX: Repair consistency after group assignment change if let Err(e) = resource_provider_service.repair_node_group_consistency(&user_email) { } Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": "Node assigned to group successfully", "group_name": group_name, "refresh_required": true // Signal frontend to refresh node groups })).build()) }, Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to assign node to group", "details": e })).build()) } } } /// API endpoint to delete custom node group pub async fn delete_custom_node_group(session: Session, path: web::Path) -> Result { let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "error": "User not authenticated" })).build()); } let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Service initialization failed" })).build()); } }; let group_id = path.into_inner(); match resource_provider_service.delete_custom_node_group(&user_email, &group_id) { Ok(()) => { Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": "Custom node group deleted successfully" })).build()) }, Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to delete custom node group", "details": e })).build()) } } } /// Get details for a specific default slice format pub async fn get_default_slice_details(session: Session, path: web::Path) -> Result { let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "error": "User not authenticated" })).build()); } let format_id = path.into_inner(); let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Service initialization failed" })).build()); } }; match resource_provider_service.get_default_slice_format_with_customizations(&user_email, &format_id) { Some(format) => { Ok(ResponseBuilder::ok().json(format).build()) } None => { Ok(ResponseBuilder::not_found().json(serde_json::json!({ "error": "Default slice format not found" })).build()) } } } /// Save default slice customizations pub async fn save_default_slice_customization(session: Session, path: web::Path, form: web::Json) -> Result { let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "error": "User not authenticated" })).build()); } let format_id = path.into_inner(); let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Service initialization failed" })).build()); } }; // Parse the customization data let name = form.get("name").and_then(|v| v.as_str()).unwrap_or("").to_string(); let description = form.get("description").and_then(|v| v.as_str()).unwrap_or("").to_string(); let cpu_cores = form.get("cpu_cores").and_then(|v| v.as_i64()).unwrap_or(2) as i32; let memory_gb = form.get("memory_gb").and_then(|v| v.as_i64()).unwrap_or(4) as i32; let storage_gb = form.get("storage_gb").and_then(|v| v.as_i64()).unwrap_or(100) as i32; let bandwidth_mbps = form.get("bandwidth_mbps").and_then(|v| v.as_i64()).unwrap_or(100) as i32; let price_per_hour = form.get("price_per_hour").and_then(|v| v.as_f64()).map(rust_decimal::Decimal::from_f64_retain).flatten().unwrap_or(rust_decimal::Decimal::from(10)); let customization = crate::services::resource_provider::DefaultSliceFormat { id: format_id.clone(), name, cpu_cores, memory_gb, storage_gb, bandwidth_mbps, description, price_per_hour, }; match resource_provider_service.save_default_slice_customization(&user_email, &format_id, customization) { Ok(_) => { Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": "Default slice customization saved successfully" })).build()) } Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to save customization", "details": e })).build()) } } } /// Get slice products for the authenticated user pub async fn get_slice_products(session: Session) -> Result { let user_email = session.get::("user_email").unwrap_or_default().unwrap_or_default(); let slice_products = crate::services::user_persistence::UserPersistence::get_slice_products(&user_email); Ok(ResponseBuilder::ok().json(slice_products).build()) } /// Create a new slice product pub async fn create_slice_product(session: Session, slice_data: web::Json) -> Result { let user_email = session.get::("user_email").unwrap_or_default().unwrap_or_default(); // Parse slice data from JSON let name = slice_data.get("name").and_then(|v| v.as_str()).unwrap_or("Custom Slice").to_string(); let cpu_cores = slice_data.get("cpu").and_then(|v| v.as_i64()).unwrap_or(2) as i32; let memory_gb = slice_data.get("ram").and_then(|v| v.as_i64()).unwrap_or(4) as i32; let storage_gb = slice_data.get("storage").and_then(|v| v.as_i64()).unwrap_or(50) as i32; let bandwidth_mbps = slice_data.get("bandwidth").and_then(|v| v.as_i64()).unwrap_or(100) as i32; let min_uptime_sla = slice_data.get("uptime").and_then(|v| v.as_f64()).unwrap_or(99.0) as f32; let public_ips = slice_data.get("public_ips").and_then(|v| v.as_i64()).unwrap_or(0) as i32; // Use the actual price from user input instead of hardcoded value let price_per_hour = slice_data.get("price_hour") .and_then(|v| v.as_f64()) .map(|p| rust_decimal::Decimal::from_f64_retain(p).unwrap_or(rust_decimal::Decimal::new(50, 2))) .unwrap_or(rust_decimal::Decimal::new(50, 2)); // Fallback to 0.50 MC/hour only if no price provided // Load user data to get resource_provider name let user = Self::load_user_with_persistent_data(&session); let resource_provider_name = user.as_ref().map(|u| u.name.clone()).unwrap_or_else(|| "Unknown ResourceProvider".to_string()); // Create slice configuration with pricing let slice_pricing = crate::models::product::SlicePricing::from_hourly( price_per_hour, 5.0, // 5% daily discount 15.0, // 15% monthly discount 25.0, // 25% yearly discount ); let slice_config = crate::models::product::SliceConfiguration { cpu_cores, memory_gb, storage_gb, bandwidth_mbps, min_uptime_sla, public_ips, node_id: None, slice_type: crate::models::product::SliceType::Basic, pricing: slice_pricing, }; // Create slice product let slice_product = crate::models::product::Product::create_slice_product( user_email.clone(), resource_provider_name, name, slice_config, price_per_hour, ); match crate::services::user_persistence::UserPersistence::save_slice_product(&user_email, slice_product.clone()) { Ok(_) => { ResponseBuilder::ok().status(201).json(slice_product).build() } Err(e) => { ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to save slice product", "details": e })).build() } } } /// Delete slice product pub async fn delete_slice_product(session: Session, path: web::Path) -> Result { let user_email = session.get::("user_email").unwrap_or_default().unwrap_or_default(); let product_id = path.into_inner(); match crate::services::user_persistence::UserPersistence::delete_slice_product(&user_email, &product_id) { Ok(_) => { Ok(ResponseBuilder::ok().json(serde_json::json!({ "message": "Slice product deleted successfully" })).build()) } Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to delete slice product", "details": e })).build()) } } } /// Get slice configuration details pub async fn get_slice_details(session: Session, path: web::Path) -> Result { let slice_id = path.into_inner(); let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({"error": "Not authenticated"})).build()), }; // Load user's slice products let slice_products = crate::services::user_persistence::UserPersistence::get_user_slice_products(&user_email); if let Some(slice_product) = slice_products.iter().find(|p| p.id == slice_id) { // Extract slice configuration from product attributes let slice_config = slice_product.get_slice_configuration(); let response_data = serde_json::json!({ "id": slice_product.id, "name": slice_product.name, "description": slice_product.description, "base_price": slice_product.base_price, "base_currency": slice_product.base_currency, "slice_configuration": slice_config, "provider_name": slice_product.provider_name, "availability": slice_product.availability, "metadata": slice_product.metadata, "created_at": slice_product.created_at, "updated_at": slice_product.updated_at }); Ok(ResponseBuilder::ok().json(response_data).build()) } else { Ok(ResponseBuilder::not_found().json(serde_json::json!({"error": "Slice configuration not found"})).build()) } } /// Update slice configuration pub async fn update_slice_configuration(session: Session, path: web::Path, form: web::Json) -> Result { let slice_id = path.into_inner(); let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({"error": "Not authenticated"})).build()), }; let update_data = form.into_inner(); // Load user's slice products let mut slice_products = crate::services::user_persistence::UserPersistence::get_user_slice_products(&user_email); if let Some(slice_product) = slice_products.iter_mut().find(|p| p.id == slice_id) { // Update slice configuration if let Some(name) = update_data.get("name").and_then(|v| v.as_str()) { slice_product.name = name.to_string(); } if let Some(description) = update_data.get("description").and_then(|v| v.as_str()) { slice_product.description = description.to_string(); } // Update pricing information if let Some(pricing) = update_data.get("pricing") { if let Some(hourly) = pricing.get("hourly").and_then(|v| v.as_f64()) { slice_product.base_price = rust_decimal::Decimal::try_from(hourly).unwrap_or(slice_product.base_price); } } // Update slice configuration attributes if let Some(config) = update_data.get("slice_configuration") { // Extract pricing information from config let pricing = if let Some(pricing_data) = config.get("pricing") { crate::models::product::SlicePricing { hourly: pricing_data.get("hourly") .and_then(|v| v.as_str()) .and_then(|s| rust_decimal::Decimal::from_str(s).ok()) .unwrap_or_default(), daily: pricing_data.get("daily") .and_then(|v| v.as_str()) .and_then(|s| rust_decimal::Decimal::from_str(s).ok()) .unwrap_or_default(), monthly: pricing_data.get("monthly") .and_then(|v| v.as_str()) .and_then(|s| rust_decimal::Decimal::from_str(s).ok()) .unwrap_or_default(), yearly: pricing_data.get("yearly") .and_then(|v| v.as_str()) .and_then(|s| rust_decimal::Decimal::from_str(s).ok()) .unwrap_or_default(), } } else { crate::models::product::SlicePricing::default() }; let slice_config = crate::models::product::SliceConfiguration { cpu_cores: config.get("cpu_cores").and_then(|v| v.as_i64()).unwrap_or(2) as i32, memory_gb: config.get("memory_gb").and_then(|v| v.as_i64()).unwrap_or(4) as i32, storage_gb: config.get("storage_gb").and_then(|v| v.as_i64()).unwrap_or(100) as i32, bandwidth_mbps: config.get("bandwidth_mbps").and_then(|v| v.as_i64()).unwrap_or(100) as i32, min_uptime_sla: config.get("min_uptime_sla").and_then(|v| v.as_f64()).unwrap_or(99.0) as f32, public_ips: config.get("public_ips").and_then(|v| v.as_i64()).unwrap_or(0) as i32, node_id: None, slice_type: crate::models::product::SliceType::Basic, pricing, }; // Update the slice configuration attribute slice_product.add_attribute( "slice_configuration".to_string(), serde_json::to_value(&slice_config).unwrap_or_default(), crate::models::product::AttributeType::SliceConfiguration, ); } slice_product.updated_at = chrono::Utc::now(); // Clone the updated slice for response before saving let updated_slice = slice_product.clone(); // Save updated slice products match crate::services::user_persistence::UserPersistence::save_user_slice_products(&user_email, &slice_products) { Ok(_) => { Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": "Slice configuration updated successfully", "slice": updated_slice })).build()) } Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({"error": "Failed to update slice configuration"})).build()) } } } else { Ok(ResponseBuilder::not_found().json(serde_json::json!({"error": "Slice configuration not found"})).build()) } } /// Enhanced user data API with real persistent data pub async fn user_dashboard_data_api(session: Session) -> Result { // Get user email from session let user_email = match session.get::("user_email")? { Some(email) => email, None => return ResponseBuilder::unauthorized().json(serde_json::json!({ "error": "User not authenticated" })).build() }; // Load user data using UserService let user_service = UserService::builder().build().map_err(|e| { actix_web::error::ErrorInternalServerError("Failed to create user service") })?; // Gather comprehensive dashboard data let applications = user_service.get_user_applications(&user_email); let services = user_service.get_user_published_services(&user_email); let compute_resources = user_service.get_user_compute_resources(&user_email); let recent_activities = user_service.get_user_activities(&user_email, Some(10)); let user_metrics = user_service.calculate_user_metrics(&user_email); // Get slice rentals from slice rental service let mut slice_rentals = Vec::new(); if let Ok(slice_rental_service) = crate::services::slice_rental::SliceRentalService::builder().build() { slice_rentals = slice_rental_service.get_user_slice_rentals(&user_email); } // Build comprehensive dashboard data let dashboard_data = serde_json::json!({ "applications": applications, "services": services, "compute_resources": compute_resources, "slice_rentals": slice_rentals.iter().map(|rental| { serde_json::json!({ "id": rental.rental_id, "slice_id": rental.slice_combination_id, "slice_type": "Standard", // Default type "deployment_type": "VM", // Default deployment type "cluster_config": null, "specs": format!("1 vCPU, 4GB RAM, 200GB Storage"), "location": "Global", "status": match rental.slice_allocation.status { crate::services::slice_calculator::AllocationStatus::Active => "Active", crate::services::slice_calculator::AllocationStatus::Expired => "Expired", crate::services::slice_calculator::AllocationStatus::Cancelled => "Cancelled", }, "monthly_cost": rental.slice_allocation.monthly_cost }) }).collect::>(), "recent_activities": recent_activities, "user_metrics": user_metrics, "usd_usage_trend": [10, 15, 12, 18, 22, 25], // Mock data "resource_utilization": { "cpu": 65, "memory": 72, "storage": 45, "network": 38 }, "user_activity": { "deployments": [2, 3, 1, 4, 2, 3], "resource_reservations": [1, 2, 1, 2, 3, 2] }, "deployment_distribution": { "regions": [ {"region": "US-East", "apps": 3, "nodes": 2, "slices": 4}, {"region": "EU-West", "apps": 2, "nodes": 1, "slices": 2}, {"region": "Asia-Pacific", "apps": 1, "nodes": 1, "slices": 1} ] } }); ResponseBuilder::ok().json(dashboard_data).build() } pub async fn manage_slice_rental( session: Session, path: web::Path, form: web::Json ) -> Result { let rental_id = path.into_inner(); // Get user email from session let user_email = match session.get::("user_email")? { Some(email) => email, None => return ResponseBuilder::unauthorized().json(serde_json::json!({ "error": "User not authenticated" })).build() }; let action = form.get("action").and_then(|v| v.as_str()).unwrap_or(""); match action { "terminate" => { // Simulate slice rental termination // In a real implementation, this would: // 1. Stop all deployments on the slice // 2. Release the slice allocation // 3. Update the rental status // 4. Process any refunds if applicable ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": format!("Slice rental {} has been terminated", rental_id) })).build() } "resize" => { // Simulate slice rental resizing ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": format!("Slice rental {} resize initiated", rental_id) })).build() } _ => { ResponseBuilder::bad_request().json(serde_json::json!({ "error": "Invalid action specified" })).build() } } } pub async fn user_data_api(session: Session) -> Result { // Build user service with configuration let user_service = match crate::services::user_service::UserService::builder() .activity_limit(50) .build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to initialize user service" })).build()); } }; // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, Ok(None) => { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "error": "User not authenticated" })).build()); } Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Session error" })).build()); } }; // Load real data using UserService let activities = user_service.get_user_activities(&user_email, Some(10)); let purchase_history = user_service.get_purchase_history(&user_email); let usage_statistics = user_service.get_usage_statistics(&user_email); let active_deployments = user_service.get_active_deployments(&user_email); let applications = user_service.get_user_applications(&user_email); let compute_resources = user_service.get_user_compute_resources(&user_email); let _metrics = user_service.calculate_user_metrics(&user_email); // Get user profile info let user_info = if let Some(persistent_data) = UserPersistence::load_user_data(&user_email) { crate::models::user::UserInfo { name: persistent_data.name.unwrap_or_else(|| "User".to_string()), email: user_email.clone(), member_since: "2024".to_string(), // TODO: Calculate from first transaction account_type: "Standard".to_string(), verification_status: "Verified".to_string(), } } else { crate::models::user::UserInfo { name: "User".to_string(), email: user_email.clone(), member_since: "2024".to_string(), account_type: "Standard".to_string(), verification_status: "Unverified".to_string(), } }; // Get purchased services for the user (not published services) let services = user_service.get_user_purchased_services(&user_email); // Build wallet summary let wallet_summary = if let Some(persistent_data) = UserPersistence::load_user_data(&user_email) { crate::models::user::WalletSummary { balance: persistent_data.wallet_balance_usd, currency: "USD".to_string(), recent_transactions: persistent_data.transactions.len() as i32, pending_transactions: 0, // TODO: Calculate pending transactions } } else { crate::models::user::WalletSummary { balance: rust_decimal::Decimal::ZERO, currency: "USD".to_string(), recent_transactions: 0, pending_transactions: 0, } }; // Generate recommendations based on user data let recommendations = vec![ crate::models::user::Recommendation { id: "rec1".to_string(), title: "Complete Your Profile".to_string(), description: "Add more information to your profile to get better recommendations".to_string(), action_url: "/dashboard/settings".to_string(), priority: "Medium".to_string(), category: "Profile".to_string(), }, crate::models::user::Recommendation { id: "rec2".to_string(), title: "Explore Marketplace".to_string(), description: "Discover new applications and services in our marketplace".to_string(), action_url: "/marketplace".to_string(), priority: "Low".to_string(), category: "Discovery".to_string(), }, ]; // Generate quick actions let quick_actions = vec![ crate::models::user::QuickAction { id: "action1".to_string(), label: "Add Funds".to_string(), description: "Add USD Credits to your wallet".to_string(), icon: "wallet".to_string(), url: "/dashboard/wallet".to_string(), category: "wallet".to_string(), importance: 1, last_updated: chrono::Utc::now(), title: "Add Funds".to_string(), action_url: "/dashboard/wallet".to_string(), enabled: true, }, crate::models::user::QuickAction { id: "action2".to_string(), label: "Deploy App".to_string(), description: "Deploy a new application".to_string(), icon: "deploy".to_string(), url: "/marketplace/applications".to_string(), category: "deployment".to_string(), importance: 2, last_updated: chrono::Utc::now(), title: "Deploy App".to_string(), action_url: "/marketplace/applications".to_string(), enabled: true, }, ]; // Build comprehensive dashboard data using existing builder pattern let dashboard_data = match crate::models::builders::UserDashboardDataBuilder::new() .user_info(user_info) .activities(activities.clone()) .statistics(usage_statistics) .services(services.clone()) .active_deployments(active_deployments.len() as i32) .wallet_summary(wallet_summary) .recommendations(recommendations) .quick_actions(quick_actions) .build() { Ok(data) => data, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to build dashboard data" })).build()); } }; // Create enhanced response with applications, services, and compute resources let mut response_data = serde_json::to_value(dashboard_data).unwrap(); response_data["applications"] = serde_json::to_value(&applications).unwrap(); response_data["services"] = serde_json::to_value(&services).unwrap(); response_data["compute_resources"] = serde_json::to_value(&compute_resources).unwrap(); Ok(ResponseBuilder::ok().json(response_data).build()) } /// Helper API endpoint to add a new farm node pub async fn add_farm_node(session: Session, form: web::Json) -> Result { let user_email = session.get::("user_email").unwrap_or_default().unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "error": "User not authenticated" })).build()); } // Extract node data from form let node_data = form.into_inner(); let node_id = uuid::Uuid::new_v4().to_string(); // Build new farm node let node = match crate::models::builders::FarmNodeBuilder::new() .id(node_id) .name(node_data.get("name").and_then(|v| v.as_str()).unwrap_or("New Node").to_string()) .location(node_data.get("location").and_then(|v| v.as_str()).unwrap_or("Unknown").to_string()) .status(crate::models::user::NodeStatus::Online) .capacity(crate::models::user::NodeCapacity { cpu_cores: node_data.get("cpu_cores").and_then(|v| v.as_i64()).unwrap_or(4) as i32, memory_gb: node_data.get("memory_gb").and_then(|v| v.as_i64()).unwrap_or(8) as i32, storage_gb: node_data.get("storage_gb").and_then(|v| v.as_i64()).unwrap_or(100) as i32, bandwidth_mbps: node_data.get("bandwidth_mbps").and_then(|v| v.as_i64()).unwrap_or(100) as i32, ssd_storage_gb: node_data.get("storage_gb").and_then(|v| v.as_i64()).unwrap_or(100) as i32, // Default to SSD hdd_storage_gb: 0, ram_gb: node_data.get("memory_gb").and_then(|v| v.as_i64()).unwrap_or(8) as i32, }) .uptime_percentage(99.5) .earnings_today_usd(rust_decimal::Decimal::ZERO) .health_score(100.0) .region(node_data.get("region").and_then(|v| v.as_str()).unwrap_or("Global").to_string()) .node_type(node_data.get("node_type").and_then(|v| v.as_str()).unwrap_or("MyceliumNode").to_string()) .build() { Ok(node) => node, Err(e) => { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "error": "Invalid node data", "details": e })).build()); } }; // Add node to persistence match UserPersistence::add_user_node(&user_email, node) { Ok(()) => { // Add activity record let activity = crate::models::builders::UserActivityBuilder::new() .activity_type(crate::models::user::ActivityType::NodeAdded) .description("Added new farm node".to_string()) .category("Farming".to_string()) .importance(crate::models::user::ActivityImportance::High) .build() .unwrap(); let _ = UserPersistence::add_user_activity(&user_email, activity); Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": "Farm node added successfully" })).build()) }, Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to add farm node", "details": e.to_string() })).build()) } } } /// API endpoint to validate grid nodes pub async fn validate_grid_nodes(session: Session, form: web::Json) -> Result { let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "error": "User not authenticated" })).build()); } // Parse node IDs from request let node_ids: Vec = match form.get("node_ids") { Some(serde_json::Value::Array(ids)) => { ids.iter() .filter_map(|id| id.as_u64().map(|n| n as u32)) .collect() } _ => { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "error": "Invalid node_ids format" })).build()); } }; if node_ids.is_empty() { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "error": "No node IDs provided" })).build()); } // Initialize grid service let grid_service = match crate::services::grid::GridService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to initialize grid service" })).build()); } }; // Initialize resource_provider service to check for existing nodes let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to initialize resource_provider service" })).build()); } }; // Get existing nodes to check for duplicates let existing_nodes = resource_provider_service.get_resource_provider_nodes(&user_email); // Validate each node and fetch data let mut validated_nodes = Vec::new(); let mut errors = Vec::new(); for node_id in &node_ids { // Check if node is already registered if existing_nodes.iter().any(|n| n.grid_node_id == Some(node_id.to_string())) { errors.push(serde_json::json!({ "node_id": *node_id, "error": format!("Node {} is already registered", *node_id) })); continue; } match grid_service.fetch_node_data(*node_id).await { Ok(node_data) => { validated_nodes.push(serde_json::json!({ "node_id": *node_id, "valid": true, "data": node_data })); } Err(e) => { errors.push(serde_json::json!({ "node_id": *node_id, "error": e })); } } } Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "validated_nodes": validated_nodes, "errors": errors, "total_requested": node_ids.len(), "total_valid": validated_nodes.len() })).build()) } /// API endpoint to add grid nodes pub async fn add_grid_nodes(session: Session, form: web::Json) -> Result { let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "error": "User not authenticated" })).build()); } // Structured logging: request context let req_id = Uuid::new_v4().to_string(); let start_total = Instant::now(); log::info!( target: "api.dashboard", "add_grid_nodes:start req_id={} email={}", req_id, user_email ); // Acquire per-user async lock to serialize modifications for this user let user_lock = UserPersistence::get_user_lock(&user_email); // Optional: log when waiting for the lock log::debug!(target: "concurrency", "add_grid_nodes:waiting_for_lock req_id={} email={}", req_id, user_email); let lock_wait_start = Instant::now(); let _lock_guard = user_lock.lock().await; let lock_wait_ms = lock_wait_start.elapsed().as_millis(); log::info!(target: "concurrency", "add_grid_nodes:lock_acquired req_id={} email={} wait_ms={}", req_id, user_email, lock_wait_ms); // Parse request data let node_ids: Vec = match form.get("node_ids") { Some(serde_json::Value::Array(ids)) => { ids.iter() .filter_map(|id| id.as_u64().map(|n| n as u32)) .collect() } _ => { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "error": "Invalid node_ids format" })).build()); } }; let group_config = form.get("group_config"); let slice_configuration = form.get("slice_configuration"); let node_group_id = form.get("node_group_id").and_then(|v| v.as_str()).map(|s| s.to_string()); let full_node_rental_config = form.get("full_node_rental_config"); let _multi_node_pricing = form.get("multi_node_pricing").and_then(|v| v.as_str()).unwrap_or("single"); let staking_configuration = form.get("staking_configuration"); // Extract slice formats and rental options from new configuration let slice_formats = if let Some(config) = slice_configuration { config.get("slice_formats") .and_then(|v| v.as_array()) .map(|arr| arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect::>()) .unwrap_or_default() } else { Vec::new() }; let slice_rental_enabled = slice_configuration .and_then(|config| config.get("slice_rental_enabled")) .and_then(|v| v.as_bool()) .unwrap_or(false); // Parse comprehensive full node rental configuration let (full_node_rental_enabled, full_node_pricing, individual_node_pricing, minimum_rental_days, pricing_mode) = if let Some(config) = full_node_rental_config { let enabled = config.get("full_node_rental_enabled").and_then(|v| v.as_bool()).unwrap_or(false); let min_days = config.get("minimum_rental_days").and_then(|v| v.as_u64()).unwrap_or(30) as u32; let pricing_mode = config.get("pricing_mode").and_then(|v| v.as_str()).unwrap_or("same_for_all"); if enabled && pricing_mode == "individual" { // Handle individual node pricing let individual_pricing = if let Some(individual_data) = config.get("individual_node_pricing") { if let Some(individual_obj) = individual_data.as_object() { let mut pricing_map = std::collections::HashMap::new(); for (node_key, pricing_data) in individual_obj { if let Some(pricing_obj) = pricing_data.as_object() { let hourly = pricing_obj.get("hourly") .and_then(|v| v.as_f64()) .map(|price| rust_decimal::Decimal::try_from(price).unwrap_or(rust_decimal::Decimal::ZERO)) .unwrap_or(rust_decimal::Decimal::ZERO); let daily = pricing_obj.get("daily") .and_then(|v| v.as_f64()) .map(|price| rust_decimal::Decimal::try_from(price).unwrap_or(rust_decimal::Decimal::ZERO)) .unwrap_or(rust_decimal::Decimal::ZERO); let monthly = pricing_obj.get("monthly") .and_then(|v| v.as_f64()) .map(|price| rust_decimal::Decimal::try_from(price).unwrap_or(rust_decimal::Decimal::ZERO)) .unwrap_or(rust_decimal::Decimal::ZERO); let yearly = pricing_obj.get("yearly") .and_then(|v| v.as_f64()) .map(|price| rust_decimal::Decimal::try_from(price).unwrap_or(rust_decimal::Decimal::ZERO)) .unwrap_or(rust_decimal::Decimal::ZERO); let auto_calculate = pricing_obj.get("auto_calculate") .and_then(|v| v.as_bool()) .unwrap_or(true); let daily_discount_percent = pricing_obj.get("daily_discount_percent") .and_then(|v| v.as_f64()) .unwrap_or(0.0) as f32; let monthly_discount_percent = pricing_obj.get("monthly_discount_percent") .and_then(|v| v.as_f64()) .unwrap_or(0.0) as f32; let yearly_discount_percent = pricing_obj.get("yearly_discount_percent") .and_then(|v| v.as_f64()) .unwrap_or(0.0) as f32; // Create pricing using builder pattern match crate::models::builders::FullNodePricingBuilder::new() .hourly(hourly) .daily(daily) .monthly(monthly) .yearly(yearly) .auto_calculate(auto_calculate) .daily_discount_percent(daily_discount_percent) .monthly_discount_percent(monthly_discount_percent) .yearly_discount_percent(yearly_discount_percent) .build() { Ok(pricing) => { pricing_map.insert(node_key.clone(), pricing); }, Err(e) => { } } } } Some(pricing_map) } else { None } } else { None }; (enabled, None, individual_pricing, min_days, pricing_mode.to_string()) } else { // Handle standard pricing (same for all nodes) let pricing = if enabled { if let Some(pricing_data) = config.get("full_node_pricing") { if let Some(pricing_obj) = pricing_data.as_object() { let hourly = pricing_obj.get("hourly") .and_then(|v| v.as_f64()) .map(|price| rust_decimal::Decimal::try_from(price).unwrap_or(rust_decimal::Decimal::ZERO)) .unwrap_or(rust_decimal::Decimal::ZERO); let daily = pricing_obj.get("daily") .and_then(|v| v.as_f64()) .map(|price| rust_decimal::Decimal::try_from(price).unwrap_or(rust_decimal::Decimal::ZERO)) .unwrap_or(rust_decimal::Decimal::ZERO); let monthly = pricing_obj.get("monthly") .and_then(|v| v.as_f64()) .map(|price| rust_decimal::Decimal::try_from(price).unwrap_or(rust_decimal::Decimal::ZERO)) .unwrap_or(rust_decimal::Decimal::ZERO); let yearly = pricing_obj.get("yearly") .and_then(|v| v.as_f64()) .map(|price| rust_decimal::Decimal::try_from(price).unwrap_or(rust_decimal::Decimal::ZERO)) .unwrap_or(rust_decimal::Decimal::ZERO); let auto_calculate = pricing_obj.get("auto_calculate") .and_then(|v| v.as_bool()) .unwrap_or(true); let daily_discount_percent = pricing_obj.get("daily_discount_percent") .and_then(|v| v.as_f64()) .unwrap_or(0.0) as f32; let monthly_discount_percent = pricing_obj.get("monthly_discount_percent") .and_then(|v| v.as_f64()) .unwrap_or(0.0) as f32; let yearly_discount_percent = pricing_obj.get("yearly_discount_percent") .and_then(|v| v.as_f64()) .unwrap_or(0.0) as f32; // Create pricing using builder pattern match crate::models::builders::FullNodePricingBuilder::new() .hourly(hourly) .daily(daily) .monthly(monthly) .yearly(yearly) .auto_calculate(auto_calculate) .daily_discount_percent(daily_discount_percent) .monthly_discount_percent(monthly_discount_percent) .yearly_discount_percent(yearly_discount_percent) .build() { Ok(pricing) => Some(pricing), Err(e) => { None } } } else { None } } else { None } } else { None }; (enabled, pricing, None, min_days, pricing_mode.to_string()) } } else { (false, None, None, 30, "same_for_all".to_string()) }; // Legacy support for old format let slice_format = form.get("slice_format").and_then(|v| v.as_str()).map(|s| s.to_string()); let slice_price = form.get("slice_price").and_then(|v| v.as_f64()).map(|p| Decimal::from_f64_retain(p).unwrap_or_default()); // Initialize resource_provider service let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to initialize resource_provider service" })).build()); } }; // Add nodes with comprehensive configuration log::info!( target: "api.dashboard", "add_grid_nodes:invoke_resource_provider_service req_id={} email={} node_count={} slice_enabled={} full_node_enabled={} pricing_mode={}", req_id, user_email, node_ids.len(), slice_rental_enabled, full_node_rental_enabled, pricing_mode ); let add_result = if !slice_formats.is_empty() || full_node_rental_enabled { // Create rental options if full node rental is enabled let rental_options = if full_node_rental_enabled { match crate::models::builders::NodeRentalOptionsBuilder::new() .slice_rental_enabled(slice_rental_enabled) .full_node_rental_enabled(full_node_rental_enabled) .full_node_pricing(full_node_pricing.unwrap_or_else(|| { crate::models::user::FullNodePricing { monthly_cost: rust_decimal::Decimal::new(50, 0), setup_fee: Some(rust_decimal::Decimal::new(10, 0)), deposit_required: Some(rust_decimal::Decimal::new(100, 0)), ..Default::default() } })) .minimum_rental_days(minimum_rental_days) .auto_renewal_enabled(false) .build() { Ok(options) => Some(options), Err(e) => { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "error": "Failed to create rental options", "details": e })).build()); } } } else { None }; // For multi-node scenarios, apply pricing configuration based on user choice if node_ids.len() > 1 && pricing_mode == "same_for_all" && rental_options.is_some() { } else if node_ids.len() > 1 && pricing_mode == "individual" && individual_node_pricing.is_some() { // Individual pricing will be handled in the resource_provider service } // Choose the appropriate method based on pricing mode if pricing_mode == "individual" && individual_node_pricing.is_some() { resource_provider_service.add_multiple_grid_nodes_with_individual_pricing( &user_email, node_ids.clone(), slice_formats, individual_node_pricing.unwrap() ).await } else { resource_provider_service.add_multiple_grid_nodes_with_comprehensive_config( &user_email, node_ids.clone(), slice_formats, rental_options ).await } } else { resource_provider_service.add_multiple_grid_nodes(&user_email, node_ids.clone()).await }; match add_result { Ok(added_nodes) => { let total_ms = start_total.elapsed().as_millis(); log::info!( target: "api.dashboard", "add_grid_nodes:success req_id={} email={} added_count={} total_ms={}", req_id, user_email, added_nodes.len(), total_ms ); // If node_group_id is provided, assign nodes to existing group if let Some(group_id) = node_group_id { for node in &added_nodes { if let Err(e) = resource_provider_service.assign_node_to_group(&user_email, &node.id, Some(group_id.clone())) { } else { } } } // If group configuration is provided, create group and add nodes else if let Some(group_data) = group_config { if let (Some(group_name), Some(group_description)) = ( group_data.get("name").and_then(|v| v.as_str()), group_data.get("description").and_then(|v| v.as_str()) ) { // Create node group match resource_provider_service.create_custom_node_group( &user_email, group_name.to_string(), Some(group_description.to_string()), Some(crate::models::user::NodeGroupConfig { group_name: group_name.to_string(), max_nodes: 10, // Default max nodes allocation_strategy: "balanced".to_string(), auto_scaling: false, preferred_slice_formats: vec![slice_format.clone().unwrap_or_else(|| "performance".to_string())], default_pricing: slice_price.map(|p| { serde_json::to_value(p).unwrap_or_default() }), resource_optimization: crate::models::user::ResourceOptimization::default(), }) ) { Ok(group) => { // Add nodes to group for node in &added_nodes { if let Err(e) = resource_provider_service.assign_node_to_group(&user_email, &node.id, Some(group.id.clone())) { } } } Err(e) => { } } } } // Handle staking configuration if provided if let Some(staking_config) = staking_configuration { if let Some(staking_enabled) = staking_config.get("staking_enabled").and_then(|v| v.as_bool()) { if staking_enabled { // Check if it's individual staking or same for all if let Some(individual_config) = staking_config.get("individual_staking") { // Handle individual staking per node if let Some(individual_obj) = individual_config.as_object() { for node in &added_nodes { let node_key = format!("grid_{}", node.grid_node_id.clone().unwrap_or_default()); if let Some(node_staking) = individual_obj.get(&node_key) { if let Some(staked_amount) = node_staking.get("staked_amount").and_then(|v| v.as_f64()) { let staking_period = node_staking.get("staking_period_months").and_then(|v| v.as_u64()).unwrap_or(12) as u32; let staking_options = match NodeStakingOptionsBuilder::new() .staking_enabled(true) .staked_amount(Decimal::from_str(&staked_amount.to_string()).unwrap_or(Decimal::ZERO)) .staking_period_months(staking_period) .early_withdrawal_allowed(true) .build() { Ok(options) => options, Err(e) => { continue; } }; if let Err(e) = resource_provider_service.stake_on_node(&user_email, &node.id, staking_options) { } else { } } } } } } else { // Handle same staking for all nodes if let Some(staked_amount) = staking_config.get("staked_amount").and_then(|v| v.as_f64()) { let staking_period = staking_config.get("staking_period_months").and_then(|v| v.as_u64()).unwrap_or(12) as u32; let staking_options = match NodeStakingOptionsBuilder::new() .staking_enabled(true) .staked_amount(Decimal::from_str(&staked_amount.to_string()).unwrap_or(Decimal::ZERO)) .staking_period_months(staking_period) .early_withdrawal_allowed(true) .build() { Ok(options) => options, Err(e) => { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "error": "Failed to create staking options", "details": e })).build()); } }; for node in &added_nodes { if let Err(e) = resource_provider_service.stake_on_node(&user_email, &node.id, staking_options.clone()) { } else { } } } } } } } Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "added_nodes": added_nodes, "node_ids": node_ids })).build()) } Err(_e) => { let total_ms = start_total.elapsed().as_millis(); log::error!( target: "api.dashboard", "add_grid_nodes:error req_id={} email={} err={} total_ms={}", req_id, user_email, _e, total_ms ); Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to add grid nodes", "details": _e.to_string() })).build()) } } } /// API endpoint to create node group pub async fn create_node_group(session: Session, form: web::Json) -> Result { let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "error": "User not authenticated" })).build()); } let name = match form.get("name").and_then(|v| v.as_str()) { Some(n) => n, None => { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "error": "Group name is required" })).build()); } }; let description = form.get("description").and_then(|v| v.as_str()).map(|s| s.to_string()); let slice_format = form.get("slice_format").and_then(|v| v.as_str()).map(|s| s.to_string()); let slice_price = form.get("slice_price").and_then(|v| v.as_f64()).map(|p| Decimal::from_f64_retain(p).unwrap_or_default()); let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(_e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to initialize resource_provider service" })).build()); } }; // Convert old API to new custom group creation let config = crate::models::user::NodeGroupConfig { group_name: name.to_string(), max_nodes: 10, // Default max nodes allocation_strategy: "balanced".to_string(), auto_scaling: false, preferred_slice_formats: vec![slice_format.unwrap_or_else(|| "performance".to_string())], default_pricing: slice_price.map(|p| { serde_json::to_value(p).unwrap_or_default() }), resource_optimization: crate::models::user::ResourceOptimization::default(), }; match resource_provider_service.create_custom_node_group( &user_email, name.to_string(), description, Some(config) ) { Ok(group) => { Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": "Node group created successfully", "group": group })).build()) } Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to create node group", "details": e })).build()) } } } /// API endpoint to get all node groups with statistics pub async fn get_node_groups_api(session: Session) -> Result { let user_email = match session.get::("user_email") { Ok(Some(email)) => email, Ok(None) => return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "User not logged in" })).build()), Err(_) => return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Session error" })).build()), }; let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": format!("Service initialization failed: {}", e) })).build()), }; // RESOURCE_PROVIDER FIX: Repair node-group data consistency before getting statistics if let Err(e) = resource_provider_service.repair_node_group_consistency(&user_email) { } let groups = resource_provider_service.get_node_groups(&user_email); let mut groups_with_stats = Vec::new(); for group in groups { match resource_provider_service.get_group_statistics(&user_email, &group.id) { Ok(stats) => { groups_with_stats.push(serde_json::json!({ "group": group, "stats": stats })); } Err(e) => { groups_with_stats.push(serde_json::json!({ "group": group, "stats": null })); } } } Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "groups": groups_with_stats })).build()) } /// API endpoint to return application provider dashboard data as JSON pub async fn application_provider_data_api(session: Session) -> Result { // Get user email for debugging let user_email = session.get::("user_email") .unwrap_or(None) .unwrap_or_else(|| "unknown".to_string()); // Load fresh persistent apps directly to ensure we get latest updates let mut fresh_apps = UserPersistence::get_user_apps(&user_email); // CROSS-USER DEPLOYMENT COUNT FIX: Count deployments across all users let cross_user_deployment_counts = Self::count_cross_user_deployments(&user_email); // Update app deployment counts with cross-user data for app in &mut fresh_apps { if let Some(&count) = cross_user_deployment_counts.get(&app.id) { app.deployments = count; } } let fresh_deployments = UserPersistence::get_user_application_deployments(&user_email); // Load user persistent data if let Some(user) = Self::load_user_with_persistent_data(&session) { let persistent_data = UserPersistence::load_user_data(&user.email).unwrap_or_default(); // Create app provider data from persistent data let total_deployments = fresh_apps.iter().map(|a| a.deployments).sum::(); let user_published_app_ids: std::collections::HashSet = fresh_apps.iter() .map(|app| app.id.clone()) .collect(); let active_deployments = fresh_deployments.iter() .filter(|d| d.status == "Active" && user_published_app_ids.contains(&d.app_id)) .count() as i32; let monthly_revenue_usd = fresh_apps.iter().map(|a| { use std::str::FromStr; let decimal_val: rust_decimal::Decimal = a.monthly_revenue_usd; i32::from_str(&decimal_val.to_string()).unwrap_or(0) }).sum::(); let deployment_stats: Vec = fresh_deployments.iter() .filter(|d| user_published_app_ids.contains(&d.app_id)) .map(|d| { // Get auto_healing from parent app since AppDeployment doesn't have this field let auto_healing = fresh_apps.iter() .find(|app| app.id == d.app_id) .and_then(|app| app.auto_healing) .unwrap_or(false); crate::models::user::DeploymentStat { app_name: d.app_name.clone(), region: d.region.clone(), active_instances: d.instances, total_instances: d.instances, avg_response_time_ms: Some(100.0), // Default response time uptime_percentage: Some(99.5), // Default uptime status: d.status.clone(), instances: d.instances, resource_usage: Some(serde_json::to_string(&d.resource_usage).unwrap_or_default()), customer_name: Some(d.customer_name.clone()), deployed_date: { use chrono::DateTime; DateTime::parse_from_rfc3339(&d.deployed_date) .or_else(|_| DateTime::parse_from_str(&d.deployed_date, "%Y-%m-%d %H:%M:%S")) .map(|dt| dt.with_timezone(&chrono::Utc)) .ok() }, deployment_id: Some(d.id.clone()), last_deployment: { use chrono::DateTime; DateTime::parse_from_rfc3339(&d.deployed_date) .or_else(|_| DateTime::parse_from_str(&d.deployed_date, "%Y-%m-%d %H:%M:%S")) .map(|dt| dt.with_timezone(&chrono::Utc)) .ok() }, auto_healing: d.auto_healing, } }).collect(); let application_provider_data = crate::models::user::AppProviderData { apps: fresh_apps.clone(), published_apps: fresh_apps.len() as i32, total_deployments, active_deployments, monthly_revenue_usd, total_revenue_usd: monthly_revenue_usd * 12, // Estimate annual deployment_stats, revenue_history: Vec::new(), }; return Ok(ResponseBuilder::ok().json(application_provider_data).build()); } else { } // Return data based on persistent apps even if no mock data if !fresh_apps.is_empty() { let total_deployments = fresh_apps.iter().map(|a| a.deployments).sum::(); let monthly_revenue = fresh_apps.iter().map(|a| a.monthly_revenue_usd.to_string().parse::().unwrap_or(0)).sum::(); return Ok(ResponseBuilder::ok().json(serde_json::json!({ "published_apps": fresh_apps.len(), "total_deployments": total_deployments, "active_deployments": fresh_deployments.iter().filter(|d| d.status == "Active").count(), "monthly_revenue_usd": monthly_revenue, "total_revenue_usd": monthly_revenue * 12, "apps": fresh_apps, "deployment_stats": fresh_deployments.iter().map(|d| { // Use auto_healing directly from deployment data, or fall back to parent app let auto_healing = d.auto_healing.unwrap_or_else(|| { fresh_apps.iter() .find(|app| app.id == d.app_id) .and_then(|app| app.auto_healing) .unwrap_or(false) }); serde_json::json!({ "app_name": d.app_name, "region": d.region, "instances": d.instances, "status": d.status, "resource_usage": d.resource_usage, "customer_name": d.customer_name, "deployed_date": d.deployed_date, "deployment_id": d.id, "auto_healing": auto_healing }) }).collect::>(), "revenue_history": [] })).build()); } // Return empty app provider data if no user or app provider data Ok(ResponseBuilder::ok().json(serde_json::json!({ "published_apps": 0, "total_deployments": 0, "active_deployments": 0, "monthly_revenue_usd": 0, "total_revenue_usd": 0, "apps": [], "deployment_stats": [], "revenue_history": [] })).build()) } /// API endpoint to return service provider dashboard data as JSON pub async fn service_provider_data_api(session: Session) -> Result { // Get user email for debugging let user_email = session.get::("user_email") .unwrap_or(None) .unwrap_or_else(|| "unknown".to_string()); // PHASE 1 FIX: Enhanced data loading with better error handling and consistency // Load fresh persistent services directly to ensure we get latest updates let fresh_services = UserPersistence::get_user_services(&user_email); for service in &fresh_services { } // Load fresh persistent service requests let fresh_requests = UserPersistence::get_user_service_requests(&user_email); // New users start with empty service requests - no automatic mock data for request in &fresh_requests { } // PHASE 1 FIX: Build service provider data directly from persistent storage // This ensures we always return the most up-to-date data let service_provider_data = crate::models::user::ServiceProviderData { active_services: fresh_services.len() as i32, total_clients: fresh_services.iter().map(|s| s.clients).sum::(), monthly_revenue_usd: fresh_services.iter().map(|s| s.price_per_hour_usd * s.total_hours.unwrap_or(0)).sum::(), total_revenue_usd: fresh_services.iter().map(|s| s.price_per_hour_usd * s.total_hours.unwrap_or(0) * 2).sum::(), // Estimate service_rating: if fresh_services.is_empty() { 0.0 } else { fresh_services.iter().map(|s| s.rating).sum::() / fresh_services.len() as f32 }, services: fresh_services, client_requests: fresh_requests, availability: Some(true), // Default to available hourly_rate_range: Some("$50-$150".to_string()), // Default range last_payment_method: Some("Credit Card".to_string()), // Default payment method revenue_history: Vec::new(), // Can be enhanced later }; Ok(ResponseBuilder::ok().json(service_provider_data).build()) } /// Update user profile information pub async fn update_profile( form: web::Form, session: Session, ) -> Result { // Get current user from session if let Some(mut user) = Self::load_user_with_persistent_data(&session) { // Update user name and profile fields user.name = form.name.clone(); user.country = Some(form.country.clone()); user.timezone = Some(form.timezone.clone()); // Save profile changes permanently using UserPersistence if let Err(e) = UserPersistence::update_user_profile( &user.email, Some(form.name.clone()), Some(form.country.clone()), Some(form.timezone.clone()) ) { return ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "message": "Failed to save profile changes" })).build(); } else { } // Update session with essential user data only (avoid serializing complex MockUserData) // Create a lightweight user object for session storage let mut session_user_builder = User::builder() .name(user.name.clone()) .email(user.email.clone()) .role(user.role.clone()); if let Some(id) = user.id { session_user_builder = session_user_builder.id(id); } if let Some(country) = &user.country { session_user_builder = session_user_builder.country(country.clone()); } if let Some(timezone) = &user.timezone { session_user_builder = session_user_builder.timezone(timezone.clone()); } let session_user = session_user_builder.build().unwrap(); // Update session with lightweight user data match serde_json::to_string(&session_user) { Ok(user_json) => { if let Err(e) = session.insert("user", user_json) { return ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "message": "Failed to update session" })).build(); } // Also store preferences separately for easy access if let Err(e) = session.insert("user_country", &form.country) { } if let Err(e) = session.insert("user_timezone", &form.timezone) { } }, Err(e) => { return ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "message": "Failed to serialize user data" })).build(); } } // Return success response with updated user data for immediate UI update let response = ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": "Profile updated successfully", "user": { "name": form.name, "country": form.country, "timezone": form.timezone } })).build(); return response; } let error_response = ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "message": "User not found" })).build(); error_response } /// Update user password pub async fn update_password( form: web::Form, session: Session, ) -> Result { // Get current user from session if let Some(user) = Self::load_user_with_persistent_data(&session) { // In a real app, we'd verify the current password // For mock data, we'll just validate the new password format // Validate password strength if form.new_password.len() < 12 { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } if form.new_password != form.confirm_password { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "message": "Passwords do not match" })).build()); } // Check password complexity let has_letter = form.new_password.chars().any(|c| c.is_alphabetic()); let has_number = form.new_password.chars().any(|c| c.is_numeric()); let has_special = form.new_password.chars().any(|c| !c.is_alphanumeric()); if !has_letter || !has_number || !has_special { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "message": "Password must contain letters, numbers, and special characters" })).build()); } // Hash the password before storing use bcrypt::{hash, DEFAULT_COST}; let password_hash = match hash(&form.new_password, DEFAULT_COST) { Ok(hash) => hash, Err(_) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "message": "Failed to process password" })).build()); } }; // Store password update persistently using UserPersistence if let Err(e) = UserPersistence::update_user_password(&user.email, password_hash.clone()) { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "message": "Failed to save password changes" })).build()); } // Also store in session for immediate use during current session let _ = session.insert("password_updated", true); let _ = session.insert("password_update_time", chrono::Utc::now().to_rfc3339()); let _ = session.insert("password_hash", &password_hash); let _ = session.insert("updated_password", &form.new_password); return Ok(ResponseBuilder::ok() .json(serde_json::json!({ "success": true, "message": "Password updated successfully" })).build()); } Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "message": "User not found" })).build()) } /// Update notification preferences pub async fn update_notifications( form: web::Form, session: Session, ) -> Result { use crate::services::user_persistence::UserPersistence; use crate::models::user::NotificationSettings; // Get current user from session if let Some(user) = Self::load_user_with_persistent_data(&session) { // Create notification settings from form data let notification_settings = NotificationSettings { email_enabled: form.email_security_alerts, push_enabled: form.dashboard_alerts, sms_enabled: false, // Not in form, use default slack_webhook: None, // Not in form, use default discord_webhook: None, // Not in form, use default enabled: form.email_newsletter, push: form.dashboard_updates, node_offline_alerts: form.email_system_alerts, maintenance_reminders: form.dashboard_updates, earnings_reports: form.email_billing_alerts, }; // Store notification preferences persistently using UserPersistence if let Err(e) = UserPersistence::update_notification_settings(&user.email, notification_settings) { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "message": "Failed to save notification settings" })).build()); } // Also store in session for immediate use during current session let _ = session.insert("email_security_alerts", form.email_security_alerts); let _ = session.insert("email_billing_alerts", form.email_billing_alerts); let _ = session.insert("email_system_alerts", form.email_system_alerts); let _ = session.insert("email_newsletter", form.email_newsletter); let _ = session.insert("dashboard_alerts", form.dashboard_alerts); let _ = session.insert("dashboard_updates", form.dashboard_updates); let _ = session.insert("notification_preferences_updated", chrono::Utc::now().to_rfc3339()); return Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": "Notification preferences updated successfully" })).build()); } Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "message": "User not found" })).build()) } /// Add MC Credits to user balance pub async fn add_tfp( form: web::Form, session: Session, ) -> Result { // Get current user from session if let Some(user) = Self::load_user_with_persistent_data(&session) { let user_email = user.email.clone(); let mut persistent_data = UserPersistence::load_user_data(&user_email).unwrap_or_default(); // Add USD Credits to balance let amount_to_add = Decimal::from(form.amount); persistent_data.wallet_balance_usd += amount_to_add; // Add transaction record let transaction = crate::models::user::Transaction { id: Uuid::new_v4().to_string(), user_id: user_email.clone(), transaction_type: crate::models::user::TransactionType::Purchase { product_id: "manual_addition".to_string() }, amount: amount_to_add, currency: Some("USD".to_string()), exchange_rate_usd: Some(rust_decimal::Decimal::ONE), amount_usd: Some(amount_to_add), description: Some(format!("Manual wallet top-up of ${}", form.amount)), reference_id: None, metadata: None, timestamp: chrono::Utc::now(), status: crate::models::user::TransactionStatus::Completed, }; persistent_data.transactions.push(transaction); let new_balance = persistent_data.wallet_balance_usd; // Save updated persistent data if let Err(_e) = UserPersistence::save_user_data(&persistent_data) { return Ok(ResponseBuilder::error() .json(serde_json::json!({ "success": false, "message": "Failed to save wallet update" })) .build()?); } return Ok(ResponseBuilder::ok() .json(serde_json::json!({ "success": true, "message": format!("Successfully added ${} USD Credits to your account", form.amount), "new_balance": new_balance.to_f64().unwrap_or(0.0) })) .build()?); } Ok(ResponseBuilder::bad_request() .json(serde_json::json!({ "success": false, "message": "User not found or invalid amount" })) .build()?) } /// Verify user password (pre-check for account deletion) pub async fn verify_password( form: web::Form, session: Session, ) -> Result { use crate::services::user_persistence::UserPersistence; // Do NOT log raw password log::info!( target: "account_deletion", "verify_password:received pwd_len={}", form.password.len() ); if form.password.trim().is_empty() { return Ok(ResponseBuilder::bad_request() .message("Enter your current password.") .build()); } if let Some(user) = Self::load_user_with_persistent_data(&session) { let persistent_data = UserPersistence::load_user_data(&user.email).unwrap_or_default(); if let Some(stored_hash) = &persistent_data.password_hash { match verify(&form.password, stored_hash) { Ok(true) => { log::info!(target: "account_deletion", "verify_password:success email={}", user.email); return Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": "Password verified." })).build()); } Ok(false) => { log::warn!(target: "account_deletion", "verify_password:incorrect email={}", user.email); return Ok(ResponseBuilder::bad_request() .message("The password you entered is incorrect. Please try again.") .build()); } Err(_) => { log::error!(target: "account_deletion", "verify_password:error email={}", user.email); return Ok(ResponseBuilder::internal_error() .message("Failed to verify password") .build()); } } } else { return Ok(ResponseBuilder::bad_request() .message("This account does not have a password set. Please set a password in Settings > Password before deleting your account.") .build()); } } Ok(ResponseBuilder::bad_request() .message("User not found in session") .build()) } /// Delete user account pub async fn delete_account( form: web::Form, session: Session, ) -> Result { use crate::services::user_persistence::UserPersistence; // Trace incoming request (do NOT log raw password) log::info!( target: "account_deletion", "delete_account:received confirmation={} pwd_len={}", form.confirmation, form.password.len() ); // Validate confirmation text if form.confirmation.to_uppercase() != "DELETE" { return Ok(ResponseBuilder::bad_request() .message("Invalid confirmation. Please type DELETE to confirm account deletion.") .build()); } // Get current user from session if let Some(user) = Self::load_user_with_persistent_data(&session) { log::info!( target: "account_deletion", "delete_account:user_loaded email={}", user.email ); // Load persistent data to access password hash let persistent_data = UserPersistence::load_user_data(&user.email).unwrap_or_default(); // Verify password if user has one set; otherwise block deletion until a password is set if let Some(stored_hash) = &persistent_data.password_hash { log::info!( target: "account_deletion", "delete_account:password_hash_present email={} hash_prefix={}", user.email, &stored_hash.chars().take(7).collect::() ); if form.password.trim().is_empty() { return Ok(ResponseBuilder::bad_request() .message("Enter your current password.") .build()); } match verify(&form.password, stored_hash) { Ok(true) => { // Password is correct, proceed log::info!( target: "account_deletion", "delete_account:password_verify=success email={}", user.email ); }, Ok(false) => { log::warn!( target: "account_deletion", "delete_account:password_verify=incorrect email={} pwd_len={}", user.email, form.password.len() ); return Ok(ResponseBuilder::bad_request() .message("The password you entered is incorrect. Please try again.") .build()); }, Err(_) => { log::error!( target: "account_deletion", "delete_account:password_verify=error email={}", user.email ); return Ok(ResponseBuilder::internal_error() .message("Failed to verify password") .build()); } } } else { log::warn!( target: "account_deletion", "delete_account:no_password_set email={}", user.email ); // For accounts without a password set (e.g., created via SSO), require setting a password first return Ok(ResponseBuilder::bad_request() .message("This account does not have a password set. Please set a password in Settings > Password before deleting your account.") .build()); } // Perform soft delete - mark account as deleted instead of removing data match UserPersistence::soft_delete_user_account(&user.email, Some("User requested account deletion".to_string())) { Ok(()) => { log::info!( target: "account_deletion", "delete_account:soft_delete_success email={}", user.email ); // Clear all session data to log out the user session.clear(); return Ok(ResponseBuilder::ok() .json(serde_json::json!({ "success": true, "message": "Account deleted successfully. All your data has been preserved for potential recovery.", "redirect": "/dashboard" })) .build()); }, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "message": format!("Failed to delete account: {}", e) })).build()); } } } Ok(ResponseBuilder::bad_request() .json(serde_json::json!({ "success": false, "message": "User not found in session" })) .build()) } /// Get billing history for the user pub async fn get_billing_history(session: Session) -> Result { // Get current user from session if let Some(user) = Self::load_user_with_persistent_data(&session) { // Create mock billing history let billing_history = vec![ serde_json::json!({ "id": "bill_001", "date": "2024-05-01", "description": "Monthly USD Credits Usage", "amount": 450, "status": "Paid", "invoice_url": "/invoices/bill_001.pdf" }), serde_json::json!({ "id": "bill_002", "date": "2024-04-01", "description": "Monthly USD Credits Usage", "amount": 380, "status": "Paid", "invoice_url": "/invoices/bill_002.pdf" }), serde_json::json!({ "id": "bill_003", "date": "2024-03-01", "description": "Monthly USD Credits Usage", "amount": 520, "status": "Paid", "invoice_url": "/invoices/bill_003.pdf" }), serde_json::json!({ "id": "bill_004", "date": "2024-02-01", "description": "Monthly USD Credits Usage", "amount": 290, "status": "Paid", "invoice_url": "/invoices/bill_004.pdf" }) ]; return Ok(ResponseBuilder::ok() .json(serde_json::json!({ "success": true, "billing_history": billing_history, "total_bills": billing_history.len(), "user_email": user.email })) .build()?); } Ok(ResponseBuilder::bad_request() .json(serde_json::json!({ "success": false, "message": "User not found" })) .build()?) } /// Get user's services pub async fn get_user_services(session: Session) -> Result { // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); } }; // PHASE 1 FIX: Load services directly from persistent storage for consistency let user_services = UserPersistence::get_user_services(&user_email); let response = serde_json::json!({ "success": true, "services": user_services }); Ok(ResponseBuilder::ok() .json(response) .build()?) } /// Convert JSON Value to Service struct fn json_to_service(json_value: &serde_json::Value) -> Option { // Handle missing required fields with defaults let id = json_value.get("id") .and_then(|v| v.as_str()) .map(|s| s.to_string()) .unwrap_or_else(|| { // Generate a unique UUID if missing Uuid::new_v4().to_string() }); let name = json_value.get("name") .and_then(|v| v.as_str()) .unwrap_or("Unnamed Service") .to_string(); let category = json_value.get("category") .and_then(|v| v.as_str()) .unwrap_or("General") .to_string(); let description = json_value.get("description") .and_then(|v| v.as_str()) .unwrap_or("No description provided") .to_string(); let price_per_hour = json_value.get("price_per_hour") .or_else(|| json_value.get("pricing")) .or_else(|| json_value.get("price_amount")) .or_else(|| json_value.get("hourly_rate")) .and_then(|p| p.as_i64()) .unwrap_or(0) as i32; let status = json_value.get("status") .and_then(|v| v.as_str()) .unwrap_or("Active") .to_string(); let clients = json_value.get("clients") .and_then(|c| c.as_i64()) .unwrap_or(0) as i32; let rating = json_value.get("rating") .and_then(|r| r.as_f64()) .unwrap_or(0.0) as f32; let total_hours = json_value.get("total_hours") .and_then(|h| h.as_i64()) .unwrap_or(0) as i32; Some(crate::models::user::Service { id, name, category, description, price_usd: rust_decimal::Decimal::new(price_per_hour as i64, 0), hourly_rate_usd: Some(rust_decimal::Decimal::new(price_per_hour as i64, 0)), availability: true, created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), price_per_hour_usd: price_per_hour, status, clients, rating, total_hours: Some(total_hours), }) } /// Create a new service pub async fn create_service( service_data: web::Json, session: Session, ) -> Result { // Convert JSON to Service struct let service = match Self::json_to_service(&service_data) { Some(service) => service, None => { let error_response = serde_json::json!({ "success": false, "message": "Invalid service data format" }); return Ok(ResponseBuilder::bad_request() .json(error_response) .build()?); } }; // Get user email from session for service persistence let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { let error_response = serde_json::json!({ "success": false, "message": "User not authenticated" }); return Ok(ResponseBuilder::unauthorized() .json(error_response) .build()?); } }; // Persist service to user's persistent data (this is the source of truth) if let Err(e) = UserPersistence::add_user_service(&user_email, service.clone()) { let error_response = serde_json::json!({ "success": false, "message": "Failed to persist service data" }); return Ok(ResponseBuilder::internal_error() .json(error_response) .build()?); } else { } // Verify we can load the user to ensure they exist if Self::load_user_with_persistent_data(&session).is_some() { // Service registration is now handled through persistent user data // Products are automatically aggregated by ProductService from user-owned data // Prepare and send response let response_json = serde_json::json!({ "success": true, "message": "Service created successfully", "service": { "id": service.id, "name": service.name, "category": service.category, "description": service.description, "price_per_hour": service.price_per_hour_usd, "status": service.status } }); return Ok(ResponseBuilder::ok() .json(response_json) .build()?); } let error_response = serde_json::json!({ "success": false, "message": "Failed to create service - user not found or not a service provider" }); Ok(ResponseBuilder::bad_request() .json(error_response) .build()?) } /// Update an existing service pub async fn update_service( path: web::Path, service_data: web::Json, session: Session, ) -> Result { let service_id = path.into_inner(); // Convert JSON to Service struct let updated_service = match Self::json_to_service(&service_data) { Some(mut service) => { // Ensure the service ID matches the path parameter service.id = service_id.clone(); service }, None => { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()?); } }; // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); } }; // Load user's persistent data if let Some(mut persistent_data) = UserPersistence::load_user_data(&user_email) { // Find and update the service in persistent storage if let Some(service) = persistent_data.services.iter_mut().find(|s| s.id == service_id) { *service = updated_service.clone(); // Save updated persistent data if let Err(e) = UserPersistence::save_user_data(&persistent_data) { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()?); } return Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()?); } } Ok(ResponseBuilder::not_found().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()?) } /// Delete a service pub async fn delete_service( path: web::Path, session: Session, ) -> Result { let service_id = path.into_inner(); // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); } }; // Remove service from persistent storage match UserPersistence::remove_user_service(&user_email, &service_id) { Ok(true) => { Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()?) }, Ok(false) => { Ok(ResponseBuilder::not_found().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()?) }, Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()?) } } } /// Get user service requests pub async fn get_user_service_requests(session: Session) -> Result { // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); } }; // Load service requests from persistent storage let user_requests = UserPersistence::get_user_service_requests(&user_email); // New users start with empty service requests - no automatic mock data let requests_to_return = user_requests; let response = serde_json::json!({ "success": true, "requests": requests_to_return }); Ok(ResponseBuilder::ok() .json(response) .build()?) } /// Update service request status pub async fn update_service_request( path: web::Path, request_data: web::Json, session: Session, ) -> Result { let request_id = path.into_inner(); // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "message": "User not authenticated" })).build()?); } }; // Extract new status from request data let new_status = request_data.get("status") .and_then(|s| s.as_str()) .unwrap_or("In Progress"); let should_remove = request_data.get("remove") .and_then(|r| r.as_bool()) .unwrap_or(false); // If declining and should remove, remove the request entirely if new_status == "Declined" && should_remove { match UserPersistence::remove_user_service_request(&user_email, &request_id) { Ok(true) => { log::info!( target: "dashboard_controller", "update_service_request:removed provider_email={} request_id={}", user_email, request_id ); return Ok(ResponseBuilder::ok().json(serde_json::json!({ "message": "Service request declined and removed successfully", "removed": true, "request_id": request_id })).build()?); }, Ok(false) => { return Ok(ResponseBuilder::not_found().json(serde_json::json!({ "message": "Service request not found", "request_id": request_id })).build()?); }, Err(e) => { log::error!( target: "dashboard_controller", "update_service_request:remove_error provider_email={} request_id={} error={}", user_email, request_id, e ); return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "message": "Failed to remove service request", "request_id": request_id })).build()?); } } } // Regular status update match UserPersistence::update_service_request_status(&user_email, &request_id, new_status) { Ok(true) => { // Load fresh data and find updated request for response and sync let updated_requests = UserPersistence::get_user_service_requests(&user_email); let updated_request = updated_requests .iter() .find(|r| r.id == request_id) .cloned(); // Attempt to sync buyer booking if we have client email let mut booking_sync = serde_json::json!({ "attempted": false, "updated": false }); if let Some(ref req) = updated_request { let client_email = &req.customer_email; let cd = req.completed_date.as_deref(); match UserPersistence::update_user_service_booking_fields( client_email, &request_id, Some(new_status), req.progress.map(|p| p as i32), Some(req.priority.as_str()), cd, ) { Ok(updated) => { booking_sync = serde_json::json!({ "attempted": true, "updated": updated, "buyer_email": client_email }); } Err(e) => { log::error!( target: "dashboard_controller", "booking_sync_error provider_email={} buyer_email={} request_id={} error={}", user_email, client_email, request_id, e ); booking_sync = serde_json::json!({ "attempted": true, "updated": false, "buyer_email": client_email, "error": "sync_failed" }); } } } log::info!( target: "dashboard_controller", "update_service_request:updated provider_email={} request_id={} status={} booking_sync_attempted={}", user_email, request_id, new_status, booking_sync.get("attempted").and_then(|v| v.as_bool()).unwrap_or(false) ); Ok(ResponseBuilder::ok().json(serde_json::json!({ "message": "Service request updated successfully", "updated_request": updated_request, "total_requests": updated_requests.len(), "booking_sync": booking_sync })).build()?) }, Ok(false) => { Ok(ResponseBuilder::not_found().json(serde_json::json!({ "message": "Service request not found", "request_id": request_id })).build()?) }, Err(e) => { log::error!( target: "dashboard_controller", "update_service_request:error provider_email={} request_id={} error={}", user_email, request_id, e ); Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "message": "Failed to update service request", "request_id": request_id })).build()?) } } } /// Update service request progress with enhanced data pub async fn update_service_request_progress( path: web::Path, progress_data: web::Json, session: Session, ) -> Result { let request_id = path.into_inner(); // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); } }; // Extract progress data let progress = progress_data.get("progress") .and_then(|p| p.as_i64()) .unwrap_or(0) as i32; let priority = progress_data.get("priority") .and_then(|p| p.as_str()) .unwrap_or("Medium"); let hours_worked = progress_data.get("hours_worked") .and_then(|h| h.as_f64()) .unwrap_or(0.0); let notes = progress_data.get("notes") .and_then(|n| n.as_str()) .unwrap_or(""); let notify_client = progress_data.get("notify_client") .and_then(|n| n.as_bool()) .unwrap_or(false); // Determine status based on progress let new_status = progress_data.get("status") .and_then(|s| s.as_str()) .unwrap_or(if progress >= 100 { "Completed" } else { "In Progress" }); // Update service request progress and status in persistent storage match UserPersistence::update_service_request_progress( &user_email, &request_id, progress, Some(priority), Some(hours_worked), if notes.is_empty() { None } else { Some(notes) }, new_status ) { Ok(true) => { // Reload updated request to return and to drive sync let updated_requests = UserPersistence::get_user_service_requests(&user_email); let updated_request = updated_requests .iter() .find(|r| r.id == request_id) .cloned(); // Attempt to sync buyer booking let mut booking_sync = serde_json::json!({ "attempted": false, "updated": false }); if let Some(ref req) = updated_request { let client_email = &req.customer_email; let cd = req.completed_date.as_deref(); match UserPersistence::update_user_service_booking_fields( client_email, &request_id, Some(new_status), req.progress.map(|p| p as i32), Some(req.priority.as_str()), cd, ) { Ok(updated) => { booking_sync = serde_json::json!({ "attempted": true, "updated": updated, "buyer_email": client_email }); } Err(e) => { log::error!( target: "dashboard_controller", "booking_sync_error provider_email={} buyer_email={} request_id={} error={}", user_email, client_email, request_id, e ); booking_sync = serde_json::json!({ "attempted": true, "updated": false, "buyer_email": client_email, "error": "sync_failed" }); } } } log::info!( target: "dashboard_controller", "update_service_request_progress:updated provider_email={} request_id={} status={} progress={} notify_client={} booking_sync_attempted={}", user_email, request_id, new_status, progress, notify_client, booking_sync.get("attempted").and_then(|v| v.as_bool()).unwrap_or(false) ); Ok(ResponseBuilder::ok().json(serde_json::json!({ "message": "Service request progress updated successfully", "updated_request": updated_request, "total_requests": updated_requests.len(), "booking_sync": booking_sync })).build()?) }, Ok(false) => { Ok(ResponseBuilder::not_found().json(serde_json::json!({ "message": "Service request not found", "request_id": request_id })).build()?) }, Err(e) => { log::error!( target: "dashboard_controller", "update_service_request_progress:error provider_email={} request_id={} error={}", user_email, request_id, e ); Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "message": "Failed to update service request progress", "request_id": request_id })).build()?) } } } /// Get user availability settings pub async fn get_user_availability(session: Session) -> Result { // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); } }; // Load availability from persistence let availability = UserPersistence::get_user_availability(&user_email) .unwrap_or_else(|| { // Default availability settings crate::services::user_persistence::AvailabilitySettings { available: true, weekly_hours: 20, updated_at: chrono::Utc::now().to_rfc3339(), } }); ResponseBuilder::ok().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build() } /// Update user availability settings pub async fn update_user_availability( availability_data: web::Json, session: Session, ) -> Result { // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); } }; // Parse availability data let available = availability_data.get("available") .and_then(|v| v.as_bool()) .unwrap_or(true); let weekly_hours = availability_data.get("weekly_hours") .and_then(|v| v.as_i64()) .unwrap_or(20) as i32; let availability_settings = crate::services::user_persistence::AvailabilitySettings { available, weekly_hours, updated_at: chrono::Utc::now().to_rfc3339(), }; // Save to persistence if let Err(e) = UserPersistence::update_user_availability(&user_email, availability_settings) { return ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); } ResponseBuilder::ok().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build() } /// PHASE 3 FIX: Create SLA with full implementation pub async fn create_sla( sla_data: web::Json, session: Session, ) -> Result { // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); } }; // Extract SLA data from JSON let sla_id = sla_data.get("id") .and_then(|v| v.as_str()) .map(|s| s.to_string()) .unwrap_or_else(|| format!("sla_{}", Utc::now().timestamp())); let name = sla_data.get("name") .and_then(|v| v.as_str()) .unwrap_or("Unnamed SLA") .to_string(); let description = sla_data.get("description") .and_then(|v| v.as_str()) .unwrap_or("No description provided") .to_string(); let service_id = sla_data.get("service_id") .and_then(|v| v.as_str()) .map(|s| s.to_string()); let response_time_hours = sla_data.get("response_time_hours") .and_then(|v| v.as_i64()) .unwrap_or(24) as i32; let resolution_time_hours = sla_data.get("resolution_time_hours") .and_then(|v| v.as_i64()) .unwrap_or(72) as i32; let availability_percentage = sla_data.get("availability_percentage") .and_then(|v| v.as_f64()) .unwrap_or(99.9) as f32; let support_hours = sla_data.get("support_hours") .and_then(|v| v.as_str()) .unwrap_or("Business Hours") .to_string(); let escalation_procedure = sla_data.get("escalation_procedure") .and_then(|v| v.as_str()) .unwrap_or("Standard escalation procedure") .to_string(); // Create SLA struct let sla = crate::services::user_persistence::ServiceLevelAgreement { id: sla_id.clone(), name: name.clone(), description, service_id, response_time_hours, resolution_time_hours, availability_percentage, support_hours, escalation_procedure, penalties: Vec::new(), // Can be enhanced later created_at: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(), status: "Active".to_string(), }; // Save SLA to persistent storage match UserPersistence::add_user_sla(&user_email, sla.clone()) { Ok(()) => { ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": "SLA created successfully", "sla": sla })).build() } Err(e) => { ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "message": "Failed to persist SLA", "error": e.to_string() })).build() } } } /// PHASE 3 FIX: Get user's SLAs pub async fn get_user_slas(session: Session) -> Result { // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); } }; // Load SLAs from persistent storage let user_slas = UserPersistence::get_user_slas(&user_email); ResponseBuilder::ok().json(serde_json::json!({ "success": true, "slas": user_slas })).build() } /// PHASE 3 FIX: Update SLA pub async fn update_sla( path: web::Path, sla_data: web::Json, session: Session, ) -> Result { let sla_id = path.into_inner(); // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()?); } }; // Get existing SLA let mut existing_sla = match UserPersistence::get_user_sla_by_id(&user_email, &sla_id) { Some(sla) => sla, None => { return ResponseBuilder::not_found().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); } }; // Update fields if provided if let Some(name) = sla_data.get("name").and_then(|v| v.as_str()) { existing_sla.name = name.to_string(); } if let Some(description) = sla_data.get("description").and_then(|v| v.as_str()) { existing_sla.description = description.to_string(); } if let Some(response_time) = sla_data.get("response_time_hours").and_then(|v| v.as_i64()) { existing_sla.response_time_hours = response_time as i32; } if let Some(resolution_time) = sla_data.get("resolution_time_hours").and_then(|v| v.as_i64()) { existing_sla.resolution_time_hours = resolution_time as i32; } if let Some(availability) = sla_data.get("availability_percentage").and_then(|v| v.as_f64()) { existing_sla.availability_percentage = availability as f32; } if let Some(status) = sla_data.get("status").and_then(|v| v.as_str()) { existing_sla.status = status.to_string(); } // Save updated SLA match UserPersistence::update_user_sla(&user_email, existing_sla.clone()) { Ok(true) => { ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": "SLA updated successfully", "sla": existing_sla })).build() } Ok(false) => { ResponseBuilder::not_found().json(serde_json::json!({ "success": false, "message": "SLA not found" })).build() } Err(e) => { ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "message": "Failed to update SLA", "error": e.to_string() })).build() } } } /// PHASE 3 FIX: Delete SLA pub async fn delete_sla( path: web::Path, session: Session, ) -> Result { let sla_id = path.into_inner(); // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); } }; // Remove SLA match UserPersistence::remove_user_sla(&user_email, &sla_id) { Ok(true) => { ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": "SLA deleted successfully", "id": sla_id })).build() } Ok(false) => { ResponseBuilder::not_found().json(serde_json::json!({ "success": false, "message": "SLA not found" })).build() } Err(e) => { ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "message": "Failed to delete SLA", "error": e.to_string() })).build() } } } /// Download service provider agreement pub async fn download_agreement(session: Session) -> Result { // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); } }; // Generate text agreement content let agreement_content = format!( "SERVICE PROVIDER AGREEMENT\n\n\ ThreeFold Grid Service Provider Agreement\n\ \n\ Service Provider: {}\n\ Agreement Date: January 15, 2025\n\ Renewal Date: January 15, 2026\n\ Status: Active\n\ \n\ This agreement outlines the terms and conditions for providing services\n\ on the ThreeFold Grid platform.\n\ \n\ Terms:\n\ 1. Service Quality Standards\n\ 2. Payment Terms and Conditions\n\ 3. Data Protection and Privacy\n\ 4. Intellectual Property Rights\n\ 5. Termination Conditions\n\ \n\ For full terms and conditions, please visit:\n\ https://threefold.io/terms/service-providers\n\ \n\ Generated on: {}\n", user_email, chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC") ); // Return the agreement as a downloadable text file ResponseBuilder::ok() .content_type("text/plain") .add_metadata("Content-Disposition", format!("attachment; filename=\"service-provider-agreement-{}.txt\"", user_email.replace("@", "_at_"))) .body(agreement_content) .build() } /// Get detailed service request information pub async fn get_service_request_details( path: web::Path, session: Session, ) -> Result { let request_id = path.into_inner(); // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); } }; // Get user service requests let service_requests = UserPersistence::get_user_service_requests(&user_email); // Find the specific request if let Some(request) = service_requests.iter().find(|r| r.id == request_id) { // Create detailed request object with additional fields let detailed_request = serde_json::json!({ "id": request.id, "client_name": request.customer_email.split('@').next().unwrap_or(&request.customer_email), "service_name": request.service_name, "status": request.status, "requested_date": request.requested_date, "estimated_hours": request.estimated_hours, "budget": request.budget, "priority": request.priority, "progress": request.progress.unwrap_or(if request.status == "Completed" { 100.0 } else if request.status == "In Progress" { 50.0 } else { 0.0 }), "description": format!("Service request for {} from {}", request.service_name, request.customer_email.split('@').next().unwrap_or(&request.customer_email)), "notes": "Progress updates and notes will appear here." }); ResponseBuilder::ok().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build() } else { ResponseBuilder::not_found().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build() } } /// Get completed request details with additional metrics pub async fn get_completed_request_details( path: web::Path, session: Session, ) -> Result { let request_id = path.into_inner(); // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Get user service requests let service_requests = UserPersistence::get_user_service_requests(&user_email); // Find the specific completed request if let Some(request) = service_requests.iter().find(|r| r.id == request_id && r.status == "Completed") { // Create detailed completed request object with metrics let completed_request = serde_json::json!({ "id": request.id, "client_name": request.customer_email.split('@').next().unwrap_or(&request.customer_email), "service_name": request.service_name, "completed_date": chrono::Utc::now().format("%Y-%m-%d").to_string(), "hours_logged": request.estimated_hours, "revenue": request.budget, "rating": 4.8, "on_time": true, "summary": format!("Successfully completed {} service for {}. All requirements met and client satisfied.", request.service_name, request.customer_email.split('@').next().unwrap_or(&request.customer_email)), "client_feedback": "Excellent work! Professional, timely, and exceeded expectations." }); Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } else { Ok(ResponseBuilder::not_found().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } } /// Generate invoice for completed request (HTML page for viewing and printing) pub async fn generate_service_request_invoice( tmpl: web::Data, path: web::Path, session: Session, ) -> Result { let request_id = path.into_inner(); // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Load user with mock data if let Some(user) = Self::load_user_with_persistent_data(&session) { // Get user service requests let service_requests = UserPersistence::get_user_service_requests(&user_email); // Find the specific completed request if let Some(request) = service_requests.iter().find(|r| r.id == request_id && r.status == "Completed") { // Calculate rate safely let rate = if let Some(hours) = request.estimated_hours { if hours > 0 { request.budget / rust_decimal::Decimal::from(hours) } else { rust_decimal::Decimal::ZERO } } else { rust_decimal::Decimal::ZERO }; // Generate dates let invoice_date = chrono::Utc::now().format("%Y-%m-%d").to_string(); let due_date = (chrono::Utc::now() + chrono::Duration::days(30)).format("%Y-%m-%d").to_string(); let generated_date = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(); // Format status for CSS class (lowercase, replace spaces with dashes) let status_class = request.status.to_lowercase().replace(' ', "-"); let mut ctx = tera::Context::new(); ctx.insert("active_page", "dashboard"); ctx.insert("active_section", "service-provider"); ctx.insert("request", request); ctx.insert("status_class", &status_class); ctx.insert("rate", &rate); ctx.insert("user", &user); ctx.insert("invoice_date", &invoice_date); ctx.insert("due_date", &due_date); ctx.insert("generated_date", &generated_date); Ok(render_template(&tmpl, "dashboard/service_request_invoice.html", &ctx)) } else { Ok(ResponseBuilder::not_found().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } } else { Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } } /// Get service request report page (for viewing and printing) pub async fn get_service_request_report( tmpl: web::Data, path: web::Path, session: Session, ) -> Result { let request_id = path.into_inner(); // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Load user with mock data if let Some(user) = Self::load_user_with_persistent_data(&session) { // Load persistent data for this user if let Some(persistent_data) = UserPersistence::load_user_data(&user.email) { // Find the completed request from service_requests if let Some(request) = persistent_data.service_requests.iter() .find(|r| r.id == request_id && r.status == "Completed") { // Calculate rate (budget / estimated_hours) let rate = if let Some(hours) = request.estimated_hours { if hours > 0 { request.budget / rust_decimal::Decimal::from(hours) } else { rust_decimal::Decimal::ZERO } } else { rust_decimal::Decimal::ZERO }; // Format status for CSS class (lowercase, replace spaces with dashes) let status_class = request.status.to_lowercase().replace(' ', "-"); let mut ctx = tera::Context::new(); ctx.insert("active_page", "dashboard"); ctx.insert("active_section", "service-provider"); ctx.insert("request", request); ctx.insert("status_class", &status_class); ctx.insert("rate", &rate); ctx.insert("user", &user); Ok(render_template(&tmpl, "dashboard/service_request_report.html", &ctx)) } else { Ok(ResponseBuilder::not_found().json(serde_json::json!({ "success": false, "error": "Service request not found or not completed" })).build()) } } else { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Failed to load user data" })).build()) } } else { Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "User session not found" })).build()) } } /// Get comprehensive service details with analytics and client data pub async fn get_service_details( path: web::Path, session: Session, ) -> Result { let service_id = path.into_inner(); // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Load user's services from persistent storage let user_services = UserPersistence::get_user_services(&user_email); // Find the specific service if let Some(service) = user_services.iter().find(|s| s.id == service_id) { // Load service requests to calculate analytics let service_requests = UserPersistence::get_user_service_requests(&user_email); let service_related_requests: Vec<_> = service_requests.iter() .filter(|req| req.service_name == service.name) .collect(); // Calculate analytics let total_revenue: i32 = service_related_requests.iter() .filter(|req| req.status == "Completed") .map(|req| req.budget.to_string().parse::().unwrap_or(0)) .sum(); let completed_requests = service_related_requests.iter() .filter(|req| req.status == "Completed") .count(); let avg_rating = if completed_requests > 0 { service.rating } else { 0.0 }; let on_time_delivery = if completed_requests > 0 { 95.0 } else { 0.0 }; // Mock calculation // Generate monthly revenue data (mock for now) let monthly_revenue = if total_revenue > 0 { total_revenue / 3 } else { 0 }; // Get active clients (unique client names from non-completed requests) let active_clients: std::collections::HashSet<_> = service_related_requests.iter() .filter(|req| req.status != "Completed") .map(|req| req.customer_email.split('@').next().unwrap_or(&req.customer_email).to_string()) .collect(); let response = serde_json::json!({ "success": true, "service": { "id": service.id, "name": service.name, "description": service.description, "category": service.category, "price_per_hour": service.price_per_hour_usd, "status": service.status, "clients": service.clients, "rating": service.rating, "total_hours": service.total_hours, "analytics": { "revenue": { "total": total_revenue, "monthly": monthly_revenue, "trend": [monthly_revenue - 2000, monthly_revenue - 1000, monthly_revenue] }, "performance": { "avg_rating": avg_rating, "on_time_delivery": on_time_delivery, "response_time_avg": 1.5 }, "client_satisfaction": avg_rating, "completed_projects": completed_requests, "active_clients": active_clients.len() }, "availability": { "weekly_hours": 20, "response_time": "2 hours" }, "skills": ["System Administration", "Security", "DevOps"], // Mock data "experience_level": "Expert" } }); Ok(ResponseBuilder::ok().json(response).build()) } else { Ok(ResponseBuilder::not_found().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } } /// Get service analytics data pub async fn get_service_analytics( path: web::Path, session: Session, ) -> Result { let service_id = path.into_inner(); // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Load user's services and requests let user_services = UserPersistence::get_user_services(&user_email); let service_requests = UserPersistence::get_user_service_requests(&user_email); if let Some(service) = user_services.iter().find(|s| s.id == service_id) { let service_related_requests: Vec<_> = service_requests.iter() .filter(|req| req.service_name == service.name) .collect(); // Calculate detailed analytics let total_revenue: i32 = service_related_requests.iter() .filter(|req| req.status == "Completed") .map(|req| req.budget.to_string().parse::().unwrap_or(0)) .sum(); let total_hours: i32 = service_related_requests.iter() .filter(|req| req.status == "Completed") .map(|req| req.estimated_hours.unwrap_or(0)) .sum(); let completed_count = service_related_requests.iter() .filter(|req| req.status == "Completed") .count(); // Generate revenue trend (last 6 months) let mut revenue_trend = Vec::new(); let monthly_avg = if total_revenue > 0 { total_revenue / 6 } else { 0 }; for i in 0..6 { let variation = (i as f32 * 0.2 + 0.8) * monthly_avg as f32; revenue_trend.push(variation as i32); } let response = serde_json::json!({ "success": true, "analytics": { "revenue": { "total": total_revenue, "monthly": monthly_avg, "trend": revenue_trend, "per_hour_avg": if total_hours > 0 { total_revenue / total_hours } else { 0 } }, "performance": { "avg_rating": service.rating, "on_time_delivery": 95.0, "response_time_avg": 1.5, "completion_rate": if service_related_requests.len() > 0 { (completed_count as f32 / service_related_requests.len() as f32) * 100.0 } else { 0.0 } }, "client_metrics": { "total_clients": service.clients, "repeat_clients": (service.clients as f32 * 0.7) as i32, "client_satisfaction": service.rating, "referral_rate": 25.0 }, "project_metrics": { "total_projects": service_related_requests.len(), "completed_projects": completed_count, "total_hours": total_hours, "avg_project_size": if completed_count > 0 { total_hours / completed_count as i32 } else { 0 } } } }); Ok(ResponseBuilder::ok().json(response).build()) } else { Ok(ResponseBuilder::not_found().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } } /// Get service client history and relationships pub async fn get_service_clients( path: web::Path, session: Session, ) -> Result { let service_id = path.into_inner(); // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Load user's services and requests let user_services = UserPersistence::get_user_services(&user_email); let service_requests = UserPersistence::get_user_service_requests(&user_email); if let Some(service) = user_services.iter().find(|s| s.id == service_id) { let service_related_requests: Vec<_> = service_requests.iter() .filter(|req| req.service_name == service.name) .collect(); // Group requests by client let mut client_data = std::collections::HashMap::new(); for request in &service_related_requests { let client_entry = client_data.entry(request.customer_email.split('@').next().unwrap_or(&request.customer_email).to_string()).or_insert_with(|| { serde_json::json!({ "name": request.customer_email.split('@').next().unwrap_or(&request.customer_email), "email": request.customer_email.clone(), "phone": "", // No phone field in ServiceRequest "projects": [], "total_revenue": 0, "total_hours": 0, "avg_rating": 0.0, "status": "Active" }) }); // Add project to client if let Some(projects) = client_entry.get_mut("projects") { if let Some(projects_array) = projects.as_array_mut() { projects_array.push(serde_json::json!({ "id": request.id, "service_name": request.service_name, "status": request.status, "requested_date": request.requested_date, "completed_date": request.completed_date, "budget": request.budget, "estimated_hours": request.estimated_hours, "priority": request.priority, "description": request.description.clone().unwrap_or_default() })); } } // Update totals if let Some(total_revenue) = client_entry.get_mut("total_revenue") { if request.status == "Completed" { *total_revenue = serde_json::Value::Number( serde_json::Number::from(total_revenue.as_i64().unwrap_or(0) + request.budget.to_i64().unwrap_or(0)) ); } } if let Some(total_hours) = client_entry.get_mut("total_hours") { if request.status == "Completed" { *total_hours = serde_json::Value::Number( serde_json::Number::from(total_hours.as_i64().unwrap_or(0) + request.estimated_hours.unwrap_or(0) as i64) ); } } } // Convert to array and add ratings let clients: Vec<_> = client_data.into_values().map(|mut client| { // Set average rating (mock calculation) let rating = 4.2 + (rand::random::() * 1.6); client["avg_rating"] = serde_json::Value::Number( serde_json::Number::from_f64(rating).unwrap_or_else(|| serde_json::Number::from(4)) ); client }).collect(); let response = serde_json::json!({ "success": true, "clients": clients, "summary": { "total_clients": clients.len(), "active_clients": clients.iter().filter(|c| c["status"] == "Active").count(), "total_projects": service_related_requests.len(), "completed_projects": service_related_requests.iter().filter(|r| r.status == "Completed").count() } }); Ok(ResponseBuilder::ok().json(response).build()) } else { Ok(ResponseBuilder::not_found().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } } /// Update service status (Active/Paused/Draft) pub async fn update_service_status( path: web::Path, status_data: web::Json, session: Session, ) -> Result { let service_id = path.into_inner(); // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Extract new status let new_status = status_data.get("status") .and_then(|s| s.as_str()) .unwrap_or("Active") .to_string(); // Validate status if !["Active", "Paused", "Draft"].contains(&new_status.as_str()) { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } // Load user's persistent data if let Some(mut persistent_data) = UserPersistence::load_user_data(&user_email) { // Find and update the service status if let Some(service) = persistent_data.services.iter_mut().find(|s| s.id == service_id) { let old_status = service.status.clone(); let service_name = service.name.clone(); let service_id_clone = service.id.clone(); service.status = new_status.clone(); // Save updated persistent data if let Err(e) = UserPersistence::save_user_data(&persistent_data) { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } return Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } } Ok(ResponseBuilder::not_found().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } // ======================================== // APP PROVIDER MANAGEMENT ENDPOINTS // ======================================== /// Get all apps for the current user pub async fn get_user_apps(session: Session) -> Result { let user_email = match session.get::("user_email") { Ok(Some(email)) => email, Ok(None) => { return ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); }, Err(e) => { return ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); } }; let apps = UserPersistence::get_user_apps(&user_email); ResponseBuilder::ok().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build() } /// Create a new app pub async fn create_app( app_data: web::Json, session: Session ) -> Result { let user_email = match session.get::("user_email") { Ok(Some(email)) => email, Ok(None) => { return ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); }, Err(e) => { return ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); } }; // Convert JSON to PublishedApp let app = match Self::json_to_app(&app_data) { Some(app) => app, None => { return ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); } }; // Save the app match UserPersistence::add_user_app(&user_email, app.clone()) { Ok(()) => { // App registration is now handled through persistent user data // Products are automatically aggregated by ProductService from user-owned data ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": "App published successfully" })).build() }, Err(e) => { ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": format!("Failed to publish app: {}", e) })).build() } } } /// Update an existing app pub async fn update_app( app_id: web::Path, app_data: web::Json, session: Session ) -> Result { let user_email = match session.get::("user_email") { Ok(Some(email)) => email, Ok(None) => { return ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); }, Err(e) => { return ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); } }; // Convert JSON to PublishedApp let mut updated_app = match Self::json_to_app(&app_data) { Some(app) => app, None => { return ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); } }; // Ensure the app ID matches updated_app.id = app_id.to_string(); // Update the app match UserPersistence::update_user_app(&user_email, updated_app.clone()) { Ok(true) => { ResponseBuilder::ok().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build() }, Ok(false) => { ResponseBuilder::not_found().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build() }, Err(e) => { ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build() } } } /// Delete an app pub async fn delete_app( app_id: web::Path, session: Session ) -> Result { let user_email = match session.get::("user_email") { Ok(Some(email)) => email, Ok(None) => { return ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); }, Err(e) => { return ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build(); } }; // Delete the app match UserPersistence::remove_user_app(&user_email, &app_id) { Ok(true) => { ResponseBuilder::ok().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build() }, Ok(false) => { ResponseBuilder::not_found().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build() }, Err(e) => { ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build() } } } /// API endpoint to get deployment details by ID pub async fn get_deployment_details( path: web::Path, session: Session ) -> Result { let deployment_id = path.into_inner(); // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, Ok(None) => { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "message": "Unauthorized" })).build()); }, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "message": "Failed to retrieve session" })).build()); } }; // Load user's app deployments let deployments = UserPersistence::get_user_application_deployments(&user_email); // Find the specific deployment if let Some(deployment) = deployments.iter().find(|d| d.id == deployment_id) { Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "deployment": deployment })).build()) } else { Ok(ResponseBuilder::not_found().json(serde_json::json!({ "success": false, "message": "Deployment not found" })).build()) } } /// Helper function to convert JSON to PublishedApp fn json_to_app(json_value: &serde_json::Value) -> Option { let name = json_value.get("name")?.as_str()?.to_string(); let category = json_value.get("category")?.as_str().unwrap_or("Other").to_string(); let version = json_value.get("version")?.as_str().unwrap_or("1.0.0").to_string(); let status = json_value.get("status")?.as_str().unwrap_or("Active").to_string(); let deployments = json_value.get("deployments")?.as_i64().unwrap_or(0) as i32; let rating = json_value.get("rating")?.as_f64().unwrap_or(4.5) as f32; let monthly_revenue_usd = json_value .get("monthly_revenue_usd") .or_else(|| json_value.get("monthly_revenue")) .and_then(|v| v.as_i64()) .unwrap_or(0) as i32; let last_updated = json_value.get("last_updated")?.as_str().unwrap_or("Today").to_string(); // Generate ID if not provided let id = json_value.get("id") .and_then(|v| v.as_str()) .map(|s| s.to_string()) .unwrap_or_else(|| format!("app_{}", Utc::now().timestamp())); Some(crate::models::user::PublishedApp::builder() .id(id) .name(name) .category(category) .version(version) .status(status) .deployments(deployments) .rating(rating) .monthly_revenue_usd(monthly_revenue_usd) .last_updated(last_updated) .auto_healing(true) // Default to enabled for new apps .build() .unwrap()) } /// Delete a node pub async fn delete_node( session: Session, path: web::Path, ) -> Result { let node_id = path.into_inner(); let user_email = match session.get::("user_email") { Ok(Some(email)) => email, Ok(None) => { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Check if node exists and get its details first let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Verify node exists and belongs to user let _node = match resource_provider_service.get_node_by_id(&user_email, &node_id) { Some(node) => node, None => { return Ok(ResponseBuilder::not_found().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Check if node has active rentals (optional safety check) // This could be expanded to check for active slice rentals // Remove node from persistent storage match UserPersistence::remove_user_node(&user_email, &node_id) { Ok(_) => { // TODO: Remove associated marketplace products // This would involve removing slice products and full node products // from the marketplace that were created for this node Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to delete node", "details": e.to_string() })).build()) } } } /// Update node configuration (group, slice formats, rental options) pub async fn update_node_configuration( session: Session, path: web::Path, form: web::Json, ) -> Result { let node_id = path.into_inner(); let user_email = match session.get::("user_email") { Ok(Some(email)) => email, Ok(None) => { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; let _resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Parse the configuration update let config_data = form.into_inner(); // Extract configuration fields let node_group_id = config_data.get("node_group_id") .and_then(|v| v.as_str()) .map(|s| if s.is_empty() { None } else { Some(s.to_string()) }) .flatten(); let slice_formats = config_data.get("slice_formats") .and_then(|v| v.as_array()) .map(|arr| { let mut formats: Vec = arr.iter() .filter_map(|v| v.as_str()) .map(|s| s.to_string()) .collect(); // Remove duplicates while preserving order formats.dedup(); formats }); let full_node_rental_enabled = config_data.get("full_node_rental_enabled") .and_then(|v| v.as_bool()) .unwrap_or(false); // Parse comprehensive full node pricing configuration let full_node_pricing = if let Some(pricing_data) = config_data.get("full_node_pricing") { if let Some(pricing_obj) = pricing_data.as_object() { let hourly = pricing_obj.get("hourly") .and_then(|v| v.as_f64()) .map(|price| rust_decimal::Decimal::try_from(price).unwrap_or(rust_decimal::Decimal::ZERO)) .unwrap_or(rust_decimal::Decimal::ZERO); let daily = pricing_obj.get("daily") .and_then(|v| v.as_f64()) .map(|price| rust_decimal::Decimal::try_from(price).unwrap_or(rust_decimal::Decimal::ZERO)) .unwrap_or(rust_decimal::Decimal::ZERO); let monthly = pricing_obj.get("monthly") .and_then(|v| v.as_f64()) .map(|price| rust_decimal::Decimal::try_from(price).unwrap_or(rust_decimal::Decimal::ZERO)) .unwrap_or(rust_decimal::Decimal::ZERO); let yearly = pricing_obj.get("yearly") .and_then(|v| v.as_f64()) .map(|price| rust_decimal::Decimal::try_from(price).unwrap_or(rust_decimal::Decimal::ZERO)) .unwrap_or(rust_decimal::Decimal::ZERO); let auto_calculate = pricing_obj.get("auto_calculate") .and_then(|v| v.as_bool()) .unwrap_or(true); let daily_discount_percent = pricing_obj.get("daily_discount_percent") .and_then(|v| v.as_f64()) .unwrap_or(0.0) as f32; let monthly_discount_percent = pricing_obj.get("monthly_discount_percent") .and_then(|v| v.as_f64()) .unwrap_or(0.0) as f32; let yearly_discount_percent = pricing_obj.get("yearly_discount_percent") .and_then(|v| v.as_f64()) .unwrap_or(0.0) as f32; // Create pricing using builder pattern let setup_fee = pricing_obj.get("setup_fee") .and_then(|v| v.as_f64()) .map(|price| rust_decimal::Decimal::try_from(price).unwrap_or_default()); let deposit_required = pricing_obj.get("deposit_required") .and_then(|v| v.as_f64()) .map(|price| rust_decimal::Decimal::try_from(price).unwrap_or_default()); let full_node_pricing = crate::models::user::FullNodePricing { monthly_cost: monthly, setup_fee, deposit_required, ..Default::default() }; Some(full_node_pricing) } else { None } } else { None }; // Legacy support for old monthly price field let full_node_monthly_price = config_data.get("full_node_monthly_price") .and_then(|v| v.as_f64()) .map(|price| rust_decimal::Decimal::try_from(price).unwrap_or(rust_decimal::Decimal::ZERO)); let minimum_rental_days = config_data.get("minimum_rental_days") .and_then(|v| v.as_u64()) .unwrap_or(30) as u32; let maximum_rental_days = config_data.get("maximum_rental_days") .and_then(|v| v.as_u64()) .unwrap_or(365) as u32; let auto_renewal_enabled = config_data.get("auto_renewal_enabled") .and_then(|v| v.as_bool()); let availability_status = config_data.get("availability_status") .and_then(|v| v.as_str()) .and_then(|s| match s { "Available" => Some(crate::models::user::NodeAvailabilityStatus::Available), "PartiallyRented" | "FullyRented" | "Rented" => Some(crate::models::user::NodeAvailabilityStatus::Rented), "Unavailable" | "Offline" => Some(crate::models::user::NodeAvailabilityStatus::Offline), "Maintenance" => Some(crate::models::user::NodeAvailabilityStatus::Maintenance), _ => None, }); // Only validate slice formats if they are being updated if let Some(ref formats) = slice_formats { if formats.is_empty() && !full_node_rental_enabled { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } } // Load current node data let mut persistent_data = match UserPersistence::load_user_data(&user_email) { Some(data) => data, None => { return Ok(ResponseBuilder::not_found().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Find and update the node if let Some(node) = persistent_data.nodes.iter_mut().find(|n| n.id == node_id) { // Update node group only if provided if config_data.get("node_group_id").is_some() { if let Some(group_id) = node_group_id { // Node group assignment - store in grid_data or rental_options if let Some(rental_options) = &mut node.rental_options { if let Some(rental_obj) = rental_options.as_object_mut() { rental_obj.insert("node_group_id".to_string(), serde_json::Value::String(group_id)); } } } else { // Remove node group assignment if let Some(rental_options) = &mut node.rental_options { if let Some(rental_obj) = rental_options.as_object_mut() { rental_obj.remove("node_group_id"); } } } } // Update slice formats only if provided if config_data.get("slice_formats").is_some() { if let Some(ref formats) = slice_formats { node.slice_formats = Some(formats.clone()); } } // Update rental options only if provided if config_data.get("full_node_rental_enabled").is_some() || config_data.get("full_node_pricing").is_some() || config_data.get("full_node_monthly_price").is_some() || config_data.get("minimum_rental_days").is_some() { if let Some(ref mut rental_options) = node.rental_options { // Update existing rental options if config_data.get("full_node_rental_enabled").is_some() { if let Some(rental_obj) = rental_options.as_object_mut() { rental_obj.insert("full_node_rental_enabled".to_string(), serde_json::Value::Bool(full_node_rental_enabled)); } } // Update with new comprehensive pricing if provided if let Some(pricing) = full_node_pricing { if let Some(rental_obj) = rental_options.as_object_mut() { rental_obj.insert("full_node_pricing".to_string(), serde_json::to_value(pricing).unwrap_or_default()); } } else if let Some(price) = full_node_monthly_price { // Legacy support: convert monthly price to comprehensive pricing let legacy_pricing = crate::models::user::FullNodePricing { monthly_cost: price, setup_fee: None, deposit_required: None, ..Default::default() }; if let Some(rental_obj) = rental_options.as_object_mut() { rental_obj.insert("full_node_pricing".to_string(), serde_json::to_value(legacy_pricing).unwrap_or_default()); } } if config_data.get("minimum_rental_days").is_some() { if let Some(rental_obj) = rental_options.as_object_mut() { rental_obj.insert("minimum_rental_days".to_string(), serde_json::Value::Number(serde_json::Number::from(minimum_rental_days))); } } } else if full_node_rental_enabled || full_node_pricing.is_some() { // Create rental options directly let pricing = if let Some(pricing) = full_node_pricing { pricing } else if let Some(price) = full_node_monthly_price { // Legacy support: convert monthly price to comprehensive pricing crate::models::user::FullNodePricing { monthly_cost: price, setup_fee: None, deposit_required: None, ..Default::default() } } else { crate::models::user::FullNodePricing { monthly_cost: rust_decimal::Decimal::ZERO, setup_fee: None, deposit_required: None, ..Default::default() } }; let rental_options = crate::models::user::NodeRentalOptions { full_node_available: full_node_rental_enabled, slice_formats: slice_formats.unwrap_or_default(), pricing: pricing.clone(), slice_rental_enabled: true, minimum_rental_days: if minimum_rental_days == 0 { 1 } else { minimum_rental_days }, maximum_rental_days: Some(maximum_rental_days), full_node_rental_enabled: full_node_rental_enabled, full_node_pricing: if full_node_rental_enabled { Some(pricing) } else { None }, auto_renewal_enabled: auto_renewal_enabled.unwrap_or(true), }; node.rental_options = Some(serde_json::to_value(rental_options).unwrap_or_default()); } } // Update availability status only if provided if config_data.get("availability_status").is_some() { if let Some(status) = availability_status { // Store availability status in grid_data or rental_options if let Some(grid_data) = &mut node.grid_data { if let Some(grid_obj) = grid_data.as_object_mut() { grid_obj.insert("availability_status".to_string(), serde_json::to_value(status).unwrap_or_default()); } } else { let mut grid_data = serde_json::Map::new(); grid_data.insert("availability_status".to_string(), serde_json::to_value(status).unwrap_or_default()); node.grid_data = Some(serde_json::Value::Object(grid_data)); } } } // Update staking configuration if provided if config_data.get("staking_enabled").is_some() || config_data.get("staked_amount").is_some() || config_data.get("staking_period_months").is_some() || config_data.get("early_withdrawal_allowed").is_some() || config_data.get("early_withdrawal_penalty_percent").is_some() { let staking_enabled = config_data.get("staking_enabled") .and_then(|v| v.as_bool()) .unwrap_or(false); if staking_enabled { // Parse staking configuration let staked_amount = config_data.get("staked_amount") .and_then(|v| v.as_f64()) .map(|amount| rust_decimal::Decimal::try_from(amount).unwrap_or(rust_decimal::Decimal::ZERO)) .unwrap_or(rust_decimal::Decimal::ZERO); let staking_period_months = config_data.get("staking_period_months") .and_then(|v| v.as_u64()) .unwrap_or(12) as u32; let early_withdrawal_allowed = config_data.get("early_withdrawal_allowed") .and_then(|v| v.as_bool()) .unwrap_or(true); let early_withdrawal_penalty_percent = config_data.get("early_withdrawal_penalty_percent") .and_then(|v| v.as_f64()) .unwrap_or(25.0) as f32; // Validate wallet balance if this is a new staking or increase let current_staked = if let Some(grid_data) = &node.grid_data { grid_data.get("staked_amount") .and_then(|v| v.as_str()) .and_then(|s| rust_decimal::Decimal::from_str(s).ok()) .unwrap_or(rust_decimal::Decimal::ZERO) } else { rust_decimal::Decimal::ZERO }; let additional_stake_needed = staked_amount - current_staked; if additional_stake_needed > Decimal::ZERO { if persistent_data.wallet_balance_usd < additional_stake_needed { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } // Deduct additional stake from wallet persistent_data.wallet_balance_usd -= additional_stake_needed; } else if additional_stake_needed < Decimal::ZERO { // Return excess stake to wallet (with potential penalty) let return_amount = additional_stake_needed.abs(); let penalty = if let Some(grid_data) = &node.grid_data { let early_withdrawal_allowed = grid_data.get("early_withdrawal_allowed") .and_then(|v| v.as_bool()) .unwrap_or(true); let penalty_percent = grid_data.get("early_withdrawal_penalty_percent") .and_then(|v| v.as_f64()) .unwrap_or(25.0); if early_withdrawal_allowed { return_amount * rust_decimal::Decimal::try_from(penalty_percent).unwrap_or_default() / rust_decimal::Decimal::from(100) } else { rust_decimal::Decimal::ZERO } } else { rust_decimal::Decimal::ZERO }; persistent_data.wallet_balance_usd += return_amount - penalty; } // Build staking options match NodeStakingOptionsBuilder::new() .staking_enabled(true) .staked_amount(staked_amount) .staking_period_months(staking_period_months) .early_withdrawal_allowed(early_withdrawal_allowed) .early_withdrawal_penalty_percent(early_withdrawal_penalty_percent) .staking_start_date( if let Some(grid_data) = &node.grid_data { // Try to get existing start date from grid_data grid_data.get("staking_start_date") .and_then(|v| v.as_str()) .and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok()) .map(|dt| dt.with_timezone(&chrono::Utc)) .unwrap_or_else(|| chrono::Utc::now()) } else { // Set new start date if creating chrono::Utc::now() } ) .build() { Ok(staking_options) => { // Store staking options in grid_data if let Some(grid_data) = &mut node.grid_data { if let Some(grid_obj) = grid_data.as_object_mut() { grid_obj.insert("staking_options".to_string(), serde_json::to_value(staking_options).unwrap_or_default()); } } else { let mut grid_data = serde_json::Map::new(); grid_data.insert("staking_options".to_string(), serde_json::to_value(staking_options).unwrap_or_default()); node.grid_data = Some(serde_json::Value::Object(grid_data)); } } Err(e) => { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } } } else { // Disable staking - return staked amount to wallet // Check for existing staking in grid_data and return staked amount to wallet if let Some(grid_data) = &node.grid_data { if let Some(staking_data) = grid_data.get("staking_options") { let staking_enabled = staking_data.get("staking_enabled") .and_then(|v| v.as_bool()) .unwrap_or(false); if staking_enabled { let return_amount = staking_data.get("staked_amount") .and_then(|v| v.as_str()) .and_then(|s| rust_decimal::Decimal::from_str(s).ok()) .unwrap_or(rust_decimal::Decimal::ZERO); let early_withdrawal_allowed = staking_data.get("early_withdrawal_allowed") .and_then(|v| v.as_bool()) .unwrap_or(true); let penalty_percent = staking_data.get("early_withdrawal_penalty_percent") .and_then(|v| v.as_f64()) .unwrap_or(25.0); let penalty = if early_withdrawal_allowed { return_amount * rust_decimal::Decimal::try_from(penalty_percent).unwrap_or_default() / rust_decimal::Decimal::from(100) } else { rust_decimal::Decimal::ZERO }; persistent_data.wallet_balance_usd += return_amount - penalty; } } } // Remove staking options from grid_data if let Some(grid_data) = &mut node.grid_data { if let Some(grid_obj) = grid_data.as_object_mut() { grid_obj.remove("staking_options"); } } } } // Save updated data match UserPersistence::save_user_data(&persistent_data) { Ok(_) => { // TODO: Update marketplace products based on new configuration // This would involve updating slice products and full node products Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to save node configuration", "details": e.to_string() })).build()) } } } else { Ok(ResponseBuilder::not_found().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } } /// Stake MC Credits on a node pub async fn stake_on_node( session: Session, path: web::Path, form: web::Json, ) -> Result { let node_id = path.into_inner(); let user_email = match session.get::("user_email") { Ok(Some(email)) => email, Ok(None) => { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Parse staking configuration let staking_data = form.into_inner(); let staked_amount = staking_data.get("staked_amount") .and_then(|v| v.as_str()) .and_then(|s| rust_decimal::Decimal::from_str(s).ok()) .unwrap_or(rust_decimal::Decimal::ZERO); let staking_period_months = staking_data.get("staking_period_months") .and_then(|v| v.as_u64()) .unwrap_or(3) as u32; let early_withdrawal_allowed = staking_data.get("early_withdrawal_allowed") .and_then(|v| v.as_bool()) .unwrap_or(true); // Build staking options let staking_options = match NodeStakingOptionsBuilder::new() .staking_enabled(true) .staked_amount(staked_amount) .staking_period_months(staking_period_months) .early_withdrawal_allowed(early_withdrawal_allowed) .build() { Ok(options) => options, Err(e) => { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Stake on node match resource_provider_service.stake_on_node(&user_email, &node_id, staking_options) { Ok(()) => { Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": format!("Successfully staked ${} USD Credits on node", staked_amount) })).build()) } Err(e) => { Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } } } /// Update staking on a node pub async fn update_node_staking( session: Session, path: web::Path, form: web::Json, ) -> Result { let node_id = path.into_inner(); let user_email = match session.get::("user_email") { Ok(Some(email)) => email, Ok(None) => { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Parse staking configuration let staking_data = form.into_inner(); let action = staking_data.get("action") .and_then(|v| v.as_str()) .unwrap_or("update"); match action { "unstake" => { // Unstake from node match resource_provider_service.unstake_from_node(&user_email, &node_id) { Ok(returned_amount) => { Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": format!("Successfully unstaked ${} USD Credits from node", returned_amount), "returned_amount": returned_amount })).build()) } Err(e) => { Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } } } "update" => { // Update staking amount let staked_amount = staking_data.get("staked_amount") .and_then(|v| v.as_str()) .and_then(|s| rust_decimal::Decimal::from_str(s).ok()) .unwrap_or(rust_decimal::Decimal::ZERO); let staking_period_months = staking_data.get("staking_period_months") .and_then(|v| v.as_u64()) .unwrap_or(3) as u32; let early_withdrawal_allowed = staking_data.get("early_withdrawal_allowed") .and_then(|v| v.as_bool()) .unwrap_or(true); // Build new staking options let staking_options = match NodeStakingOptionsBuilder::new() .staking_enabled(true) .staked_amount(staked_amount) .staking_period_months(staking_period_months) .early_withdrawal_allowed(early_withdrawal_allowed) .build() { Ok(options) => options, Err(e) => { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Update node staking match resource_provider_service.update_node_staking(&user_email, &node_id, staking_options) { Ok(()) => { Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": format!("Successfully updated staking to ${} USD Credits", staked_amount) })).build()) } Err(e) => { Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } } } _ => { Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } } } /// Get staking statistics for a user pub async fn get_staking_statistics(session: Session) -> Result { let user_email = match session.get::("user_email") { Ok(Some(email)) => email, Ok(None) => { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; let statistics = resource_provider_service.get_staking_statistics(&user_email); Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } /// Add user activity tracking pub async fn add_user_activity( session: Session, activity_data: web::Json ) -> Result { let user_service = match crate::services::user_service::UserService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) }; // Parse activity data let activity_type_str = activity_data.get("activity_type") .and_then(|v| v.as_str()) .unwrap_or("Unknown"); let activity_type = match activity_type_str { "Login" => crate::models::user::ActivityType::Login, "Purchase" => crate::models::user::ActivityType::Purchase, "Deployment" => crate::models::user::ActivityType::Deployment, "MarketplaceView" => crate::models::user::ActivityType::MarketplaceView, _ => crate::models::user::ActivityType::MarketplaceView, }; let description = activity_data.get("description") .and_then(|v| v.as_str()) .unwrap_or("User activity") .to_string(); // Create activity using builder pattern let activity = match crate::models::builders::UserActivityBuilder::new() .activity_type(activity_type) .description(description) .category("user".to_string()) .importance(crate::models::user::ActivityImportance::Medium) .build() { Ok(activity) => activity, Err(e) => { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Save activity match user_service.add_user_activity(&user_email, activity) { Ok(_) => { Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } } } /// Get user preferences pub async fn get_user_preferences(session: Session) -> Result { let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) }; if let Some(persistent_data) = UserPersistence::load_user_data(&user_email) { Ok(ResponseBuilder::ok().json(persistent_data.user_preferences).build()) } else { Ok(ResponseBuilder::ok().json(serde_json::Value::Null).build()) } } /// Update user preferences pub async fn update_user_preferences( session: Session, preferences_data: web::Json ) -> Result { let user_service = match crate::services::user_service::UserService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) }; match user_service.update_user_preferences(&user_email, preferences_data.into_inner()) { Ok(_) => { Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } } } /// Get user's service bookings for user dashboard pub async fn get_user_service_bookings_api(session: Session) -> Result { // Get user email from session let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return Ok(ResponseBuilder::unauthorized() .json(serde_json::json!({ "error": "User not authenticated" })) .build()); } }; // Load user's service bookings from persistent storage let service_bookings = UserPersistence::get_user_service_bookings(&user_email); Ok(ResponseBuilder::ok() .json(serde_json::json!({ "bookings": service_bookings, "total": service_bookings.len() })) .build()) } /// Refresh slice calculations for resource_provider pub async fn refresh_slice_calculations(session: Session) -> Result { let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) }; let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; match resource_provider_service.refresh_all_slice_calculations(&user_email) { Ok(_) => { Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } } } /// Sync resource_provider nodes with ThreeFold Grid pub async fn sync_with_grid(session: Session) -> Result { // Check authentication if let Err(response) = Self::check_authentication(&session) { return Ok(response); } // Mock sync operation let sync_result = serde_json::json!({ "success": true, "message": "Successfully synced with ThreeFold Grid", "nodes_updated": 3, "new_nodes_found": 1, "sync_timestamp": chrono::Utc::now().to_rfc3339() }); Ok(ResponseBuilder::ok().json(sync_result).build()?) } /// Renders the embedded shopping cart page within dashboard pub async fn cart_section(tmpl: web::Data, session: Session) -> Result { // Check authentication if let Err(response) = Self::check_authentication(&session) { return Ok(response); } // Load user data let user = match Self::load_user_with_persistent_data(&session) { Some(user) => user, None => { return Ok(ResponseBuilder::redirect("/register").build()?); } }; // Prepare template context let mut context = tera::Context::new(); // Add required template variables let is_gitea_flow_active = get_app_config().is_gitea_enabled(); // Add user context for navbar authentication state if let Ok(Some(user_json)) = session.get::("user") { context.insert("user_json", &user_json); } context.insert("user", &user); context.insert("active_section", "cart"); context.insert("active_page", "cart"); context.insert("page_title", "Shopping Cart"); context.insert("gitea_enabled", &is_gitea_flow_active); // Inject currency display variables { let currency_service = match crate::services::currency::CurrencyService::builder().build() { Ok(svc) => svc, Err(_) => crate::services::currency::CurrencyService::new(), }; let display_currency = currency_service.get_user_preferred_currency(&session); let currency_symbol = currency_service .get_currency(&display_currency) .map(|c| c.symbol) .unwrap_or_else(|| "$".to_string()); context.insert("display_currency", &display_currency); context.insert("currency_symbol", ¤cy_symbol); } // Render template render_template(&tmpl, "dashboard/cart.html", &context) } /// Renders the embedded orders history page within dashboard pub async fn orders_section(tmpl: web::Data, session: Session) -> Result { // Check authentication if let Err(response) = Self::check_authentication(&session) { return Ok(response); } // Load user data let user = match Self::load_user_with_persistent_data(&session) { Some(user) => user, None => { return Ok(ResponseBuilder::redirect("/register").build()?); } }; // Prepare template context let mut context = tera::Context::new(); // Add required template variables let is_gitea_flow_active = get_app_config().is_gitea_enabled(); // Add user context for navbar authentication state if let Ok(Some(user_json)) = session.get::("user") { context.insert("user_json", &user_json); } context.insert("user", &user); context.insert("active_section", "orders"); context.insert("active_page", "orders"); context.insert("page_title", "Order History"); context.insert("gitea_enabled", &is_gitea_flow_active); // Inject currency display variables { let currency_service = match crate::services::currency::CurrencyService::builder().build() { Ok(svc) => svc, Err(_) => crate::services::currency::CurrencyService::new(), }; let display_currency = currency_service.get_user_preferred_currency(&session); let currency_symbol = currency_service .get_currency(&display_currency) .map(|c| c.symbol) .unwrap_or_else(|| "$".to_string()); context.insert("display_currency", &display_currency); context.insert("currency_symbol", ¤cy_symbol); } // Render template render_template(&tmpl, "dashboard/orders.html", &context) } /// Get slices for a specific node pub async fn get_node_slices( session: Session, path: web::Path ) -> Result { let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) }; let node_id = path.into_inner(); let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; match resource_provider_service.get_node_slices(&user_email, node_id) { Ok(slices) => { Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } } } /// API endpoint to validate grid nodes for automatic slice management pub async fn validate_grid_nodes_automatic(session: Session, form: web::Json) -> Result { let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } let node_ids: Vec = match form.get("node_ids").and_then(|v| v.as_array()) { Some(ids) => { ids.iter() .filter_map(|id| id.as_u64().map(|n| n as u32)) .collect() } _ => { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; if node_ids.is_empty() { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } // Initialize services let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; let slice_calculator = match crate::services::slice_calculator::SliceCalculatorService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Validate each node and calculate slices let mut validated_nodes = Vec::new(); let mut errors = Vec::new(); for node_id in &node_ids { match resource_provider_service.fetch_and_validate_grid_node(*node_id).await { Ok(node_data) => { // Calculate automatic slices let total_base_slices = slice_calculator.calculate_max_base_slices(&node_data.capacity); // Create a temporary FarmNode for slice calculation let temp_node = crate::models::user::FarmNode { id: format!("grid_node_{}", *node_id), name: format!("Grid Node {}", *node_id), location: node_data.location.clone(), status: crate::models::user::NodeStatus::Online, capacity: node_data.capacity.clone(), used_capacity: crate::models::user::NodeCapacity { cpu_cores: 0, memory_gb: 0, storage_gb: 0, ram_gb: 0, bandwidth_mbps: 0, ssd_storage_gb: 0, hdd_storage_gb: 0, }, uptime_percentage: 99.0, farming_start_date: chrono::Utc::now(), last_updated: chrono::Utc::now(), last_seen: Some(chrono::Utc::now()), health_score: 98.5, utilization_7_day_avg: 50.0, slice_formats_supported: vec!["1x1".to_string(), "2x1".to_string()], rental_options: None, earnings_today_usd: rust_decimal::Decimal::ZERO, region: if node_data.country.is_empty() { "Unknown".to_string() } else { node_data.country.clone() }, node_type: "MyceliumNode".to_string(), slice_formats: None, staking_options: None, availability_status: crate::models::user::NodeAvailabilityStatus::Available, grid_node_id: Some(node_id.to_string()), grid_data: Some(serde_json::to_value(node_data.clone()).unwrap_or_default()), node_group_id: None, group_assignment_date: None, group_slice_format: None, group_slice_price: None, // NEW: Marketplace SLA field (None for temp node) marketplace_sla: None, total_base_slices: total_base_slices as i32, allocated_base_slices: 0, slice_allocations: Vec::new(), available_combinations: Vec::new(), slice_pricing: Some(serde_json::to_value(crate::services::slice_calculator::SlicePricing::default()).unwrap_or_default()), slice_last_calculated: Some(chrono::Utc::now()), }; let available_combinations = slice_calculator.generate_slice_combinations( total_base_slices, 0, // No allocated slices yet &temp_node, &user_email ); validated_nodes.push(serde_json::json!({ "grid_node_id": *node_id, "name": format!("Grid Node {}", *node_id), "location": format!("{}, {}", if node_data.city.is_empty() { "Unknown City" } else { &node_data.city }, if node_data.country.is_empty() { "Unknown Country" } else { &node_data.country } ), "status": "Online", "capacity": node_data.total_resources, "total_base_slices": total_base_slices, "available_combinations": available_combinations })); } Err(e) => { errors.push(serde_json::json!({ "node_id": node_id, "error": e.to_string() })); // Only log as debug to avoid confusing users with error messages when operation succeeds } } } // Determine success based on whether any nodes were validated let success = !validated_nodes.is_empty(); Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": success, "nodes": validated_nodes, "errors": errors, "total_requested": node_ids.len(), "total_valid": validated_nodes.len(), "partial_success": success && !errors.is_empty() })).build()) } /// API endpoint to add nodes with automatic slice management pub async fn add_nodes_automatic(session: Session, form: web::Json) -> Result { let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } // Parse form data let node_ids: Vec = match form.get("node_ids").and_then(|v| v.as_array()) { Some(ids) => { ids.iter() .filter_map(|id| id.as_u64().map(|n| n as u32)) .collect() } _ => { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; let base_slice_price = form.get("base_slice_price") .and_then(|v| v.as_f64()) .map(|p| rust_decimal::Decimal::from_f64_retain(p).unwrap_or(rust_decimal::Decimal::new(50, 2))) .unwrap_or(rust_decimal::Decimal::new(50, 2)); // Default $0.50 USD let node_uptime_sla = form.get("node_uptime_sla") .and_then(|v| v.as_f64()) .unwrap_or(99.8) as f32; let node_bandwidth_sla = form.get("node_bandwidth_sla") .and_then(|v| v.as_i64()) .unwrap_or(100) as i32; let node_group = form.get("node_group") .and_then(|v| v.as_str()) .filter(|s| !s.is_empty()) .map(|s| s.to_string()); let enable_staking = form.get("enable_staking") .and_then(|v| v.as_bool()) .unwrap_or(false); let enable_full_node_rental = form.get("enable_full_node_rental") .and_then(|v| v.as_bool()) .unwrap_or(false); // Validate pricing range if base_slice_price < rust_decimal::Decimal::new(10, 2) || base_slice_price > rust_decimal::Decimal::new(200, 2) { return Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } // Initialize resource_provider service let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Add nodes with automatic slice management match resource_provider_service.add_multiple_grid_nodes_with_automatic_slices( &user_email, node_ids.clone(), base_slice_price, node_uptime_sla, node_bandwidth_sla, node_group, enable_staking, enable_full_node_rental, ).await { Ok(added_nodes) => { // Add a small delay to ensure all backend operations are fully complete actix_web::rt::time::sleep(std::time::Duration::from_millis(200)).await; // Create response with detailed information let response = serde_json::json!({ "success": true, "message": format!("Successfully added {} nodes with automatic slice management", added_nodes.len()), "nodes_added": added_nodes.len(), "added_nodes": added_nodes, "timestamp": chrono::Utc::now().to_rfc3339(), "user_email": user_email }); Ok(ResponseBuilder::ok().json(response).build()) } Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Failed to add nodes with automatic slice management", "details": e.to_string(), "timestamp": chrono::Utc::now().to_rfc3339() })).build()) } } } /// API endpoint to refresh slice calculations for all resource_provider nodes pub async fn refresh_slice_calculations_api(session: Session) -> Result { let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } // Initialize resource_provider service let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Refresh slice calculations for all nodes match resource_provider_service.refresh_all_slice_calculations_async(&user_email).await { Ok(updated_nodes) => { Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": format!("Successfully refreshed slice calculations for {} nodes", updated_nodes), "updated_nodes": updated_nodes })).build()) } Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to refresh slice calculations", "details": e.to_string() })).build()) } } } /// API endpoint to sync with ThreeFold Grid pub async fn sync_with_grid_api(session: Session) -> Result { let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } // Initialize resource_provider service let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Sync all nodes with grid match resource_provider_service.sync_all_nodes_with_grid_async(&user_email).await { Ok(synced_nodes) => { Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": format!("Successfully synced {} nodes with ThreeFold Grid", synced_nodes), "synced_nodes": synced_nodes })).build()) } Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to sync with ThreeFold Grid", "details": e.to_string() })).build()) } } } /// API endpoint to get node slice details pub async fn get_node_slices_api(session: Session, path: web::Path) -> Result { let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } let node_id = path.into_inner(); // Initialize resource_provider service let resource_provider_service = match crate::services::resource_provider::ResourceProviderService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Get node slice details match resource_provider_service.get_node_slice_details(&user_email, &node_id) { Ok(slice_details) => { Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "error": "Failed to get node slice details", "details": e.to_string() })).build()) } } } /// API endpoint to get user's slice rentals pub async fn get_user_slice_rentals(session: Session) -> Result { let user_email = match session.get::("user_email")? { Some(email) => email, None => return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) }; // Build slice rental service let slice_rental_service = match crate::services::slice_rental::SliceRentalService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Get user's slice rentals let slice_rentals = slice_rental_service.get_user_slice_rentals(&user_email); // Transform rentals for frontend display let rental_data: Vec = slice_rentals.iter().map(|rental| { serde_json::json!({ "id": rental.rental_id, "slice_combination_id": rental.slice_combination_id, "deployment_type": rental.deployment_type.as_ref().unwrap_or(&"vm".to_string()), "deployment_name": rental.deployment_name.as_ref().unwrap_or(&format!("Deployment-{}", &rental.rental_id[..8])), "deployment_status": rental.deployment_status.as_ref().unwrap_or(&"provisioning".to_string()), "deployment_endpoint": rental.deployment_endpoint, "specs": format!("{} vCPU, {}GB RAM, {}GB Storage", rental.slice_allocation.base_slices_used, rental.slice_allocation.base_slices_used * 4, rental.slice_allocation.base_slices_used * 200 ), "monthly_cost": rental.slice_allocation.monthly_cost, "status": match rental.slice_allocation.status { crate::services::slice_calculator::AllocationStatus::Active => "active", crate::services::slice_calculator::AllocationStatus::Expired => "expired", crate::services::slice_calculator::AllocationStatus::Cancelled => "cancelled", }, "created_at": rental.slice_allocation.rental_start.format("%Y-%m-%d %H:%M:%S").to_string(), "expires_at": rental.slice_allocation.rental_end .map(|dt| dt.format("%Y-%m-%d %H:%M:%S").to_string()) .unwrap_or_else(|| "Never".to_string()), "metadata": rental.deployment_metadata }) }).collect(); Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } /// API endpoint to manage slice rental deployments (start/stop/restart) pub async fn manage_slice_rental_deployment( session: Session, path: web::Path, form: web::Json ) -> Result { let rental_id = path.into_inner(); let user_email = match session.get::("user_email")? { Some(email) => email, None => return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) }; let action = form.get("action").and_then(|v| v.as_str()).unwrap_or(""); // Build slice rental service let slice_rental_service = match crate::services::slice_rental::SliceRentalService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Get user's slice rentals to verify ownership let slice_rentals = slice_rental_service.get_user_slice_rentals(&user_email); let rental = slice_rentals.iter().find(|r| r.rental_id == rental_id); if rental.is_none() { return Ok(ResponseBuilder::not_found().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } match action { "start" => { // Add activity record let activity = crate::models::builders::UserActivityBuilder::new() .activity_type(crate::models::user::ActivityType::SliceRentalStarted) .description(format!("Started deployment for slice rental: {}", rental_id)) .category("Slice Rental".to_string()) .importance(crate::models::user::ActivityImportance::Medium) .build() .unwrap(); let _ = crate::services::user_persistence::UserPersistence::add_user_activity(&user_email, activity); Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": format!("Deployment started for slice rental: {}", rental_id), "status": "starting" })).build()) } "stop" => { // Add activity record let activity = crate::models::builders::UserActivityBuilder::new() .activity_type(crate::models::user::ActivityType::SliceRentalStopped) .description(format!("Stopped deployment for slice rental: {}", rental_id)) .category("Slice Rental".to_string()) .importance(crate::models::user::ActivityImportance::Medium) .build() .unwrap(); let _ = crate::services::user_persistence::UserPersistence::add_user_activity(&user_email, activity); Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": format!("Deployment stopped for slice rental: {}", rental_id), "status": "stopped" })).build()) } "restart" => { // Add activity record let activity = crate::models::builders::UserActivityBuilder::new() .activity_type(crate::models::user::ActivityType::SliceRentalRestarted) .description(format!("Restarted deployment for slice rental: {}", rental_id)) .category("Slice Rental".to_string()) .importance(crate::models::user::ActivityImportance::Medium) .build() .unwrap(); let _ = crate::services::user_persistence::UserPersistence::add_user_activity(&user_email, activity); Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": format!("Deployment restarted for slice rental: {}", rental_id), "status": "restarting" })).build()) } _ => { Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } } } /// API endpoint to cancel/terminate a slice rental pub async fn cancel_slice_rental( session: Session, path: web::Path ) -> Result { let rental_id = path.into_inner(); let user_email = match session.get::("user_email")? { Some(email) => email, None => return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) }; // Build slice rental service let slice_rental_service = match crate::services::slice_rental::SliceRentalService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Get user's slice rentals to verify ownership let slice_rentals = slice_rental_service.get_user_slice_rentals(&user_email); let rental = slice_rentals.iter().find(|r| r.rental_id == rental_id); if rental.is_none() { return Ok(ResponseBuilder::not_found().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } // In a real implementation, this would: // 1. Stop all deployments on the slice // 2. Release the slice allocation // 3. Update the rental status to cancelled // 4. Process any refunds if applicable // 5. Remove from user's active rentals // Add activity record let activity = crate::models::builders::UserActivityBuilder::new() .activity_type(crate::models::user::ActivityType::SliceRentalCancelled) .description(format!("Cancelled slice rental: {}", rental_id)) .category("Slice Rental".to_string()) .importance(crate::models::user::ActivityImportance::High) .build() .unwrap(); let _ = crate::services::user_persistence::UserPersistence::add_user_activity(&user_email, activity); Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": format!("Slice rental {} has been cancelled successfully", rental_id) })).build()) } /// Create a new product/application from service provider dashboard pub async fn create_product(session: Session, product_data: web::Json) -> Result { // Check authentication if let Err(response) = Self::check_authentication(&session) { return Ok(response); } let user_email = session.get::("user_email").unwrap_or_default().unwrap_or_default(); // Parse product data from JSON let name = product_data.get("name") .and_then(|v| v.as_str()) .unwrap_or("Unnamed Application") .to_string(); let description = product_data.get("description") .and_then(|v| v.as_str()) .unwrap_or("No description provided") .to_string(); let category_id = product_data.get("category") .and_then(|v| v.as_str()) .unwrap_or("service") .to_string(); let base_price = product_data.get("price") .and_then(|v| v.as_f64()) .map(|p| rust_decimal::Decimal::from_f64_retain(p).unwrap_or(rust_decimal::Decimal::new(0, 0))) .unwrap_or(rust_decimal::Decimal::new(0, 0)); let base_currency = product_data.get("currency") .and_then(|v| v.as_str()) .unwrap_or("USD") .to_string(); // Get provider name from user data let user = Self::load_user_with_persistent_data(&session); let provider_name = user.as_ref().map(|u| u.name.clone()).unwrap_or_else(|| user_email.clone()); // Create product ID let product_id = format!("user_{}_{}", user_email.replace("@", "_").replace(".", "_"), uuid::Uuid::new_v4().to_string()[0..8].to_string() ); // Create product attributes map let mut attributes = std::collections::HashMap::new(); // Add any additional attributes from the request if let Some(attrs) = product_data.get("attributes").and_then(|v| v.as_object()) { for (key, value) in attrs { attributes.insert(key.clone(), crate::models::product::ProductAttribute { key: key.clone(), value: value.clone(), attribute_type: crate::models::product::AttributeType::Text, is_searchable: true, is_filterable: false, display_order: None, }); } } // Create availability from status let availability = match product_data.get("availability") .and_then(|v| v.as_str()) .unwrap_or("Available") { "Available" => crate::models::product::ProductAvailability::Available, "Limited" => crate::models::product::ProductAvailability::Limited, "Unavailable" => crate::models::product::ProductAvailability::Unavailable, "PreOrder" => crate::models::product::ProductAvailability::PreOrder, other => crate::models::product::ProductAvailability::Custom(other.to_string()), }; // Create product metadata let metadata = crate::models::product::ProductMetadata { tags: product_data.get("tags") .and_then(|v| v.as_array()) .map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect()) .unwrap_or_default(), location: product_data.get("location").and_then(|v| v.as_str()).map(String::from), rating: None, review_count: 0, featured: false, last_updated: chrono::Utc::now(), visibility: crate::models::product::ProductVisibility::Public, seo_keywords: Vec::new(), custom_fields: std::collections::HashMap::new(), }; // Create the product let product = crate::models::product::Product { id: product_id, name, category_id, description, base_price, base_currency, attributes, provider_id: user_email.clone(), provider_name, availability, metadata, created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), }; // Save the product to user's persistent data match crate::services::user_persistence::UserPersistence::add_user_product(&user_email, product.clone()) { Ok(_) => { ResponseBuilder::ok().status(201).json(serde_json::json!({ "success": true, "message": "Product created successfully", "product": product })).build() } Err(e) => { ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Failed to create product", "details": format!("{}", e) })).build() } } } /// Get all products for the authenticated user pub async fn get_user_products(session: Session) -> Result { // Check authentication if let Err(response) = Self::check_authentication(&session) { return Ok(response); } let user_email = session.get::("user_email").unwrap_or_default().unwrap_or_default(); let products = crate::services::user_persistence::UserPersistence::get_user_products(&user_email); ResponseBuilder::ok().json(serde_json::json!({ "success": true, "products": products })).build() } } /// Form data structures for settings updates #[derive(Debug, Deserialize)] pub struct ProfileUpdateForm { pub name: String, pub country: String, pub timezone: String, } #[derive(Debug, Deserialize)] pub struct PasswordUpdateForm { pub current_password: String, pub new_password: String, pub confirm_password: String, } #[derive(Debug, Deserialize)] pub struct NotificationUpdateForm { pub email_security_alerts: bool, pub email_billing_alerts: bool, pub email_system_alerts: bool, pub email_newsletter: bool, pub dashboard_alerts: bool, pub dashboard_updates: bool, } #[derive(Debug, Deserialize)] pub struct AddTfpForm { pub amount: i32, } #[derive(Debug, Deserialize)] pub struct VerifyPasswordForm { pub password: String, } #[derive(Debug, Deserialize)] pub struct DeleteAccountForm { pub confirmation: String, pub password: String, } /// API endpoint for user dashboard data pub async fn user_dashboard_data_api(session: Session) -> Result { let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Build user service let user_service = match crate::services::user_service::UserService::builder().build() { Ok(service) => service, Err(e) => { return Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; // Load comprehensive user dashboard data let user_applications = user_service.get_user_applications(&user_email); let user_services = user_service.get_user_purchased_services(&user_email); let compute_resources = user_service.get_user_compute_resources(&user_email); let recent_activities = user_service.get_user_activities(&user_email, Some(20)); let user_metrics = user_service.calculate_user_metrics(&user_email); let usage_statistics = user_service.get_usage_statistics(&user_email); // Load slice rental data let slice_rental_data = if let Ok(slice_rental_service) = crate::services::slice_rental::SliceRentalService::builder().build() { let user_slice_rentals = slice_rental_service.get_user_slice_rentals(&user_email); // Calculate deployment type breakdown let mut vm_count = 0; let k8s_count = 0; let mut total_monthly_cost = rust_decimal::Decimal::ZERO; for rental in &user_slice_rentals { total_monthly_cost += rental.slice_allocation.monthly_cost; // For now, we'll assume all rentals are VM deployments // In a real implementation, this would be stored in the rental metadata vm_count += 1; } serde_json::json!({ "active_rentals": user_slice_rentals, "stats": { "total_active_rentals": user_slice_rentals.len(), "total_monthly_cost": total_monthly_cost, "vm_deployments": vm_count, "kubernetes_deployments": k8s_count } }) } else { serde_json::json!({ "active_rentals": [], "stats": { "total_active_rentals": 0, "total_monthly_cost": 0, "vm_deployments": 0, "kubernetes_deployments": 0 } }) }; // Prepare comprehensive response let response_data = serde_json::json!({ "applications": user_applications, "services": user_services, "compute_resources": compute_resources, "recent_activities": recent_activities, "user_metrics": user_metrics, "usage_statistics": usage_statistics, "slice_rentals": slice_rental_data, "usd_usage_trend": user_metrics.cost_trend, "resource_utilization": user_metrics.resource_utilization, "user_activity": { "deployments": [2, 3, 1, 4, 2, 3], // Mock data for chart "resource_reservations": [1, 2, 2, 3, 1, 2] // Mock data for chart }, "deployment_distribution": { "regions": [ {"region": "Amsterdam", "apps": 2, "nodes": 1, "slices": 4}, {"region": "New York", "apps": 1, "nodes": 0, "slices": 2}, {"region": "Singapore", "apps": 0, "nodes": 1, "slices": 3} ] } }); Ok(ResponseBuilder::ok().json(response_data).build()) } /// API endpoint for managing user slice rentals pub async fn manage_slice_rental( path: web::Path, form: web::Json, session: Session, ) -> Result { let rental_id = path.into_inner(); let user_email = match session.get::("user_email") { Ok(Some(email)) => email, _ => { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } }; let action = form.get("action").and_then(|v| v.as_str()).unwrap_or(""); match action { "terminate" => { // Implement slice rental termination // Add user activity if let Ok(user_service) = crate::services::user_service::UserService::builder().build() { let activity = crate::models::user::UserActivity { id: Uuid::new_v4().to_string(), user_email: user_email.clone(), activity_type: crate::models::user::ActivityType::SliceReleased, description: format!("Terminated slice rental {}", rental_id), timestamp: Utc::now(), metadata: None, category: "Slice Management".to_string(), importance: crate::models::user::ActivityImportance::Medium, ip_address: None, user_agent: None, session_id: None, }; let _ = user_service.add_user_activity(&user_email, activity); } Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } "resize" => { let new_quantity = form.get("quantity").and_then(|v| v.as_u64()).unwrap_or(1) as u32; Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "message": format!("Slice rental resized to {} slices", new_quantity) })).build()) } _ => { Ok(ResponseBuilder::bad_request().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } } } /// API endpoint to cleanup duplicate nodes and fix data inconsistencies pub async fn cleanup_data(session: Session) -> Result { let user_email = session.get::("user_email") .unwrap_or_default() .unwrap_or_default(); if user_email.is_empty() { return Ok(ResponseBuilder::unauthorized().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()); } // Run data cleanup match DataCleanup::remove_duplicate_nodes(&user_email) { Ok(duplicates_removed) => { // Also validate and fix slice data let slice_fixes = DataCleanup::validate_slice_data(&user_email) .unwrap_or(false); Ok(ResponseBuilder::ok().json(serde_json::json!({ "success": true, "duplicates_removed": duplicates_removed, "slice_data_fixed": slice_fixes, "message": format!("Cleanup complete: {} duplicates removed", duplicates_removed) })).build()) } Err(e) => { Ok(ResponseBuilder::internal_error().json(serde_json::json!({ "success": false, "error": "Pattern needs manual fix" })).build()) } } } // ============================================================================= // SSH KEY MANAGEMENT ENDPOINTS // ============================================================================= impl DashboardController { /// Get all SSH keys for the authenticated user pub async fn get_ssh_keys(session: Session) -> Result { // Check authentication let user_email = match session.get::("user_email")? { Some(email) => email, None => { return Ok(ResponseBuilder::unauthorized() .message("Authentication required") .build()?); } }; // Build SSH key service let ssh_service = match crate::services::ssh_key_service::SSHKeyService::builder().build() { Ok(service) => service, Err(e) => { log::error!("Failed to build SSH key service: {}", e); return Ok(ResponseBuilder::internal_error() .message("Service unavailable") .build()?); } }; // Get user's SSH keys let req_id = uuid::Uuid::new_v4().to_string(); let ssh_keys = ssh_service.get_user_ssh_keys_async(&user_email, Some(&req_id)).await; Ok(ResponseBuilder::success() .data(serde_json::json!({ "ssh_keys": ssh_keys, "count": ssh_keys.len() })) .message("SSH keys retrieved successfully") .build()?) } /// Add a new SSH key for the authenticated user pub async fn add_ssh_key(session: Session, form: web::Json) -> Result { // Check authentication let user_email = match session.get::("user_email")? { Some(email) => email, None => { return Ok(ResponseBuilder::unauthorized() .message("Authentication required") .build()?); } }; // Extract form data let name = match form.get("name").and_then(|v| v.as_str()) { Some(name) if !name.trim().is_empty() => name.trim(), _ => { return Ok(ResponseBuilder::bad_request() .message("SSH key name is required") .build()?); } }; let public_key = match form.get("public_key").and_then(|v| v.as_str()) { Some(key) if !key.trim().is_empty() => key.trim(), _ => { return Ok(ResponseBuilder::bad_request() .message("SSH public key is required") .build()?); } }; let is_default = form.get("is_default") .and_then(|v| v.as_bool()) .unwrap_or(false); // Build SSH key service let ssh_service = match crate::services::ssh_key_service::SSHKeyService::builder().build() { Ok(service) => service, Err(e) => { log::error!("Failed to build SSH key service: {}", e); return Ok(ResponseBuilder::internal_error() .message("Service unavailable") .build()?); } }; // Add SSH key let req_id = uuid::Uuid::new_v4().to_string(); match ssh_service.add_ssh_key_async(&user_email, name, public_key, is_default, Some(&req_id)).await { Ok(ssh_key) => { log::info!("SSH key added successfully for user {}: {}", user_email, ssh_key.id); Ok(ResponseBuilder::success() .data(serde_json::json!({ "ssh_key": ssh_key })) .message("SSH key added successfully") .build()?) } Err(e) => { log::warn!("Failed to add SSH key for user {}: {}", user_email, e); Ok(ResponseBuilder::bad_request() .message(&e.to_string()) .build()?) } } } /// Update an SSH key (name or default status) pub async fn update_ssh_key(session: Session, path: web::Path, form: web::Json) -> Result { let key_id = path.into_inner(); // Check authentication let user_email = match session.get::("user_email")? { Some(email) => email, None => { return Ok(ResponseBuilder::unauthorized() .message("Authentication required") .build()?); } }; // Extract form data let name = form.get("name").and_then(|v| v.as_str()); let is_default = form.get("is_default").and_then(|v| v.as_bool()); if name.is_none() && is_default.is_none() { return Ok(ResponseBuilder::bad_request() .message("At least one field (name or is_default) must be provided") .build()?); } // Build SSH key service let ssh_service = match crate::services::ssh_key_service::SSHKeyService::builder().build() { Ok(service) => service, Err(e) => { log::error!("Failed to build SSH key service: {}", e); return Ok(ResponseBuilder::internal_error() .message("Service unavailable") .build()?); } }; // Update SSH key let req_id = uuid::Uuid::new_v4().to_string(); match ssh_service.update_ssh_key_async(&user_email, &key_id, name, is_default, Some(&req_id)).await { Ok(ssh_key) => { log::info!("SSH key updated successfully for user {}: {}", user_email, key_id); Ok(ResponseBuilder::success() .data(serde_json::json!({ "ssh_key": ssh_key })) .message("SSH key updated successfully") .build()?) } Err(e) => { log::warn!("Failed to update SSH key {} for user {}: {}", key_id, user_email, e); Ok(ResponseBuilder::bad_request() .message(&e.to_string()) .build()?) } } } /// Delete an SSH key pub async fn delete_ssh_key(session: Session, path: web::Path) -> Result { let key_id = path.into_inner(); // Check authentication let user_email = match session.get::("user_email")? { Some(email) => email, None => { return Ok(ResponseBuilder::unauthorized() .message("Authentication required") .build()?); } }; // Build SSH key service let ssh_service = match crate::services::ssh_key_service::SSHKeyService::builder().build() { Ok(service) => service, Err(e) => { log::error!("Failed to build SSH key service: {}", e); return Ok(ResponseBuilder::internal_error() .message("Service unavailable") .build()?); } }; // Delete SSH key let req_id = uuid::Uuid::new_v4().to_string(); match ssh_service.delete_ssh_key_async(&user_email, &key_id, Some(&req_id)).await { Ok(()) => { log::info!("SSH key deleted successfully for user {}: {}", user_email, key_id); Ok(ResponseBuilder::success() .message("SSH key deleted successfully") .build()?) } Err(e) => { log::warn!("Failed to delete SSH key {} for user {}: {}", key_id, user_email, e); Ok(ResponseBuilder::bad_request() .message(&e.to_string()) .build()?) } } } /// Set an SSH key as default pub async fn set_default_ssh_key(session: Session, path: web::Path) -> Result { let key_id = path.into_inner(); // Check authentication let user_email = match session.get::("user_email")? { Some(email) => email, None => { return Ok(ResponseBuilder::unauthorized() .message("Authentication required") .build()?); } }; // Build SSH key service let ssh_service = match crate::services::ssh_key_service::SSHKeyService::builder().build() { Ok(service) => service, Err(e) => { log::error!("Failed to build SSH key service: {}", e); return Ok(ResponseBuilder::internal_error() .message("Service unavailable") .build()?); } }; // Set SSH key as default let req_id = uuid::Uuid::new_v4().to_string(); match ssh_service.set_default_ssh_key_async(&user_email, &key_id, Some(&req_id)).await { Ok(()) => { log::info!("SSH key set as default for user {}: {}", user_email, key_id); Ok(ResponseBuilder::success() .message("SSH key set as default successfully") .build()?) } Err(e) => { log::warn!("Failed to set default SSH key {} for user {}: {}", key_id, user_email, e); Ok(ResponseBuilder::bad_request() .message(&e.to_string()) .build()?) } } } /// Get SSH key details by ID pub async fn get_ssh_key_details(session: Session, path: web::Path) -> Result { let key_id = path.into_inner(); // Check authentication let user_email = match session.get::("user_email")? { Some(email) => email, None => { return Ok(ResponseBuilder::unauthorized() .message("Authentication required") .build()?); } }; // Build SSH key service let ssh_service = match crate::services::ssh_key_service::SSHKeyService::builder().build() { Ok(service) => service, Err(e) => { log::error!("Failed to build SSH key service: {}", e); return Ok(ResponseBuilder::internal_error() .message("Service unavailable") .build()?); } }; // Get SSH key details let req_id = uuid::Uuid::new_v4().to_string(); match ssh_service.get_ssh_key_by_id_async(&user_email, &key_id, Some(&req_id)).await { Some(ssh_key) => { Ok(ResponseBuilder::success() .data(serde_json::json!({ "ssh_key": ssh_key })) .message("SSH key retrieved successfully") .build()?) } None => { Ok(ResponseBuilder::not_found() .message("SSH key not found") .build()?) } } } }