init projectmycelium

This commit is contained in:
mik-tf
2025-09-01 21:37:01 -04:00
commit b41efb0e99
319 changed files with 128160 additions and 0 deletions

View File

@@ -0,0 +1,437 @@
use crate::models::user::{UserActivity, UsageStatistics, UserPreferences, Transaction};
use crate::services::user_persistence::UserPersistence;
use rust_decimal::Decimal;
use chrono::{Utc, Datelike};
use std::collections::HashMap;
/// Configuration for UserService
#[derive(Debug, Clone)]
pub struct UserServiceConfig {
pub activity_limit: usize,
}
impl Default for UserServiceConfig {
fn default() -> Self {
Self {
activity_limit: 50,
}
}
}
/// Builder for UserService following established pattern
#[derive(Default)]
pub struct UserServiceBuilder {
activity_limit: Option<usize>,
}
impl UserServiceBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn activity_limit(mut self, limit: usize) -> Self {
self.activity_limit = Some(limit);
self
}
pub fn build(self) -> Result<UserService, String> {
let config = UserServiceConfig {
activity_limit: self.activity_limit.unwrap_or(50),
};
Ok(UserService { config })
}
}
/// Main UserService for managing user dashboard data
#[derive(Clone)]
pub struct UserService {
config: UserServiceConfig,
}
impl UserService {
/// Create a new builder for UserService
pub fn builder() -> UserServiceBuilder {
UserServiceBuilder::new()
}
/// Get user activities with optional limit
pub fn get_user_activities(&self, user_email: &str, limit: Option<usize>) -> Vec<UserActivity> {
if let Some(persistent_data) = UserPersistence::load_user_data(user_email) {
let mut activities = persistent_data.user_activities;
// Sort by timestamp (newest first)
activities.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
// Apply limit
let limit = limit.unwrap_or(self.config.activity_limit);
activities.truncate(limit);
activities
} else {
Vec::new()
}
}
/// Get user purchase history from transactions
pub fn get_purchase_history(&self, user_email: &str) -> Vec<Transaction> {
if let Some(persistent_data) = UserPersistence::load_user_data(user_email) {
let purchases: Vec<Transaction> = persistent_data.transactions
.into_iter()
.filter(|t| matches!(t.transaction_type, crate::models::user::TransactionType::Purchase { .. }))
.collect();
purchases
} else {
Vec::new()
}
}
/// Get or calculate usage statistics
pub fn get_usage_statistics(&self, user_email: &str) -> Option<UsageStatistics> {
if let Some(persistent_data) = UserPersistence::load_user_data(user_email) {
// Return existing statistics or calculate new ones
if let Some(stats) = persistent_data.usage_statistics {
Some(stats)
} else {
Some(self.calculate_usage_statistics(user_email, &persistent_data))
}
} else {
None
}
}
/// Get active deployments for user
pub fn get_active_deployments(&self, user_email: &str) -> Vec<crate::models::user::UserDeployment> {
if let Some(persistent_data) = UserPersistence::load_user_data(user_email) {
// Convert active product rentals to deployments
let deployments: Vec<crate::models::user::UserDeployment> = persistent_data.active_product_rentals
.into_iter()
.filter(|r| r.status == "Active")
.map(|rental| crate::models::user::UserDeployment {
id: rental.id,
app_name: rental.product_name,
status: crate::models::user::DeploymentStatus::Active,
cost_per_month: rental.monthly_cost,
deployed_at: chrono::DateTime::parse_from_rfc3339(&rental.start_date)
.map(|dt| dt.with_timezone(&Utc))
.unwrap_or_else(|_| Utc::now()),
provider: rental.provider_email,
region: rental.metadata.get("region")
.and_then(|v| v.as_str())
.unwrap_or("Unknown")
.to_string(),
resource_usage: rental.metadata.get("resource_usage")
.and_then(|v| serde_json::from_value(v.clone()).ok())
.unwrap_or_default(),
})
.collect();
deployments
} else {
Vec::new()
}
}
/// Get user's published services (for service-provider dashboard)
pub fn get_user_published_services(&self, user_email: &str) -> Vec<crate::models::user::Service> {
if let Some(persistent_data) = UserPersistence::load_user_data(user_email) {
persistent_data.services
} else {
Vec::new()
}
}
/// Get user's purchased services (for user dashboard) - derived from service bookings
pub fn get_user_purchased_services(&self, user_email: &str) -> Vec<crate::models::user::Service> {
if let Some(persistent_data) = UserPersistence::load_user_data(user_email) {
// Convert service bookings to service view for user dashboard
// IMPORTANT: Only show bookings where THIS user is the customer
let purchased_services: Vec<crate::models::user::Service> = persistent_data.service_bookings
.into_iter()
.filter(|b| b.customer_email == user_email)
.map(|booking| {
let hourly_rate = (booking.budget / rust_decimal::Decimal::from(booking.estimated_hours.unwrap_or(1).max(1))).to_string().parse::<i32>().unwrap_or(0);
crate::models::user::Service {
id: booking.service_id,
name: booking.service_name,
category: "Service".to_string(), // Default category for purchased services
description: booking.description.unwrap_or_else(|| "Service booking".to_string()),
price_usd: booking.budget,
hourly_rate_usd: Some(rust_decimal::Decimal::new(hourly_rate as i64, 0)),
availability: true,
created_at: chrono::DateTime::parse_from_rfc3339(&booking.requested_date)
.map(|dt| dt.with_timezone(&chrono::Utc))
.unwrap_or_else(|_| chrono::Utc::now()),
updated_at: chrono::DateTime::parse_from_rfc3339(&booking.requested_date)
.map(|dt| dt.with_timezone(&chrono::Utc))
.unwrap_or_else(|_| chrono::Utc::now()),
price_per_hour_usd: hourly_rate,
status: booking.status,
clients: 0, // Users don't see client count for purchased services
rating: 4.5, // Default rating
total_hours: booking.estimated_hours,
}
})
.collect();
purchased_services
} else {
Vec::new()
}
}
/// Get user's purchased applications (for user dashboard) - derived from deployments
pub fn get_user_applications(&self, user_email: &str) -> Vec<crate::models::user::PublishedApp> {
if let Some(persistent_data) = UserPersistence::load_user_data(user_email) {
// Convert app deployments to application view for user dashboard
// IMPORTANT: Only show deployments where THIS user is the customer
let purchased_apps: Vec<crate::models::user::PublishedApp> = persistent_data.app_deployments
.into_iter()
.filter(|d| d.status == "Active" && d.customer_email == user_email)
.map(|deployment| crate::models::user::PublishedApp {
id: deployment.app_id,
name: deployment.app_name,
description: Some("User-deployed application".to_string()),
category: "Application".to_string(), // Default category for purchased apps
version: "1.0.0".to_string(), // Default version
price_usd: rust_decimal::Decimal::ZERO,
deployment_count: 1,
status: deployment.status,
created_at: chrono::DateTime::parse_from_rfc3339(&deployment.created_at)
.map(|dt| dt.with_timezone(&chrono::Utc))
.unwrap_or_else(|_| chrono::Utc::now()),
updated_at: chrono::DateTime::parse_from_rfc3339(&deployment.last_updated)
.map(|dt| dt.with_timezone(&chrono::Utc))
.unwrap_or_else(|_| chrono::Utc::now()),
auto_scaling: Some(false),
auto_healing: deployment.auto_healing,
revenue_history: Vec::new(),
deployments: 1, // User has 1 deployment of this app
rating: 4.5, // Default rating
monthly_revenue_usd: rust_decimal::Decimal::ZERO, // Users don't earn revenue from purchased apps
last_updated: chrono::DateTime::parse_from_rfc3339(&deployment.last_updated)
.map(|dt| dt.with_timezone(&chrono::Utc))
.unwrap_or_else(|_| chrono::Utc::now()),
})
.collect();
purchased_apps
} else {
Vec::new()
}
}
/// Get user's active compute resources from rentals
pub fn get_user_compute_resources(&self, user_email: &str) -> Vec<crate::models::user::UserComputeResource> {
if let Some(persistent_data) = UserPersistence::load_user_data(user_email) {
let compute_resources: Vec<crate::models::user::UserComputeResource> = persistent_data.active_product_rentals
.into_iter()
.filter(|r| r.status == "Active")
.map(|rental| crate::models::user::UserComputeResource {
id: rental.product_name.clone(),
resource_type: rental.metadata.get("type")
.and_then(|v| v.as_str())
.unwrap_or("Unknown")
.to_string(),
specs: rental.metadata.get("specs")
.and_then(|v| v.as_str())
.unwrap_or("Unknown")
.to_string(),
location: rental.metadata.get("location")
.and_then(|v| v.as_str())
.unwrap_or("Unknown")
.to_string(),
status: rental.status.clone(),
sla: rental.metadata.get("sla")
.and_then(|v| v.as_str())
.unwrap_or("Unknown")
.to_string(),
monthly_cost: rental.monthly_cost,
provider: rental.provider_email,
resource_usage: rental.metadata.get("resource_usage")
.and_then(|v| serde_json::from_value(v.clone()).ok())
.unwrap_or_default(),
})
.collect();
compute_resources
} else {
Vec::new()
}
}
/// Calculate comprehensive user metrics
pub fn calculate_user_metrics(&self, user_email: &str) -> crate::models::user::UserMetrics {
if let Some(persistent_data) = UserPersistence::load_user_data(user_email) {
// Calculate total spent this month
let current_month = Utc::now().format("%Y-%m").to_string();
let total_spent_this_month: Decimal = persistent_data.transactions
.iter()
.filter(|t| {
t.timestamp.format("%Y-%m").to_string() == current_month &&
matches!(t.transaction_type, crate::models::user::TransactionType::Purchase { .. })
})
.map(|t| t.amount)
.sum();
// Count active deployments
let active_deployments_count = persistent_data.active_product_rentals
.iter()
.filter(|r| r.status == "Active")
.count() as i32;
// Calculate resource utilization from active rentals
let resource_utilization = self.calculate_resource_utilization(&persistent_data.active_product_rentals);
// Generate cost trend (last 6 months)
let cost_trend = self.calculate_cost_trend(&persistent_data.transactions);
crate::models::user::UserMetrics {
total_spent_this_month,
active_deployments_count,
resource_utilization,
cost_trend,
wallet_balance: persistent_data.wallet_balance_usd,
total_transactions: persistent_data.transactions.len() as i32,
}
} else {
crate::models::user::UserMetrics::default()
}
}
/// Add new user activity
pub fn add_user_activity(&self, user_email: &str, activity: UserActivity) -> Result<(), String> {
if let Some(mut persistent_data) = UserPersistence::load_user_data(user_email) {
persistent_data.user_activities.push(activity);
// Keep only recent activities (limit to prevent file bloat)
persistent_data.user_activities.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
persistent_data.user_activities.truncate(100);
UserPersistence::save_user_data(&persistent_data)
.map_err(|e| format!("Failed to save user activity: {}", e))
} else {
Err("User data not found".to_string())
}
}
/// Update user preferences
pub fn update_user_preferences(&self, user_email: &str, preferences: UserPreferences) -> Result<(), String> {
if let Some(mut persistent_data) = UserPersistence::load_user_data(user_email) {
persistent_data.user_preferences = Some(preferences);
UserPersistence::save_user_data(&persistent_data)
.map_err(|e| format!("Failed to save user preferences: {}", e))
} else {
Err("User data not found".to_string())
}
}
// Private helper methods
fn calculate_usage_statistics(&self, _user_email: &str, persistent_data: &crate::services::user_persistence::UserPersistentData) -> UsageStatistics {
let total_deployments = persistent_data.active_product_rentals.len() as i32;
let total_spent = persistent_data.transactions
.iter()
.filter(|t| matches!(t.transaction_type, crate::models::user::TransactionType::Purchase { .. }))
.map(|t| t.amount)
.sum();
// Calculate favorite categories from purchase history
let mut category_counts: HashMap<String, i32> = HashMap::new();
for rental in &persistent_data.active_product_rentals {
if let Some(category) = rental.metadata.get("category").and_then(|v| v.as_str()) {
*category_counts.entry(category.to_string()).or_insert(0) += 1;
}
}
let mut favorite_categories_vec: Vec<(String, i32)> = category_counts
.into_iter()
.collect();
favorite_categories_vec.sort_by(|a, b| b.1.cmp(&a.1));
let favorite_categories = favorite_categories_vec
.into_iter()
.take(5)
.map(|(category, _)| category)
.collect();
UsageStatistics {
cpu_usage: 15.0, // TODO: Calculate from actual deployments
memory_usage: 45.0, // TODO: Calculate from actual deployments
storage_usage: 60.0, // TODO: Calculate from actual deployments
network_usage: 25.0, // TODO: Calculate from actual deployments
total_deployments,
active_services: persistent_data.services.len() as i32,
total_spent,
favorite_categories,
usage_trends: Vec::new(), // TODO: Implement trend calculation
login_frequency: 3.5, // TODO: Calculate from activity log
preferred_regions: vec!["Amsterdam".to_string(), "New York".to_string()], // TODO: Calculate from deployments
account_age_days: 90, // TODO: Calculate from creation date
last_activity: chrono::Utc::now(),
}
}
fn calculate_resource_utilization(&self, rentals: &[crate::services::user_persistence::ProductRental]) -> crate::models::user::ResourceUtilization {
// Calculate average resource utilization across active rentals
if rentals.is_empty() {
return crate::models::user::ResourceUtilization {
cpu: 0,
memory: 0,
storage: 0,
network: 0,
};
}
let mut total_cpu = 0;
let mut total_memory = 0;
let mut total_storage = 0;
let mut total_network = 0;
let mut count = 0;
for rental in rentals {
if let Some(usage) = rental.metadata.get("resource_usage") {
if let Ok(usage) = serde_json::from_value::<crate::models::user::ResourceUtilization>(usage.clone()) {
total_cpu += usage.cpu;
total_memory += usage.memory;
total_storage += usage.storage;
total_network += usage.network;
count += 1;
}
}
}
if count > 0 {
crate::models::user::ResourceUtilization {
cpu: total_cpu / count,
memory: total_memory / count,
storage: total_storage / count,
network: total_network / count,
}
} else {
crate::models::user::ResourceUtilization {
cpu: 45, // Default reasonable values
memory: 60,
storage: 35,
network: 25,
}
}
}
fn calculate_cost_trend(&self, transactions: &[Transaction]) -> Vec<i32> {
// Calculate monthly spending for last 6 months
let mut monthly_costs = vec![0; 6];
let current_date = Utc::now();
for transaction in transactions {
if matches!(transaction.transaction_type, crate::models::user::TransactionType::Purchase { .. }) {
let months_ago = (current_date.year() * 12 + current_date.month() as i32) -
(transaction.timestamp.year() * 12 + transaction.timestamp.month() as i32);
if months_ago >= 0 && months_ago < 6 {
let index = (5 - months_ago) as usize;
if index < monthly_costs.len() {
monthly_costs[index] += transaction.amount.to_string().parse::<i32>().unwrap_or(0);
}
}
}
}
monthly_costs
}
}