init projectmycelium
This commit is contained in:
437
src/services/user_service.rs
Normal file
437
src/services/user_service.rs
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user