//! Builder patterns for all marketplace models //! This module provides a centralized, maintainable way to construct complex structs //! with sensible defaults and validation. use chrono::{DateTime, Utc}; use rust_decimal::Decimal; use rust_decimal_macros::dec; use serde_json::Value; use std::collections::HashMap; use super::{ user::{PublishedApp, DeploymentStat, ResourceUtilization, User, UserRole, ServiceBooking}, product::{Product, ProductAttribute, ProductAvailability, ProductMetadata}, order::{Order, OrderItem, OrderStatus, PaymentDetails, Address, PurchaseType}, }; use crate::services::user_persistence::AppDeployment; // ============================================================================= // USER MODEL BUILDERS // ============================================================================= #[derive(Default)] pub struct PublishedAppBuilder { id: Option, name: Option, category: Option, version: Option, status: Option, deployments: Option, rating: Option, monthly_revenue: Option, last_updated: Option, auto_healing: Option, } impl PublishedAppBuilder { pub fn new() -> Self { Self::default() } pub fn id(mut self, id: impl Into) -> Self { self.id = Some(id.into()); self } pub fn name(mut self, name: impl Into) -> Self { self.name = Some(name.into()); self } pub fn category(mut self, category: impl Into) -> Self { self.category = Some(category.into()); self } pub fn version(mut self, version: impl Into) -> Self { self.version = Some(version.into()); self } pub fn status(mut self, status: impl Into) -> Self { self.status = Some(status.into()); self } pub fn deployments(mut self, deployments: i32) -> Self { self.deployments = Some(deployments); self } pub fn rating(mut self, rating: f32) -> Self { self.rating = Some(rating); self } pub fn monthly_revenue_usd(mut self, revenue: i32) -> Self { self.monthly_revenue = Some(revenue); self } pub fn last_updated(mut self, updated: impl Into) -> Self { self.last_updated = Some(updated.into()); self } pub fn auto_healing(mut self, enabled: bool) -> Self { self.auto_healing = Some(enabled); self } pub fn build(self) -> Result { Ok(PublishedApp { id: self.id.ok_or("id is required")?, name: self.name.ok_or("name is required")?, description: None, category: self.category.ok_or("category is required")?, version: self.version.unwrap_or_else(|| "1.0.0".to_string()), price_usd: rust_decimal::Decimal::ZERO, deployment_count: self.deployments.unwrap_or(0), status: self.status.unwrap_or_else(|| "Active".to_string()), created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), auto_scaling: Some(false), revenue_history: Vec::new(), deployments: self.deployments.unwrap_or(0), rating: self.rating.unwrap_or(0.0), monthly_revenue_usd: rust_decimal::Decimal::from(self.monthly_revenue.unwrap_or(0)), last_updated: chrono::Utc::now(), // Use current time instead of string auto_healing: self.auto_healing.or(Some(true)), }) } } impl PublishedApp { pub fn builder() -> PublishedAppBuilder { PublishedAppBuilder::new() } // Template methods for common app types pub fn analytics_template(id: impl Into, name: impl Into) -> Self { Self::builder() .id(id) .name(name) .category("Analytics") .status("Active") .auto_healing(true) .build() .unwrap() } pub fn database_template(id: impl Into, name: impl Into) -> Self { Self::builder() .id(id) .name(name) .category("Database") .status("Active") .auto_healing(false) // Databases need manual intervention .build() .unwrap() } pub fn web_template(id: impl Into, name: impl Into) -> Self { Self::builder() .id(id) .name(name) .category("Web") .status("Active") .auto_healing(true) .build() .unwrap() } // Fluent methods for chaining pub fn with_stats(mut self, deployments: i32, rating: f32, monthly_revenue_usd: i32) -> Self { self.deployments = deployments; self.rating = rating; self.monthly_revenue_usd = rust_decimal::Decimal::from(monthly_revenue_usd); self } pub fn with_auto_healing(mut self, enabled: bool) -> Self { self.auto_healing = Some(enabled); self } pub fn with_version(mut self, version: impl Into) -> Self { self.version = version.into(); self } pub fn with_last_updated(mut self, updated: chrono::DateTime) -> Self { self.last_updated = updated; self } } #[derive(Default)] pub struct DeploymentStatBuilder { app_name: Option, region: Option, instances: Option, status: Option, resource_usage: Option, customer_name: Option, deployed_date: Option, deployment_id: Option, auto_healing: Option, active_instances: Option, total_instances: Option, avg_response_time_ms: Option, uptime_percentage: Option, last_deployment: Option>, } impl DeploymentStatBuilder { pub fn new() -> Self { Self::default() } pub fn app_name(mut self, name: impl Into) -> Self { self.app_name = Some(name.into()); self } pub fn region(mut self, region: impl Into) -> Self { self.region = Some(region.into()); self } pub fn instances(mut self, instances: i32) -> Self { self.instances = Some(instances); self } pub fn status(mut self, status: impl Into) -> Self { self.status = Some(status.into()); self } pub fn resource_usage(mut self, usage: ResourceUtilization) -> Self { self.resource_usage = Some(usage); self } pub fn customer_name(mut self, name: impl Into) -> Self { self.customer_name = Some(name.into()); self } pub fn deployed_date(mut self, date: impl Into) -> Self { self.deployed_date = Some(date.into()); self } pub fn deployment_id(mut self, id: impl Into) -> Self { self.deployment_id = Some(id.into()); self } pub fn auto_healing(mut self, enabled: bool) -> Self { self.auto_healing = Some(enabled); self } pub fn build(self) -> Result { Ok(DeploymentStat { app_name: self.app_name.ok_or("app_name is required")?, region: self.region.ok_or("region is required")?, instances: self.instances.unwrap_or(1), status: self.status.unwrap_or_else(|| "Active".to_string()), resource_usage: self.resource_usage .and_then(|usage| serde_json::to_string(&usage).ok()) .or_else(|| { let default_usage = ResourceUtilization { cpu: 50, memory: 50, storage: 50, network: 50, }; serde_json::to_string(&default_usage).ok() }), customer_name: self.customer_name, deployed_date: self.deployed_date.map(|_| chrono::Utc::now()), deployment_id: self.deployment_id, active_instances: self.active_instances.unwrap_or(1), total_instances: self.total_instances.unwrap_or(1), avg_response_time_ms: self.avg_response_time_ms, uptime_percentage: self.uptime_percentage, last_deployment: self.last_deployment, auto_healing: self.auto_healing.or(Some(true)), }) } } impl DeploymentStat { pub fn builder() -> DeploymentStatBuilder { DeploymentStatBuilder::new() } } #[derive(Default)] pub struct UserBuilder { id: Option, name: Option, email: Option, role: Option, country: Option, timezone: Option, created_at: Option>, updated_at: Option>, } impl UserBuilder { pub fn new() -> Self { Self::default() } pub fn id(mut self, id: i32) -> Self { self.id = Some(id); self } pub fn name(mut self, name: impl Into) -> Self { self.name = Some(name.into()); self } pub fn email(mut self, email: impl Into) -> Self { self.email = Some(email.into()); self } pub fn role(mut self, role: UserRole) -> Self { self.role = Some(role); self } pub fn country(mut self, country: impl Into) -> Self { self.country = Some(country.into()); self } pub fn timezone(mut self, timezone: impl Into) -> Self { self.timezone = Some(timezone.into()); self } pub fn build(self) -> Result { let now = Utc::now(); Ok(User { id: self.id, name: self.name.ok_or("name is required")?, email: self.email.ok_or("email is required")?, role: self.role.unwrap_or(UserRole::User), country: self.country, timezone: self.timezone, created_at: self.created_at.or(Some(now)), updated_at: self.updated_at.or(Some(now)), }) } } impl User { pub fn builder() -> UserBuilder { UserBuilder::new() } // Template methods for common user types pub fn new_user_template(name: impl Into, email: impl Into) -> Self { Self::builder() .name(name) .email(email) .role(UserRole::User) .build() .unwrap() } pub fn admin_template(name: impl Into, email: impl Into) -> Self { Self::builder() .name(name) .email(email) .role(UserRole::Admin) .build() .unwrap() } } #[derive(Default)] pub struct AppDeploymentBuilder { id: Option, app_id: Option, app_name: Option, customer_name: Option, customer_email: Option, deployed_date: Option, status: Option, health_score: Option, region: Option, instances: Option, resource_usage: Option, monthly_revenue: Option, last_updated: Option, auto_healing: Option, } impl AppDeploymentBuilder { pub fn new() -> Self { Self::default() } pub fn id(mut self, id: impl Into) -> Self { self.id = Some(id.into()); self } pub fn app_id(mut self, app_id: impl Into) -> Self { self.app_id = Some(app_id.into()); self } pub fn app_name(mut self, name: impl Into) -> Self { self.app_name = Some(name.into()); self } pub fn customer_name(mut self, name: impl Into) -> Self { self.customer_name = Some(name.into()); self } pub fn customer_email(mut self, email: impl Into) -> Self { self.customer_email = Some(email.into()); self } pub fn deployed_date(mut self, date: impl Into) -> Self { self.deployed_date = Some(date.into()); self } pub fn status(mut self, status: impl Into) -> Self { self.status = Some(status.into()); self } pub fn health_score(mut self, score: f32) -> Self { self.health_score = Some(score); self } pub fn region(mut self, region: impl Into) -> Self { self.region = Some(region.into()); self } pub fn instances(mut self, instances: i32) -> Self { self.instances = Some(instances); self } pub fn resource_usage(mut self, usage: ResourceUtilization) -> Self { self.resource_usage = Some(usage); self } pub fn monthly_revenue_usd(mut self, revenue: i32) -> Self { self.monthly_revenue = Some(revenue); self } pub fn last_updated(mut self, updated: impl Into) -> Self { self.last_updated = Some(updated.into()); self } pub fn auto_healing(mut self, enabled: bool) -> Self { self.auto_healing = Some(enabled); self } pub fn build(self) -> Result { Ok(AppDeployment { id: self.id.ok_or("id is required")?, app_id: self.app_id.ok_or("app_id is required")?, app_name: self.app_name.ok_or("app_name is required")?, customer_name: self.customer_name.ok_or("customer_name is required")?, customer_email: self.customer_email.ok_or("customer_email is required")?, deployed_date: self.deployed_date.unwrap_or_else(|| Utc::now().format("%Y-%m-%d").to_string()), created_at: Utc::now().format("%Y-%m-%d").to_string(), status: self.status.unwrap_or_else(|| "Active".to_string()), health_score: self.health_score.unwrap_or(98.0), region: self.region.unwrap_or_else(|| "Global".to_string()), instances: self.instances.unwrap_or(1), resource_usage: self.resource_usage.unwrap_or_else(|| ResourceUtilization { cpu: 25, memory: 30, storage: 15, network: 10, }), monthly_revenue: self.monthly_revenue.unwrap_or(0), last_updated: self.last_updated.unwrap_or_else(|| Utc::now().format("%Y-%m-%d %H:%M:%S").to_string()), auto_healing: self.auto_healing.or(Some(true)), }) } } impl AppDeployment { pub fn builder() -> AppDeploymentBuilder { AppDeploymentBuilder::new() } } // ============================================================================= // PRODUCT MODEL BUILDERS // ============================================================================= #[derive(Default)] pub struct ProductBuilder { id: Option, name: Option, category_id: Option, description: Option, base_price: Option, base_currency: Option, attributes: HashMap, provider_id: Option, provider_name: Option, availability: Option, metadata: Option, created_at: Option>, updated_at: Option>, } impl ProductBuilder { pub fn new() -> Self { Self::default() } pub fn id(mut self, id: impl Into) -> Self { self.id = Some(id.into()); self } pub fn name(mut self, name: impl Into) -> Self { self.name = Some(name.into()); self } pub fn category_id(mut self, category_id: impl Into) -> Self { self.category_id = Some(category_id.into()); self } pub fn description(mut self, description: impl Into) -> Self { self.description = Some(description.into()); self } pub fn base_price(mut self, price: Decimal) -> Self { self.base_price = Some(price); self } pub fn base_currency(mut self, currency: impl Into) -> Self { self.base_currency = Some(currency.into()); self } pub fn add_attribute(mut self, key: impl Into, attribute: ProductAttribute) -> Self { self.attributes.insert(key.into(), attribute); self } pub fn provider_id(mut self, provider_id: impl Into) -> Self { self.provider_id = Some(provider_id.into()); self } pub fn provider_name(mut self, provider_name: impl Into) -> Self { self.provider_name = Some(provider_name.into()); self } pub fn availability(mut self, availability: ProductAvailability) -> Self { self.availability = Some(availability); self } pub fn metadata(mut self, metadata: ProductMetadata) -> Self { self.metadata = Some(metadata); self } pub fn build(self) -> Result { let now = Utc::now(); Ok(Product { id: self.id.ok_or("id is required")?, name: self.name.ok_or("name is required")?, category_id: self.category_id.ok_or("category_id is required")?, description: self.description.unwrap_or_default(), base_price: self.base_price.ok_or("base_price is required")?, base_currency: self.base_currency.unwrap_or_else(|| "USD".to_string()), attributes: self.attributes, provider_id: self.provider_id.ok_or("provider_id is required")?, provider_name: self.provider_name.ok_or("provider_name is required")?, availability: self.availability.unwrap_or_default(), metadata: self.metadata.unwrap_or_default(), created_at: self.created_at.unwrap_or(now), updated_at: self.updated_at.unwrap_or(now), }) } } impl Product { pub fn builder() -> ProductBuilder { ProductBuilder::new() } } // ============================================================================= // ORDER MODEL BUILDERS // ============================================================================= #[derive(Default)] pub struct OrderBuilder { id: Option, user_id: Option, items: Vec, subtotal_base: Option, total_base: Option, base_currency: Option, currency_used: Option, currency_total: Option, conversion_rate: Option, status: Option, payment_method: Option, payment_details: Option, billing_address: Option
, shipping_address: Option
, notes: Option, purchase_type: Option, created_at: Option>, updated_at: Option>, } impl OrderBuilder { pub fn new() -> Self { Self::default() } pub fn id(mut self, id: impl Into) -> Self { self.id = Some(id.into()); self } pub fn user_id(mut self, user_id: impl Into) -> Self { self.user_id = Some(user_id.into()); self } pub fn add_item(mut self, item: OrderItem) -> Self { self.items.push(item); self } pub fn items(mut self, items: Vec) -> Self { self.items = items; self } pub fn subtotal_base(mut self, subtotal: Decimal) -> Self { self.subtotal_base = Some(subtotal); self } pub fn total_base(mut self, total: Decimal) -> Self { self.total_base = Some(total); self } pub fn base_currency(mut self, currency: impl Into) -> Self { self.base_currency = Some(currency.into()); self } pub fn currency_used(mut self, currency: impl Into) -> Self { self.currency_used = Some(currency.into()); self } pub fn currency_total(mut self, total: Decimal) -> Self { self.currency_total = Some(total); self } pub fn conversion_rate(mut self, rate: Decimal) -> Self { self.conversion_rate = Some(rate); self } pub fn status(mut self, status: OrderStatus) -> Self { self.status = Some(status); self } pub fn payment_method(mut self, method: impl Into) -> Self { self.payment_method = Some(method.into()); self } pub fn payment_details(mut self, details: PaymentDetails) -> Self { self.payment_details = Some(details); self } pub fn billing_address(mut self, address: Address) -> Self { self.billing_address = Some(address); self } pub fn shipping_address(mut self, address: Address) -> Self { self.shipping_address = Some(address); self } pub fn notes(mut self, notes: impl Into) -> Self { self.notes = Some(notes.into()); self } pub fn purchase_type(mut self, purchase_type: PurchaseType) -> Self { self.purchase_type = Some(purchase_type); self } pub fn build(self) -> Result { let now = Utc::now(); let subtotal = self.subtotal_base.unwrap_or_else(|| { self.items.iter().map(|item| item.total_price_base).sum() }); Ok(Order { id: self.id.ok_or("id is required")?, user_id: self.user_id.ok_or("user_id is required")?, items: self.items, subtotal_base: subtotal, total_base: self.total_base.unwrap_or(subtotal), base_currency: self.base_currency.unwrap_or_else(|| "USD".to_string()), currency_used: self.currency_used.unwrap_or_else(|| "USD".to_string()), currency_total: self.currency_total.unwrap_or(subtotal), conversion_rate: self.conversion_rate.unwrap_or_else(|| Decimal::from(1)), status: self.status.unwrap_or(OrderStatus::Pending), payment_method: self.payment_method.unwrap_or_else(|| "credit_card".to_string()), payment_details: self.payment_details, billing_address: self.billing_address, shipping_address: self.shipping_address, notes: self.notes, purchase_type: self.purchase_type.unwrap_or(PurchaseType::Cart), created_at: self.created_at.unwrap_or(now), updated_at: self.updated_at.unwrap_or(now), }) } } impl Order { pub fn builder() -> OrderBuilder { OrderBuilder::new() } } #[derive(Default)] pub struct OrderItemBuilder { product_id: Option, product_name: Option, product_category: Option, quantity: Option, unit_price_base: Option, total_price_base: Option, specifications: HashMap, provider_id: Option, provider_name: Option, } impl OrderItemBuilder { pub fn new() -> Self { Self::default() } pub fn product_id(mut self, id: impl Into) -> Self { self.product_id = Some(id.into()); self } pub fn product_name(mut self, name: impl Into) -> Self { self.product_name = Some(name.into()); self } pub fn product_category(mut self, category: impl Into) -> Self { self.product_category = Some(category.into()); self } pub fn quantity(mut self, quantity: u32) -> Self { self.quantity = Some(quantity); self } pub fn unit_price_base(mut self, price: Decimal) -> Self { self.unit_price_base = Some(price); self } pub fn add_specification(mut self, key: impl Into, value: Value) -> Self { self.specifications.insert(key.into(), value); self } pub fn provider_id(mut self, id: impl Into) -> Self { self.provider_id = Some(id.into()); self } pub fn provider_name(mut self, name: impl Into) -> Self { self.provider_name = Some(name.into()); self } pub fn build(self) -> Result { let quantity = self.quantity.unwrap_or(1); let unit_price = self.unit_price_base.ok_or("unit_price_base is required")?; let total_price = self.total_price_base.unwrap_or(unit_price * Decimal::from(quantity)); Ok(OrderItem { product_id: self.product_id.ok_or("product_id is required")?, product_name: self.product_name.ok_or("product_name is required")?, product_category: self.product_category.ok_or("product_category is required")?, quantity, unit_price_base: unit_price, total_price_base: total_price, specifications: self.specifications, provider_id: self.provider_id.ok_or("provider_id is required")?, provider_name: self.provider_name.ok_or("provider_name is required")?, }) } } impl OrderItem { pub fn builder() -> OrderItemBuilder { OrderItemBuilder::new() } } // ============================================================================= // SERVICE LAYER BUILDERS // ============================================================================= #[derive(Default)] pub struct CurrencyServiceBuilder { cache_duration_minutes: Option, base_currency: Option, display_currency: Option, auto_update: Option, fallback_rates: Option>, } impl CurrencyServiceBuilder { pub fn new() -> Self { Self::default() } pub fn cache_duration(mut self, minutes: u64) -> Self { self.cache_duration_minutes = Some(minutes); self } pub fn base_currency(mut self, currency: impl Into) -> Self { self.base_currency = Some(currency.into()); self } pub fn auto_update(mut self, enabled: bool) -> Self { self.auto_update = Some(enabled); self } pub fn fallback_rates(mut self, rates: HashMap) -> Self { self.fallback_rates = Some(rates); self } pub fn display_currency(mut self, currency: impl Into) -> Self { self.display_currency = Some(currency.into()); self } pub fn build(self) -> Result { let cache_duration = self.cache_duration_minutes.unwrap_or(60); if cache_duration == 0 { return Err("Cache duration must be greater than 0".to_string()); } let base_currency = self.base_currency.unwrap_or_else(|| "USD".to_string()); if base_currency.is_empty() { return Err("Base currency cannot be empty".to_string()); } let display_currency = self.display_currency.unwrap_or_else(|| "USD".to_string()); Ok(crate::services::currency::CurrencyService::new_with_display_config( cache_duration, base_currency, display_currency, self.auto_update.unwrap_or(true), self.fallback_rates.unwrap_or_default(), )) } } #[derive(Default)] pub struct ProductServiceBuilder { currency_service: Option, cache_enabled: Option, default_category: Option, include_slice_products: Option, } impl ProductServiceBuilder { pub fn new() -> Self { Self::default() } pub fn currency_service(mut self, service: crate::services::currency::CurrencyService) -> Self { self.currency_service = Some(service); self } pub fn cache_enabled(mut self, enabled: bool) -> Self { self.cache_enabled = Some(enabled); self } pub fn default_category(mut self, category: impl Into) -> Self { self.default_category = Some(category.into()); self } pub fn include_slice_products(mut self, include: bool) -> Self { self.include_slice_products = Some(include); self } pub fn build(self) -> Result { let currency_service = self.currency_service.unwrap_or_else(|| { CurrencyServiceBuilder::new() .build() .expect("Failed to create default currency service") }); if self.include_slice_products.unwrap_or(true) { Ok(crate::services::product::ProductService::new_with_slice_support( currency_service, true, )) } else { Ok(crate::services::product::ProductService::new_with_config( currency_service, self.cache_enabled.unwrap_or(true), self.default_category, )) } } } #[derive(Default)] pub struct OrderServiceBuilder { currency_service: Option, product_service: Option, auto_save: Option, } impl OrderServiceBuilder { pub fn new() -> Self { Self::default() } pub fn currency_service(mut self, service: crate::services::currency::CurrencyService) -> Self { self.currency_service = Some(service); self } pub fn product_service(mut self, service: crate::services::product::ProductService) -> Self { self.product_service = Some(service); self } pub fn auto_save(mut self, enabled: bool) -> Self { self.auto_save = Some(enabled); self } pub fn build(self) -> Result { let currency_service = self.currency_service.unwrap_or_else(|| { CurrencyServiceBuilder::new() .build() .expect("Failed to create default currency service") }); let product_service = self.product_service.unwrap_or_else(|| { ProductServiceBuilder::new() .currency_service(currency_service.clone()) .build() .expect("Failed to create default product service") }); Ok(crate::services::order::OrderService::new_with_config( currency_service, product_service, self.auto_save.unwrap_or(true), )) } } #[derive(Default)] pub struct PoolServiceBuilder { initial_pools: Option>, analytics_enabled: Option, } impl PoolServiceBuilder { pub fn new() -> Self { Self::default() } pub fn initial_pools(mut self, pools: HashMap) -> Self { self.initial_pools = Some(pools); self } pub fn analytics_enabled(mut self, enabled: bool) -> Self { self.analytics_enabled = Some(enabled); self } pub fn build(self) -> Result { Ok(crate::services::pool_service::PoolService::new_with_config( self.initial_pools.unwrap_or_default(), self.analytics_enabled.unwrap_or(true), )) } } // ============================================================================= // CONTEXT BUILDER FOR TEMPLATE RENDERING // ============================================================================= #[derive(Default)] pub struct ContextBuilder { active_page: Option, active_section: Option, gitea_enabled: Option, enable_mock_data: Option, user: Option, user_json: Option, custom_data: HashMap, } impl ContextBuilder { pub fn new() -> Self { Self::default() } pub fn active_page(mut self, page: impl Into) -> Self { self.active_page = Some(page.into()); self } pub fn active_section(mut self, section: impl Into) -> Self { self.active_section = Some(section.into()); self } pub fn gitea_enabled(mut self, enabled: bool) -> Self { self.gitea_enabled = Some(enabled); self } /// Controls whether mock data/UI should be available in templates pub fn enable_mock_data(mut self, enabled: bool) -> Self { self.enable_mock_data = Some(enabled); self } pub fn user(mut self, user: crate::models::user::User) -> Self { self.user = Some(user); self } pub fn user_json(mut self, json: impl Into) -> Self { self.user_json = Some(json.into()); self } pub fn custom(mut self, key: impl Into, value: serde_json::Value) -> Self { self.custom_data.insert(key.into(), value); self } pub fn user_if_available(mut self, session: &actix_session::Session) -> Self { if let Ok(Some(user_json)) = session.get::("user") { self.user_json = Some(user_json); } self } pub fn build(self) -> tera::Context { let mut ctx = tera::Context::new(); if let Some(page) = self.active_page { ctx.insert("active_page", &page); } if let Some(section) = self.active_section { ctx.insert("active_section", §ion); } if let Some(enabled) = self.gitea_enabled { ctx.insert("gitea_enabled", &enabled); } // Insert enable_mock_data: prefer explicit value from builder, // otherwise default from global app config let mocks_enabled = match self.enable_mock_data { Some(v) => v, None => crate::config::get_app_config().enable_mock_data(), }; ctx.insert("enable_mock_data", &mocks_enabled); if let Some(user) = self.user { ctx.insert("user", &user); } if let Some(user_json) = self.user_json { ctx.insert("user_json", &user_json); } for (key, value) in self.custom_data { ctx.insert(&key, &value); } ctx } } // ============================================================================= // CONFIGURATION BUILDERS // ============================================================================= #[derive(Default)] pub struct SessionDataBuilder { wallet_balance: Option, transactions: Option>, staked_amount: Option, pool_positions: Option>, name: Option, country: Option, timezone: Option, } impl SessionDataBuilder { pub fn new() -> Self { Self::default() } /// Create a new user with just email (most common case) pub fn new_user(email: impl Into) -> crate::services::user_persistence::UserPersistentData { let mut data = Self::new().build(); data.user_email = email.into(); data } /// Create a new user with email and balance pub fn new_user_with_balance(email: impl Into, balance: Decimal) -> crate::services::user_persistence::UserPersistentData { let mut data = Self::new().wallet_balance(balance).build(); data.user_email = email.into(); data } /// Load existing user or create new one (most common pattern) pub fn load_or_create(email: impl Into) -> crate::services::user_persistence::UserPersistentData { let email_str = email.into(); crate::services::user_persistence::UserPersistence::load_user_data(&email_str) .unwrap_or_else(|| Self::new_user(&email_str)) } /// Create user with email field set (internal helper) pub fn with_email(self, email: impl Into) -> Self { // We'll set email in build() method self } pub fn wallet_balance(mut self, balance: Decimal) -> Self { self.wallet_balance = Some(balance); self } pub fn transactions(mut self, transactions: Vec) -> Self { self.transactions = Some(transactions); self } pub fn staked_amount(mut self, amount: Decimal) -> Self { self.staked_amount = Some(amount); self } pub fn pool_positions(mut self, positions: HashMap) -> Self { self.pool_positions = Some(positions); self } pub fn name(mut self, name: impl Into) -> Self { self.name = Some(name.into()); self } pub fn country(mut self, country: impl Into) -> Self { self.country = Some(country.into()); self } pub fn timezone(mut self, timezone: impl Into) -> Self { self.timezone = Some(timezone.into()); self } pub fn build(self) -> crate::services::user_persistence::UserPersistentData { crate::services::user_persistence::UserPersistentData { user_email: String::new(), // Will be set by caller wallet_balance_usd: self.wallet_balance.unwrap_or_default(), transactions: self.transactions.unwrap_or_default(), staked_amount_usd: self.staked_amount.unwrap_or_default(), pool_positions: self.pool_positions.unwrap_or_default(), name: self.name, country: self.country, timezone: self.timezone, display_currency: Some("USD".to_string()), quick_topup_amounts: Some(vec![dec!(10), dec!(25), dec!(50), dec!(100)]), ..Default::default() } } } // MockDataBuilder removed - using persistent data only // ============================================================================= // RESOURCE_PROVIDER DATA BUILDER // ============================================================================= #[derive(Default)] pub struct ResourceProviderDataBuilder { total_nodes: Option, online_nodes: Option, total_capacity: Option, used_capacity: Option, monthly_earnings: Option, total_earnings: Option, uptime_percentage: Option, nodes: Option>, earnings_history: Option>, active_slices: Option, } impl ResourceProviderDataBuilder { pub fn new() -> Self { Self::default() } pub fn total_nodes(mut self, total_nodes: i32) -> Self { self.total_nodes = Some(total_nodes); self } pub fn online_nodes(mut self, online_nodes: i32) -> Self { self.online_nodes = Some(online_nodes); self } pub fn total_capacity(mut self, capacity: crate::models::user::NodeCapacity) -> Self { self.total_capacity = Some(capacity); self } pub fn used_capacity(mut self, capacity: crate::models::user::NodeCapacity) -> Self { self.used_capacity = Some(capacity); self } pub fn monthly_earnings_usd(mut self, earnings: i32) -> Self { self.monthly_earnings = Some(earnings); self } pub fn total_earnings_usd(mut self, earnings: i32) -> Self { self.total_earnings = Some(earnings); self } pub fn uptime_percentage(mut self, uptime: f32) -> Self { self.uptime_percentage = Some(uptime); self } pub fn nodes(mut self, nodes: Vec) -> Self { self.nodes = Some(nodes); self } pub fn earnings_history(mut self, history: Vec) -> Self { self.earnings_history = Some(history); self } pub fn earnings(mut self, earnings: Vec) -> Self { self.earnings_history = Some(earnings); self } pub fn active_slices(mut self, active_slices: i32) -> Self { self.active_slices = Some(active_slices); self } pub fn calculate_totals(mut self) -> Self { // Calculate totals from existing data if let Some(ref nodes) = self.nodes { self.total_nodes = Some(nodes.len() as i32); self.online_nodes = Some(nodes.iter().filter(|n| matches!(n.status, crate::models::user::NodeStatus::Online)).count() as i32); // Calculate total and used capacity from all nodes let mut total_capacity = crate::models::user::NodeCapacity { cpu_cores: 0, memory_gb: 0, storage_gb: 0, bandwidth_mbps: 0, ssd_storage_gb: 0, hdd_storage_gb: 0, ram_gb: 0, }; let mut used_capacity = crate::models::user::NodeCapacity { cpu_cores: 0, memory_gb: 0, storage_gb: 0, bandwidth_mbps: 0, ssd_storage_gb: 0, hdd_storage_gb: 0, ram_gb: 0, }; for node in nodes { total_capacity.cpu_cores += node.capacity.cpu_cores; total_capacity.memory_gb += node.capacity.memory_gb; total_capacity.storage_gb += node.capacity.storage_gb; total_capacity.bandwidth_mbps += node.capacity.bandwidth_mbps; total_capacity.ssd_storage_gb += node.capacity.ssd_storage_gb; total_capacity.hdd_storage_gb += node.capacity.hdd_storage_gb; used_capacity.cpu_cores += node.used_capacity.cpu_cores; used_capacity.memory_gb += node.used_capacity.memory_gb; used_capacity.storage_gb += node.used_capacity.storage_gb; used_capacity.bandwidth_mbps += node.used_capacity.bandwidth_mbps; used_capacity.ssd_storage_gb += node.used_capacity.ssd_storage_gb; used_capacity.hdd_storage_gb += node.used_capacity.hdd_storage_gb; } self.total_capacity = Some(total_capacity); self.used_capacity = Some(used_capacity); // Calculate uptime percentage if !nodes.is_empty() { let avg_uptime = nodes.iter().map(|n| n.uptime_percentage).sum::() / nodes.len() as f32; self.uptime_percentage = Some(avg_uptime); } } if let Some(ref earnings) = self.earnings_history { let total: i32 = earnings.iter().map(|e| e.amount.to_string().parse::().unwrap_or(0)).sum(); self.total_earnings = Some(total); self.monthly_earnings = Some(total); // Set monthly earnings as well } self } pub fn build(self) -> Result { Ok(crate::models::user::ResourceProviderData { total_nodes: self.total_nodes.unwrap_or(0), online_nodes: self.online_nodes.unwrap_or(0), total_capacity: self.total_capacity.unwrap_or(crate::models::user::NodeCapacity { cpu_cores: 0, memory_gb: 0, storage_gb: 0, bandwidth_mbps: 0, ssd_storage_gb: 0, hdd_storage_gb: 0, ram_gb: 0, }), used_capacity: self.used_capacity.unwrap_or(crate::models::user::NodeCapacity { cpu_cores: 0, memory_gb: 0, storage_gb: 0, bandwidth_mbps: 0, ssd_storage_gb: 0, hdd_storage_gb: 0, ram_gb: 0, }), monthly_earnings_usd: rust_decimal::Decimal::from(self.monthly_earnings.unwrap_or(0)), total_earnings_usd: rust_decimal::Decimal::from(self.total_earnings.unwrap_or(0)), uptime_percentage: self.uptime_percentage.unwrap_or(0.0), nodes: self.nodes.unwrap_or_default().into_iter().map(|node| crate::models::user::NodeInfo { id: node.id, name: node.name, status: node.status, location: node.location, capacity: crate::models::user::NodeCapacity { cpu_cores: 8, memory_gb: 32, storage_gb: 1500, bandwidth_mbps: 1000, ssd_storage_gb: 500, hdd_storage_gb: 1000, ram_gb: 32, }, monthly_earnings_usd: 0, cpu_total: 8, memory_total_gb: 32, ssd_storage_gb: 500, hdd_storage_gb: 1000, cpu_used: 2, memory_used_gb: 8, ssd_used_gb: 100, hdd_used_gb: 200, uptime_percentage: 99.5, network_bandwidth_mbps: 1000, farming_start_date: chrono::Utc::now(), last_updated: chrono::Utc::now(), utilization_7_day_avg: 45.0, slice_formats_supported: vec!["small".to_string(), "medium".to_string()], slice_last_calculated: Some(chrono::Utc::now()), }).collect(), earnings_history: self.earnings_history.unwrap_or_default(), slice_templates: Vec::default(), // Will be populated separately active_slices: self.active_slices.unwrap_or(0), active_nodes: self.online_nodes.unwrap_or(0), revenue_history: Vec::new(), total_monthly_earnings_usd: self.monthly_earnings.unwrap_or(0), }) } } // ============================================================================= // SLICE PRODUCT BUILDER // ============================================================================= #[derive(Default)] pub struct SliceProductBuilder { resource_provider_id: Option, resource_provider_name: Option, slice_name: Option, cpu_cores: Option, memory_gb: Option, storage_gb: Option, bandwidth_mbps: Option, min_uptime_sla: Option, public_ips: Option, node_id: Option, slice_type: Option, price_per_hour: Option, } impl SliceProductBuilder { pub fn new() -> Self { Self::default() } pub fn resource_provider_id(mut self, resource_provider_id: impl Into) -> Self { self.resource_provider_id = Some(resource_provider_id.into()); self } pub fn resource_provider_name(mut self, resource_provider_name: impl Into) -> Self { self.resource_provider_name = Some(resource_provider_name.into()); self } pub fn slice_name(mut self, slice_name: impl Into) -> Self { self.slice_name = Some(slice_name.into()); self } pub fn cpu_cores(mut self, cpu_cores: i32) -> Self { self.cpu_cores = Some(cpu_cores); self } pub fn memory_gb(mut self, memory_gb: i32) -> Self { self.memory_gb = Some(memory_gb); self } pub fn storage_gb(mut self, storage_gb: i32) -> Self { self.storage_gb = Some(storage_gb); self } pub fn bandwidth_mbps(mut self, bandwidth_mbps: i32) -> Self { self.bandwidth_mbps = Some(bandwidth_mbps); self } pub fn min_uptime_sla(mut self, min_uptime_sla: f32) -> Self { self.min_uptime_sla = Some(min_uptime_sla); self } pub fn public_ips(mut self, public_ips: i32) -> Self { self.public_ips = Some(public_ips); self } pub fn node_id(mut self, node_id: impl Into) -> Self { self.node_id = Some(node_id.into()); self } pub fn slice_type(mut self, slice_type: crate::models::product::SliceType) -> Self { self.slice_type = Some(slice_type); self } pub fn price_per_hour(mut self, price_per_hour: rust_decimal::Decimal) -> Self { self.price_per_hour = Some(price_per_hour); self } pub fn build(self) -> Result { let resource_provider_id = self.resource_provider_id.ok_or("resource_provider_id is required")?; let resource_provider_name = self.resource_provider_name.ok_or("resource_provider_name is required")?; let slice_name = self.slice_name.ok_or("slice_name is required")?; let cpu_cores = self.cpu_cores.ok_or("cpu_cores is required")?; let memory_gb = self.memory_gb.ok_or("memory_gb is required")?; let storage_gb = self.storage_gb.ok_or("storage_gb is required")?; let bandwidth_mbps = self.bandwidth_mbps.ok_or("bandwidth_mbps is required")?; let price_per_hour = self.price_per_hour.ok_or("price_per_hour is required")?; let slice_config = crate::models::product::SliceConfiguration { cpu_cores, memory_gb, storage_gb, bandwidth_mbps, min_uptime_sla: self.min_uptime_sla.unwrap_or(99.0), public_ips: self.public_ips.unwrap_or(0), node_id: self.node_id, slice_type: self.slice_type.unwrap_or(crate::models::product::SliceType::Basic), 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 ), }; Ok(crate::models::product::Product::create_slice_product( resource_provider_id, resource_provider_name, slice_name, slice_config, price_per_hour, )) } } // ============================================================================= // FARM NODE BUILDER // ============================================================================= #[derive(Default)] pub struct FarmNodeBuilder { id: Option, name: Option, location: Option, status: Option, capacity: Option, used_capacity: Option, uptime_percentage: Option, farming_start_date: Option>, last_updated: Option>, utilization_7_day_avg: Option, slice_formats_supported: Option>, earnings_today: Option, last_seen: Option>, health_score: Option, region: Option, node_type: Option, rental_options: Option, availability_status: Option, grid_node_id: Option, grid_data: Option, node_group_id: Option, group_assignment_date: Option>, group_slice_format: Option, group_slice_price: Option, } impl FarmNodeBuilder { pub fn new() -> Self { Self::default() } pub fn id(mut self, id: impl Into) -> Self { self.id = Some(id.into()); self } pub fn name(mut self, name: impl Into) -> Self { self.name = Some(name.into()); self } pub fn location(mut self, location: impl Into) -> Self { self.location = Some(location.into()); self } pub fn status(mut self, status: crate::models::user::NodeStatus) -> Self { self.status = Some(status); self } pub fn capacity(mut self, capacity: crate::models::user::NodeCapacity) -> Self { self.capacity = Some(capacity); self } pub fn used_capacity(mut self, capacity: crate::models::user::NodeCapacity) -> Self { self.used_capacity = Some(capacity); self } pub fn uptime_percentage(mut self, uptime: f32) -> Self { self.uptime_percentage = Some(uptime); self } pub fn earnings_today_usd(mut self, earnings: rust_decimal::Decimal) -> Self { self.earnings_today = Some(earnings); self } pub fn grid_node_id(mut self, grid_node_id: u32) -> Self { self.grid_node_id = Some(grid_node_id); self } pub fn grid_data(mut self, grid_data: crate::models::user::GridNodeData) -> Self { self.grid_data = Some(grid_data); self } pub fn node_group_id(mut self, group_id: impl Into) -> Self { self.node_group_id = Some(group_id.into()); self } pub fn group_assignment_date(mut self, date: chrono::DateTime) -> Self { self.group_assignment_date = Some(date); self } pub fn group_slice_format(mut self, format: impl Into) -> Self { self.group_slice_format = Some(format.into()); self } pub fn group_slice_price(mut self, price: rust_decimal::Decimal) -> Self { self.group_slice_price = Some(price); self } pub fn last_seen(mut self, last_seen: chrono::DateTime) -> Self { self.last_seen = Some(last_seen); self } pub fn health_score(mut self, score: f32) -> Self { self.health_score = Some(score); self } pub fn region(mut self, region: impl Into) -> Self { self.region = Some(region.into()); self } pub fn node_type(mut self, node_type: impl Into) -> Self { self.node_type = Some(node_type.into()); self } pub fn rental_options(mut self, options: crate::models::user::NodeRentalOptions) -> Self { self.rental_options = Some(options); self } pub fn availability_status(mut self, status: crate::models::user::NodeAvailabilityStatus) -> Self { self.availability_status = Some(status); self } pub fn build(self) -> Result { Ok(crate::models::user::FarmNode { id: self.id.ok_or("id is required")?, name: self.name.unwrap_or_else(|| "Unnamed Node".to_string()), location: self.location.unwrap_or_else(|| "Unknown".to_string()), status: self.status.unwrap_or(crate::models::user::NodeStatus::Offline), capacity: self.capacity.unwrap_or(crate::models::user::NodeCapacity { cpu_cores: 0, memory_gb: 0, storage_gb: 0, bandwidth_mbps: 0, ssd_storage_gb: 0, hdd_storage_gb: 0, ram_gb: 0, }), used_capacity: self.used_capacity.unwrap_or(crate::models::user::NodeCapacity { cpu_cores: 0, memory_gb: 0, storage_gb: 0, bandwidth_mbps: 0, ssd_storage_gb: 0, hdd_storage_gb: 0, ram_gb: 0, }), uptime_percentage: self.uptime_percentage.unwrap_or(0.0), farming_start_date: self.farming_start_date.unwrap_or_else(|| chrono::Utc::now() - chrono::Duration::days(30)), last_updated: self.last_updated.unwrap_or_else(|| chrono::Utc::now()), utilization_7_day_avg: self.utilization_7_day_avg.unwrap_or(65.0), slice_formats_supported: self.slice_formats_supported.unwrap_or_else(|| vec!["1x1".to_string(), "2x2".to_string(), "4x4".to_string()]), earnings_today_usd: self.earnings_today.unwrap_or_else(|| rust_decimal_macros::dec!(0)), last_seen: Some(self.last_seen.unwrap_or_else(|| chrono::Utc::now())), health_score: self.health_score.unwrap_or(0.0), region: self.region.unwrap_or_else(|| "Unknown".to_string()), node_type: self.node_type.unwrap_or_else(|| "MyceliumNode".to_string()), slice_formats: None, rental_options: self.rental_options.map(|ro| serde_json::to_value(&ro).unwrap_or_default()), staking_options: None, availability_status: self.availability_status.unwrap_or_default(), grid_node_id: self.grid_node_id.map(|id| id.to_string()), grid_data: self.grid_data.map(|gd| serde_json::to_value(&gd).unwrap_or_default()), node_group_id: self.node_group_id, group_assignment_date: self.group_assignment_date, group_slice_format: self.group_slice_format, group_slice_price: self.group_slice_price, // NEW: Marketplace SLA field (None for builder) marketplace_sla: None, total_base_slices: 0, 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: None, }) } } // ============================================================================= // NODE GROUP BUILDERS // ============================================================================= #[derive(Default)] pub struct NodeGroupBuilder { id: Option, name: Option, description: Option, group_type: Option, node_ids: Option>, group_config: Option, created_at: Option>, updated_at: Option>, } impl NodeGroupBuilder { pub fn new() -> Self { Self::default() } pub fn id(mut self, id: impl Into) -> Self { self.id = Some(id.into()); self } pub fn name(mut self, name: impl Into) -> Self { self.name = Some(name.into()); self } pub fn description(mut self, description: impl Into) -> Self { self.description = Some(description.into()); self } pub fn group_type(mut self, group_type: crate::models::user::NodeGroupType) -> Self { self.group_type = Some(group_type); self } pub fn node_ids(mut self, node_ids: Vec) -> Self { self.node_ids = Some(node_ids); self } pub fn group_config(mut self, config: crate::models::user::NodeGroupConfig) -> Self { self.group_config = Some(config); self } pub fn created_at(mut self, created_at: chrono::DateTime) -> Self { self.created_at = Some(created_at); self } pub fn updated_at(mut self, updated_at: chrono::DateTime) -> Self { self.updated_at = Some(updated_at); self } /// Helper method to create a default group pub fn default_group(mut self, default_type: crate::models::user::DefaultGroupType) -> Self { self.id = Some(default_type.get_id().to_string()); self.name = Some(default_type.get_name().to_string()); self.description = Some(default_type.get_description().to_string()); self.group_type = Some(crate::models::user::NodeGroupType::Default(default_type.get_id().to_string())); self.group_config = Some(default_type.get_default_config()); self } /// Helper method to create a custom group pub fn custom_group(mut self, id: impl Into, name: impl Into) -> Self { self.id = Some(id.into()); self.name = Some(name.into()); self.group_type = Some(crate::models::user::NodeGroupType::Custom); self.group_config = Some(crate::models::user::NodeGroupConfig::default()); self } pub fn build(self) -> Result { Ok(crate::models::user::NodeGroup { id: self.id.ok_or("id is required")?, name: self.name.ok_or("name is required")?, description: self.description, group_type: self.group_type.ok_or("group_type is required")?, node_ids: self.node_ids.unwrap_or_default(), group_config: self.group_config.unwrap_or_default(), created_at: self.created_at.unwrap_or_else(|| chrono::Utc::now()), updated_at: self.updated_at.unwrap_or_else(|| chrono::Utc::now()), }) } } // ============================================================================= // GRID NODE DATA BUILDERS // ============================================================================= #[derive(Default)] pub struct GridNodeDataBuilder { grid_node_id: Option, city: Option, country: Option, farm_name: Option, farm_id: Option, public_ips: Option, total_resources: Option, used_resources: Option, certification_type: Option, farming_policy_id: Option, last_updated: Option>, } impl GridNodeDataBuilder { pub fn new() -> Self { Self::default() } pub fn grid_node_id(mut self, id: u32) -> Self { self.grid_node_id = Some(id); self } pub fn city(mut self, city: impl Into) -> Self { self.city = Some(city.into()); self } pub fn country(mut self, country: impl Into) -> Self { self.country = Some(country.into()); self } pub fn farm_name(mut self, farm_name: impl Into) -> Self { self.farm_name = Some(farm_name.into()); self } pub fn farm_id(mut self, farm_id: u32) -> Self { self.farm_id = Some(farm_id); self } pub fn public_ips(mut self, public_ips: u32) -> Self { self.public_ips = Some(public_ips); self } pub fn total_resources(mut self, resources: crate::models::user::NodeCapacity) -> Self { self.total_resources = Some(resources); self } pub fn used_resources(mut self, resources: crate::models::user::NodeCapacity) -> Self { self.used_resources = Some(resources); self } pub fn certification_type(mut self, cert_type: impl Into) -> Self { self.certification_type = Some(cert_type.into()); self } pub fn farming_policy_id(mut self, policy_id: u32) -> Self { self.farming_policy_id = Some(policy_id); self } pub fn last_updated(mut self, last_updated: chrono::DateTime) -> Self { self.last_updated = Some(last_updated); self } pub fn build(self) -> Result { Ok(crate::models::user::GridNodeData { node_id: self.grid_node_id.ok_or("grid_node_id is required")?, capacity: self.total_resources.clone().unwrap_or(crate::models::user::NodeCapacity { cpu_cores: 0, memory_gb: 0, storage_gb: 0, bandwidth_mbps: 0, ssd_storage_gb: 0, hdd_storage_gb: 0, ram_gb: 0, }), location: format!("{}, {}", self.city.as_deref().unwrap_or("Unknown"), self.country.as_deref().unwrap_or("Unknown")), status: "Online".to_string(), uptime: 99.5, grid_node_id: self.grid_node_id.ok_or("grid_node_id is required")?, city: self.city.unwrap_or_else(|| "Unknown".to_string()), country: self.country.unwrap_or_else(|| "Unknown".to_string()), farm_name: self.farm_name.unwrap_or_else(|| "Unknown".to_string()), farm_id: self.farm_id.unwrap_or(0), public_ips: self.public_ips.map(|ip_count| (0..ip_count).map(|i| format!("192.168.1.{}", 100 + i)).collect()).unwrap_or_default(), total_resources: self.total_resources.clone().unwrap_or(crate::models::user::NodeCapacity { cpu_cores: 0, memory_gb: 0, storage_gb: 0, bandwidth_mbps: 0, ssd_storage_gb: 0, hdd_storage_gb: 0, ram_gb: 0, }), used_resources: self.used_resources.unwrap_or(crate::models::user::NodeCapacity { cpu_cores: 0, memory_gb: 0, storage_gb: 0, bandwidth_mbps: 0, ssd_storage_gb: 0, hdd_storage_gb: 0, ram_gb: 0, }), certification_type: self.certification_type.unwrap_or_else(|| "DIY".to_string()), farming_policy_id: self.farming_policy_id.unwrap_or(0), last_updated: self.last_updated.unwrap_or_else(|| chrono::Utc::now()), }) } } // ============================================================================= // NODE RENTAL BUILDERS // ============================================================================= #[derive(Default)] pub struct NodeRentalBuilder { id: Option, node_id: Option, renter_email: Option, rental_type: Option, monthly_cost: Option, start_date: Option>, end_date: Option>, status: Option, auto_renewal: Option, payment_method: Option, metadata: HashMap, } impl NodeRentalBuilder { pub fn new() -> Self { Self::default() } pub fn id(mut self, id: impl Into) -> Self { self.id = Some(id.into()); self } pub fn node_id(mut self, node_id: impl Into) -> Self { self.node_id = Some(node_id.into()); self } pub fn renter_email(mut self, email: impl Into) -> Self { self.renter_email = Some(email.into()); self } pub fn rental_type(mut self, rental_type: crate::models::user::NodeRentalType) -> Self { self.rental_type = Some(rental_type); self } pub fn monthly_cost(mut self, cost: Decimal) -> Self { self.monthly_cost = Some(cost); self } pub fn start_date(mut self, date: DateTime) -> Self { self.start_date = Some(date); self } pub fn end_date(mut self, date: DateTime) -> Self { self.end_date = Some(date); self } pub fn status(mut self, status: crate::models::user::NodeRentalStatus) -> Self { self.status = Some(status); self } pub fn auto_renewal(mut self, enabled: bool) -> Self { self.auto_renewal = Some(enabled); self } pub fn payment_method(mut self, method: impl Into) -> Self { self.payment_method = Some(method.into()); self } pub fn metadata(mut self, key: impl Into, value: Value) -> Self { self.metadata.insert(key.into(), value); self } pub fn build(self) -> Result { let id = self.id.unwrap_or_else(|| format!("rental_{}", uuid::Uuid::new_v4())); Ok(crate::models::user::NodeRental { rental_id: id.clone(), customer_email: self.renter_email.as_ref().ok_or("renter_email is required")?.clone(), node_id: self.node_id.ok_or("node_id is required")?, rental_type: self.rental_type.ok_or("rental_type is required")?, monthly_cost: self.monthly_cost.ok_or("monthly_cost is required")?, start_date: self.start_date.unwrap_or_else(Utc::now), end_date: self.end_date.ok_or("end_date is required")?, auto_renewal: self.auto_renewal.unwrap_or(false), payment_status: crate::models::user::PaymentStatus::Pending, sla_id: None, custom_configuration: None, metadata: self.metadata, id, status: self.status.unwrap_or(crate::models::user::NodeRentalStatus::Pending), renter_email: self.renter_email.ok_or("renter_email is required")?, payment_method: self.payment_method.unwrap_or_else(|| "USD".to_string()), }) } } #[derive(Default)] pub struct ResourceProviderRentalEarningBuilder { id: Option, node_id: Option, rental_id: Option, renter_email: Option, amount: Option, currency: Option, earning_date: Option>, rental_type: Option, payment_status: Option, } impl ResourceProviderRentalEarningBuilder { pub fn new() -> Self { Self::default() } pub fn id(mut self, id: impl Into) -> Self { self.id = Some(id.into()); self } pub fn node_id(mut self, node_id: impl Into) -> Self { self.node_id = Some(node_id.into()); self } pub fn rental_id(mut self, rental_id: impl Into) -> Self { self.rental_id = Some(rental_id.into()); self } pub fn renter_email(mut self, email: impl Into) -> Self { self.renter_email = Some(email.into()); self } pub fn amount(mut self, amount: Decimal) -> Self { self.amount = Some(amount); self } pub fn currency(mut self, currency: impl Into) -> Self { self.currency = Some(currency.into()); self } pub fn earning_date(mut self, date: DateTime) -> Self { self.earning_date = Some(date); self } pub fn rental_type(mut self, rental_type: crate::models::user::NodeRentalType) -> Self { self.rental_type = Some(rental_type); self } pub fn payment_status(mut self, status: crate::models::user::PaymentStatus) -> Self { self.payment_status = Some(status); self } pub fn build(self) -> Result { let id = self.id.unwrap_or_else(|| format!("earning_{}", uuid::Uuid::new_v4())); Ok(crate::models::user::ResourceProviderRentalEarning { id, node_id: self.node_id.ok_or("node_id is required")?, rental_id: self.rental_id.ok_or("rental_id is required")?, renter_email: self.renter_email.ok_or("renter_email is required")?, amount: self.amount.ok_or("amount is required")?, currency: self.currency.unwrap_or_else(|| "USD".to_string()), earning_date: self.earning_date.unwrap_or_else(Utc::now), date: self.earning_date.unwrap_or_else(Utc::now).to_string(), rental_type: self.rental_type.ok_or("rental_type is required")?, payment_status: self.payment_status.unwrap_or(crate::models::user::PaymentStatus::Pending), }) } } // ============================================================================= // USER ACTIVITY BUILDER // ============================================================================= #[derive(Default)] pub struct UserActivityBuilder { id: Option, user_email: Option, activity_type: Option, description: Option, timestamp: Option>, metadata: Option, category: Option, importance: Option, ip_address: Option, user_agent: Option, session_id: Option, } impl UserActivityBuilder { pub fn new() -> Self { Self::default() } pub fn id(mut self, id: impl Into) -> Self { self.id = Some(id.into()); self } pub fn activity_type(mut self, activity_type: crate::models::user::ActivityType) -> Self { self.activity_type = Some(activity_type); self } pub fn description(mut self, description: impl Into) -> Self { self.description = Some(description.into()); self } pub fn timestamp(mut self, timestamp: chrono::DateTime) -> Self { self.timestamp = Some(timestamp); self } pub fn metadata(mut self, metadata: std::collections::HashMap) -> Self { self.metadata = Some(serde_json::to_value(metadata).unwrap_or_default()); self } pub fn category(mut self, category: impl Into) -> Self { self.category = Some(category.into()); self } pub fn importance(mut self, importance: crate::models::user::ActivityImportance) -> Self { self.importance = Some(importance); self } pub fn build(self) -> Result { Ok(crate::models::user::UserActivity { id: self.id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string()), user_email: self.user_email.unwrap_or_else(|| "unknown@example.com".to_string()), activity_type: self.activity_type.ok_or("activity_type is required")?, description: self.description.unwrap_or_else(|| "No description".to_string()), timestamp: self.timestamp.unwrap_or_else(|| chrono::Utc::now()), metadata: self.metadata, category: self.category.unwrap_or_else(|| "General".to_string()), importance: self.importance.unwrap_or(crate::models::user::ActivityImportance::Medium), ip_address: self.ip_address, user_agent: self.user_agent, session_id: self.session_id, }) } } // ============================================================================= // USER DASHBOARD DATA BUILDER // ============================================================================= #[derive(Default)] pub struct UserDashboardDataBuilder { user_info: Option, activities: Option>, statistics: Option, services: Option>, active_deployments: Option, wallet_summary: Option, recommendations: Option>, quick_actions: Option>, } impl UserDashboardDataBuilder { pub fn new() -> Self { Self::default() } pub fn user_info(mut self, user_info: crate::models::user::UserInfo) -> Self { self.user_info = Some(user_info); self } pub fn activities(mut self, activities: Vec) -> Self { self.activities = Some(activities); self } pub fn statistics(mut self, statistics: Option) -> Self { self.statistics = statistics; self } pub fn services(mut self, services: Vec) -> Self { self.services = Some(services); self } pub fn active_deployments(mut self, deployments: i32) -> Self { self.active_deployments = Some(deployments); self } pub fn wallet_summary(mut self, wallet_summary: crate::models::user::WalletSummary) -> Self { self.wallet_summary = Some(wallet_summary); self } pub fn recommendations(mut self, recommendations: Vec) -> Self { self.recommendations = Some(recommendations); self } pub fn quick_actions(mut self, quick_actions: Vec) -> Self { self.quick_actions = Some(quick_actions); self } pub fn build(self) -> Result, String> { let mut dashboard_data = std::collections::HashMap::new(); if let Some(user_info) = self.user_info { dashboard_data.insert("user_info".to_string(), serde_json::to_value(user_info).unwrap()); } if let Some(activities) = self.activities { dashboard_data.insert("activities".to_string(), serde_json::to_value(activities).unwrap()); } if let Some(statistics) = self.statistics { dashboard_data.insert("statistics".to_string(), serde_json::to_value(statistics).unwrap()); } if let Some(services) = self.services { dashboard_data.insert("services".to_string(), serde_json::to_value(services).unwrap()); } if let Some(active_deployments) = self.active_deployments { dashboard_data.insert("active_deployments".to_string(), serde_json::to_value(active_deployments).unwrap()); } if let Some(wallet_summary) = self.wallet_summary { dashboard_data.insert("wallet_summary".to_string(), serde_json::to_value(wallet_summary).unwrap()); } if let Some(recommendations) = self.recommendations { dashboard_data.insert("recommendations".to_string(), serde_json::to_value(recommendations).unwrap()); } if let Some(quick_actions) = self.quick_actions { dashboard_data.insert("quick_actions".to_string(), serde_json::to_value(quick_actions).unwrap()); } Ok(dashboard_data) } } // ============================================================================= // NODE MARKETPLACE SERVICE BUILDER // ============================================================================= #[derive(Default)] pub struct NodeMarketplaceServiceBuilder { currency_service: Option, include_offline_nodes: Option, price_calculation_method: Option, cache_enabled: Option, } impl NodeMarketplaceServiceBuilder { pub fn new() -> Self { Self::default() } pub fn currency_service(mut self, service: crate::services::currency::CurrencyService) -> Self { self.currency_service = Some(service); self } pub fn include_offline_nodes(mut self, include: bool) -> Self { self.include_offline_nodes = Some(include); self } pub fn price_calculation_method(mut self, method: impl Into) -> Self { self.price_calculation_method = Some(method.into()); self } pub fn cache_enabled(mut self, enabled: bool) -> Self { self.cache_enabled = Some(enabled); self } pub fn build(self) -> Result { let currency_service = self.currency_service.unwrap_or_else(|| { CurrencyServiceBuilder::new() .build() .expect("Failed to create default currency service") }); Ok(crate::services::node_marketplace::NodeMarketplaceService::new_with_config( currency_service, self.include_offline_nodes.unwrap_or(true), self.price_calculation_method.unwrap_or_else(|| "capacity_based".to_string()), self.cache_enabled.unwrap_or(true), )) } } // ============================================================================= // NODE CREATION DATA BUILDER // ============================================================================= #[derive(Default)] pub struct NodeCreationDataBuilder { name: Option, location: Option, cpu_cores: Option, memory_gb: Option, storage_gb: Option, bandwidth_mbps: Option, region: Option, node_type: Option, slice_formats: Option>, rental_options: Option, slice_prices: Option>, } impl NodeCreationDataBuilder { pub fn new() -> Self { Self::default() } pub fn name(mut self, name: impl Into) -> Self { self.name = Some(name.into()); self } pub fn location(mut self, location: impl Into) -> Self { self.location = Some(location.into()); self } pub fn cpu_cores(mut self, cores: i32) -> Self { self.cpu_cores = Some(cores); self } pub fn memory_gb(mut self, memory: i32) -> Self { self.memory_gb = Some(memory); self } pub fn storage_gb(mut self, storage: i32) -> Self { self.storage_gb = Some(storage); self } pub fn bandwidth_mbps(mut self, bandwidth: i32) -> Self { self.bandwidth_mbps = Some(bandwidth); self } pub fn region(mut self, region: impl Into) -> Self { self.region = Some(region.into()); self } pub fn node_type(mut self, node_type: impl Into) -> Self { self.node_type = Some(node_type.into()); self } pub fn slice_formats(mut self, formats: Vec) -> Self { self.slice_formats = Some(formats); self } pub fn rental_options(mut self, options: crate::models::user::NodeRentalOptions) -> Self { self.rental_options = Some(options); self } pub fn slice_prices(mut self, prices: std::collections::HashMap) -> Self { self.slice_prices = Some(prices); self } pub fn build(self) -> Result { Ok(crate::services::resource_provider::NodeCreationData { name: self.name.ok_or("name is required")?, location: self.location.ok_or("location is required")?, cpu_cores: self.cpu_cores.unwrap_or(4), memory_gb: self.memory_gb.unwrap_or(8), storage_gb: self.storage_gb.unwrap_or(100), bandwidth_mbps: self.bandwidth_mbps.unwrap_or(100), region: self.region, node_type: self.node_type, slice_formats: self.slice_formats, rental_options: self.rental_options, slice_prices: self.slice_prices, slice_pricing: None, }) } } // ============================================================================= // FULL NODE PRICING BUILDER // ============================================================================= #[derive(Default)] pub struct FullNodePricingBuilder { hourly: Option, daily: Option, monthly: Option, yearly: Option, auto_calculate: Option, daily_discount_percent: Option, monthly_discount_percent: Option, yearly_discount_percent: Option, } impl FullNodePricingBuilder { pub fn new() -> Self { Self::default() } pub fn hourly(mut self, hourly: rust_decimal::Decimal) -> Self { self.hourly = Some(hourly); self } pub fn daily(mut self, daily: rust_decimal::Decimal) -> Self { self.daily = Some(daily); self } pub fn monthly(mut self, monthly: rust_decimal::Decimal) -> Self { self.monthly = Some(monthly); self } pub fn yearly(mut self, yearly: rust_decimal::Decimal) -> Self { self.yearly = Some(yearly); self } pub fn auto_calculate(mut self, auto_calculate: bool) -> Self { self.auto_calculate = Some(auto_calculate); self } pub fn daily_discount_percent(mut self, percent: f32) -> Self { self.daily_discount_percent = Some(percent); self } pub fn monthly_discount_percent(mut self, percent: f32) -> Self { self.monthly_discount_percent = Some(percent); self } pub fn yearly_discount_percent(mut self, percent: f32) -> Self { self.yearly_discount_percent = Some(percent); self } pub fn build(self) -> Result { let mut pricing = crate::models::user::FullNodePricing { hourly: self.hourly.unwrap_or_else(|| rust_decimal_macros::dec!(0)), daily: self.daily.unwrap_or_else(|| rust_decimal_macros::dec!(0)), monthly: self.monthly.unwrap_or_else(|| rust_decimal_macros::dec!(0)), yearly: self.yearly.unwrap_or_else(|| rust_decimal_macros::dec!(0)), auto_calculate: self.auto_calculate.unwrap_or(true), daily_discount_percent: self.daily_discount_percent.unwrap_or(0.0), monthly_discount_percent: self.monthly_discount_percent.unwrap_or(0.0), yearly_discount_percent: self.yearly_discount_percent.unwrap_or(0.0), monthly_cost: self.monthly.unwrap_or_else(|| rust_decimal_macros::dec!(0)), setup_fee: None, deposit_required: None, }; // Auto-calculate rates if enabled and hourly rate is set if pricing.auto_calculate && pricing.hourly > rust_decimal_macros::dec!(0) { pricing.calculate_from_hourly(); } Ok(pricing) } } // ============================================================================= // NODE RENTAL OPTIONS BUILDER // ============================================================================= #[derive(Default)] pub struct NodeRentalOptionsBuilder { slice_formats: Option>, pricing: Option, slice_rental_enabled: Option, full_node_rental_enabled: Option, full_node_pricing: Option, minimum_rental_days: Option, maximum_rental_days: Option, auto_renewal_enabled: Option, } impl NodeRentalOptionsBuilder { pub fn new() -> Self { Self::default() } pub fn slice_rental_enabled(mut self, enabled: bool) -> Self { self.slice_rental_enabled = Some(enabled); self } pub fn full_node_rental_enabled(mut self, enabled: bool) -> Self { self.full_node_rental_enabled = Some(enabled); self } pub fn full_node_pricing(mut self, pricing: crate::models::user::FullNodePricing) -> Self { self.full_node_pricing = Some(pricing); self } pub fn minimum_rental_days(mut self, days: u32) -> Self { self.minimum_rental_days = Some(days); self } pub fn maximum_rental_days(mut self, days: u32) -> Self { self.maximum_rental_days = Some(days); self } pub fn auto_renewal_enabled(mut self, enabled: bool) -> Self { self.auto_renewal_enabled = Some(enabled); self } pub fn build(self) -> Result { Ok(crate::models::user::NodeRentalOptions { full_node_available: self.full_node_rental_enabled.unwrap_or(false), slice_formats: self.slice_formats.unwrap_or_else(|| vec!["1x1".to_string(), "2x2".to_string(), "4x4".to_string()]), pricing: self.pricing.unwrap_or_else(|| crate::models::user::FullNodePricing { monthly_cost: rust_decimal::Decimal::try_from(100.0).unwrap_or_default(), setup_fee: Some(rust_decimal::Decimal::try_from(10.0).unwrap_or_default()), deposit_required: Some(rust_decimal::Decimal::try_from(50.0).unwrap_or_default()), hourly: rust_decimal::Decimal::try_from(4.17).unwrap_or_default(), daily: rust_decimal::Decimal::try_from(3.33).unwrap_or_default(), monthly: rust_decimal::Decimal::try_from(100.0).unwrap_or_default(), yearly: rust_decimal::Decimal::try_from(1000.0).unwrap_or_default(), daily_discount_percent: 20.0, monthly_discount_percent: 10.0, yearly_discount_percent: 17.0, auto_calculate: true, }), slice_rental_enabled: self.slice_rental_enabled.unwrap_or(true), full_node_rental_enabled: self.full_node_rental_enabled.unwrap_or(false), full_node_pricing: self.full_node_pricing, minimum_rental_days: self.minimum_rental_days.unwrap_or(30), maximum_rental_days: self.maximum_rental_days, auto_renewal_enabled: self.auto_renewal_enabled.unwrap_or(false), }) } } // ============================================================================= // SERVICE AND SERVICE REQUEST BUILDERS // ============================================================================= #[derive(Default)] pub struct ServiceBuilder { id: Option, name: Option, description: Option, category: Option, price_usd: Option, hourly_rate_usd: Option, availability: Option, } impl ServiceBuilder { pub fn new() -> Self { Self::default() } pub fn id(mut self, id: &str) -> Self { self.id = Some(id.to_string()); self } pub fn name(mut self, name: &str) -> Self { self.name = Some(name.to_string()); self } pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_string()); self } pub fn category(mut self, category: &str) -> Self { self.category = Some(category.to_string()); self } pub fn price_usd(mut self, price: f64) -> Self { self.price_usd = Some(rust_decimal::Decimal::try_from(price).unwrap_or_default()); self } pub fn hourly_rate_usd(mut self, rate: Option) -> Self { self.hourly_rate_usd = rate.map(|r| rust_decimal::Decimal::try_from(r).unwrap_or_default()); self } pub fn availability(mut self, available: bool) -> Self { self.availability = Some(available); self } pub fn build(self) -> Result { Ok(crate::models::user::Service { id: self.id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string()), name: self.name.unwrap_or_default(), description: self.description.unwrap_or_default(), category: self.category.unwrap_or_default(), price_usd: self.price_usd.unwrap_or_default(), hourly_rate_usd: self.hourly_rate_usd, availability: self.availability.unwrap_or(true), created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), clients: 0, price_per_hour_usd: self.price_usd.unwrap_or_default().to_string().parse().unwrap_or(0), status: "Active".to_string(), rating: 0.0, total_hours: None, }) } } #[derive(Default)] pub struct ServiceRequestBuilder { id: Option, customer_email: Option, service_id: Option, description: Option, status: Option, estimated_hours: Option, hourly_rate_usd: Option, total_cost_usd: Option, progress_percentage: Option, created_date: Option, completed_date: Option, } impl ServiceRequestBuilder { pub fn new() -> Self { Self::default() } pub fn id(mut self, id: &str) -> Self { self.id = Some(id.to_string()); self } pub fn customer_email(mut self, email: &str) -> Self { self.customer_email = Some(email.to_string()); self } pub fn service_id(mut self, service_id: &str) -> Self { self.service_id = Some(service_id.to_string()); self } pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_string()); self } pub fn status(mut self, status: &str) -> Self { self.status = Some(status.to_string()); self } pub fn estimated_hours(mut self, hours: i32) -> Self { self.estimated_hours = Some(hours); self } pub fn hourly_rate_usd(mut self, rate: rust_decimal::Decimal) -> Self { self.hourly_rate_usd = Some(rate); self } pub fn total_cost_usd(mut self, cost: rust_decimal::Decimal) -> Self { self.total_cost_usd = Some(cost); self } pub fn progress_percentage(mut self, progress: f32) -> Self { self.progress_percentage = Some(progress); self } pub fn created_date(mut self, date: &str) -> Self { self.created_date = Some(date.to_string()); self } pub fn completed_date(mut self, date: &str) -> Self { self.completed_date = Some(date.to_string()); self } pub fn build(self) -> Result { Ok(crate::models::user::ServiceRequest { id: self.id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string()), customer_email: self.customer_email.unwrap_or_default(), service_id: self.service_id.unwrap_or_default(), description: self.description, status: self.status.unwrap_or_else(|| "Pending".to_string()), estimated_hours: self.estimated_hours, hourly_rate_usd: self.hourly_rate_usd, total_cost_usd: self.total_cost_usd, progress_percentage: self.progress_percentage, created_date: self.created_date, completed_date: self.completed_date, progress: None, priority: "Medium".to_string(), hours_worked: None, notes: None, service_name: "Service".to_string(), budget: self.total_cost_usd.unwrap_or_default(), requested_date: chrono::Utc::now().format("%Y-%m-%d").to_string(), client_phone: None, client_name: None, client_email: None, }) } } // ============================================================================= // NODE STAKING OPTIONS BUILDER // ============================================================================= #[derive(Default)] pub struct NodeStakingOptionsBuilder { staking_enabled: Option, minimum_stake: Option, reward_rate: Option, staked_amount: Option, staking_start_date: Option>, staking_period_months: Option, early_withdrawal_allowed: Option, early_withdrawal_penalty_percent: Option, } impl NodeStakingOptionsBuilder { pub fn new() -> Self { Self::default() } pub fn staking_enabled(mut self, enabled: bool) -> Self { self.staking_enabled = Some(enabled); self } pub fn minimum_stake(mut self, stake: Decimal) -> Self { self.minimum_stake = Some(stake); self } pub fn reward_rate(mut self, rate: f32) -> Self { self.reward_rate = Some(rate); self } pub fn staked_amount(mut self, amount: Decimal) -> Self { self.staked_amount = Some(amount); self } pub fn staking_start_date(mut self, date: DateTime) -> Self { self.staking_start_date = Some(date); self } pub fn staking_period_months(mut self, months: u32) -> Self { self.staking_period_months = Some(months); self } pub fn early_withdrawal_allowed(mut self, allowed: bool) -> Self { self.early_withdrawal_allowed = Some(allowed); self } pub fn early_withdrawal_penalty_percent(mut self, percent: f32) -> Self { self.early_withdrawal_penalty_percent = Some(percent); self } pub fn build(self) -> Result { Ok(crate::models::user::NodeStakingOptions { enabled: self.staking_enabled.unwrap_or(false), minimum_stake: self.minimum_stake.unwrap_or(Decimal::new(100, 0)), reward_rate: self.reward_rate.unwrap_or(5.0), staking_enabled: self.staking_enabled.unwrap_or(false), staked_amount: self.staked_amount.unwrap_or(Decimal::ZERO), staking_start_date: self.staking_start_date, staking_period_months: self.staking_period_months.unwrap_or(12), early_withdrawal_allowed: self.early_withdrawal_allowed.unwrap_or(true), early_withdrawal_penalty_percent: self.early_withdrawal_penalty_percent.unwrap_or(25.0), }) } } /// Builder for UserDeployment #[derive(Default)] pub struct UserDeploymentBuilder { id: Option, app_name: Option, status: Option, cost_per_month: Option, deployed_at: Option>, provider: Option, region: Option, resource_usage: Option, } impl UserDeploymentBuilder { pub fn new() -> Self { Self::default() } pub fn id(mut self, id: impl Into) -> Self { self.id = Some(id.into()); self } pub fn app_name(mut self, name: impl Into) -> Self { self.app_name = Some(name.into()); self } pub fn status(mut self, status: crate::models::user::DeploymentStatus) -> Self { self.status = Some(status); self } pub fn cost_per_month(mut self, cost: rust_decimal::Decimal) -> Self { self.cost_per_month = Some(cost); self } pub fn deployed_at(mut self, date: chrono::DateTime) -> Self { self.deployed_at = Some(date); self } pub fn provider(mut self, provider: impl Into) -> Self { self.provider = Some(provider.into()); self } pub fn region(mut self, region: impl Into) -> Self { self.region = Some(region.into()); self } pub fn resource_usage(mut self, usage: crate::models::user::ResourceUtilization) -> Self { self.resource_usage = Some(usage); self } pub fn build(self) -> Result { Ok(crate::models::user::UserDeployment { id: self.id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string()), app_name: self.app_name.ok_or("app_name is required")?, status: self.status.unwrap_or_default(), cost_per_month: self.cost_per_month.unwrap_or(rust_decimal::Decimal::ZERO), deployed_at: self.deployed_at.unwrap_or_else(|| chrono::Utc::now()), provider: self.provider.unwrap_or_else(|| "Unknown".to_string()), region: self.region.unwrap_or_else(|| "Unknown".to_string()), resource_usage: self.resource_usage.unwrap_or_default(), }) } } // ============================================================================= // SERVICE BOOKING BUILDER // ============================================================================= #[derive(Default)] pub struct ServiceBookingBuilder { id: Option, service_id: Option, service_name: Option, provider_email: Option, customer_email: Option, budget: Option, estimated_hours: Option, hourly_rate_usd: Option, progress_percentage: Option, status: Option, requested_date: Option, priority: Option, description: Option, booking_date: Option, } impl ServiceBookingBuilder { pub fn new() -> Self { Self::default() } pub fn id(mut self, id: &str) -> Self { self.id = Some(id.to_string()); self } pub fn service_id(mut self, service_id: &str) -> Self { self.service_id = Some(service_id.to_string()); self } pub fn service_name(mut self, service_name: &str) -> Self { self.service_name = Some(service_name.to_string()); self } pub fn provider_email(mut self, provider_email: &str) -> Self { self.provider_email = Some(provider_email.to_string()); self } pub fn customer_email(mut self, customer_email: &str) -> Self { self.customer_email = Some(customer_email.to_string()); self } pub fn budget(mut self, budget: Decimal) -> Self { self.budget = Some(budget); self } pub fn estimated_hours(mut self, hours: i32) -> Self { self.estimated_hours = Some(hours); self } pub fn hourly_rate_usd(mut self, rate: Decimal) -> Self { self.hourly_rate_usd = Some(rate); self } pub fn progress_percentage(mut self, progress: f32) -> Self { self.progress_percentage = Some(progress); self } pub fn status(mut self, status: &str) -> Self { self.status = Some(status.to_string()); self } pub fn requested_date(mut self, date: &str) -> Self { self.requested_date = Some(date.to_string()); self } pub fn priority(mut self, priority: &str) -> Self { self.priority = Some(priority.to_string()); self } pub fn description(mut self, description: Option) -> Self { self.description = description; self } pub fn booking_date(mut self, date: &str) -> Self { self.booking_date = Some(date.to_string()); self } pub fn build(self) -> Result { Ok(ServiceBooking { id: self.id.ok_or("ID is required")?, service_id: self.service_id.ok_or("Service ID is required")?, service_name: self.service_name.ok_or("Service name is required")?, provider_email: self.provider_email.ok_or("Provider email is required")?, customer_email: self.customer_email.ok_or("Customer email is required")?, budget: self.budget.unwrap_or(Decimal::ZERO), estimated_hours: self.estimated_hours, hourly_rate_usd: self.hourly_rate_usd, total_cost_usd: None, progress_percentage: self.progress_percentage, created_date: Some(chrono::Utc::now().format("%Y-%m-%d").to_string()), status: self.status.unwrap_or_else(|| "Pending".to_string()), requested_date: self.requested_date.unwrap_or_else(|| chrono::Utc::now().format("%Y-%m-%d").to_string()), priority: self.priority.unwrap_or_else(|| "Medium".to_string()), description: self.description, booking_date: self.booking_date.map(|date| chrono::DateTime::parse_from_rfc3339(&date).ok().map(|dt| dt.with_timezone(&chrono::Utc))).flatten(), client_phone: None, progress: None, completed_date: None, }) } } impl ServiceBooking { pub fn builder() -> ServiceBookingBuilder { ServiceBookingBuilder::new() } } // ============================================================================= // CUSTOMER SERVICE DATA BUILDER // ============================================================================= #[derive(Default)] pub struct CustomerServiceDataBuilder { active_bookings: Option, completed_bookings: Option, total_spent: Option, monthly_spending: Option, average_rating_given: Option, service_bookings: Option>, favorite_providers: Option>, spending_history: Option>, } impl CustomerServiceDataBuilder { pub fn new() -> Self { Self::default() } pub fn active_bookings(mut self, count: i32) -> Self { self.active_bookings = Some(count); self } pub fn completed_bookings(mut self, count: i32) -> Self { self.completed_bookings = Some(count); self } pub fn total_spent(mut self, amount: Decimal) -> Self { self.total_spent = Some(amount); self } pub fn monthly_spending(mut self, amount: Decimal) -> Self { self.monthly_spending = Some(amount); self } pub fn average_rating_given(mut self, rating: f32) -> Self { self.average_rating_given = Some(rating); self } pub fn service_bookings(mut self, bookings: Vec) -> Self { self.service_bookings = Some(bookings); self } pub fn favorite_providers(mut self, providers: Vec) -> Self { self.favorite_providers = Some(providers); self } pub fn spending_history(mut self, history: Vec) -> Self { self.spending_history = Some(history); self } pub fn build(self) -> Result { Ok(crate::models::user::CustomerServiceData { active_bookings: self.active_bookings.unwrap_or(0), completed_bookings: self.completed_bookings.unwrap_or(0), total_spent: self.total_spent.unwrap_or(Decimal::ZERO), monthly_spending: self.monthly_spending.unwrap_or(Decimal::ZERO), average_rating_given: self.average_rating_given.unwrap_or(0.0), service_bookings: self.service_bookings.unwrap_or_default(), favorite_providers: self.favorite_providers.unwrap_or_default(), spending_history: self.spending_history.unwrap_or_default(), pending_transactions: 0, total_spent_usd: self.total_spent.unwrap_or(Decimal::ZERO).to_string().parse::().unwrap_or(0), }) } } impl crate::models::user::CustomerServiceData { pub fn builder() -> CustomerServiceDataBuilder { CustomerServiceDataBuilder::new() } } #[derive(Default)] pub struct SpendingRecordBuilder { date: Option, amount: Option, service_name: Option, provider_name: Option, } impl SpendingRecordBuilder { pub fn new() -> Self { Self::default() } pub fn date(mut self, date: &str) -> Self { self.date = Some(date.to_string()); self } pub fn amount(mut self, amount: f32) -> Self { self.amount = Some(amount); self } pub fn service_name(mut self, name: &str) -> Self { self.service_name = Some(name.to_string()); self } pub fn provider_name(mut self, name: &str) -> Self { self.provider_name = Some(name.to_string()); self } pub fn build(self) -> Result { Ok(crate::models::user::SpendingRecord { date: self.date.ok_or("Date is required")?, amount: self.amount.unwrap_or(0.0), service_name: self.service_name.ok_or("Service name is required")?, provider_name: self.provider_name.ok_or("Provider name is required")?, }) } } impl crate::models::user::SpendingRecord { pub fn builder() -> SpendingRecordBuilder { SpendingRecordBuilder::new() } } // ============================================================================= // AUTO TOP-UP BUILDERS // ============================================================================= #[derive(Default)] pub struct AutoTopUpSettingsBuilder { enabled: Option, threshold_amount: Option, topup_amount: Option, payment_method_id: Option, daily_limit: Option, monthly_limit: Option, } impl AutoTopUpSettingsBuilder { pub fn new() -> Self { Self::default() } pub fn enabled(mut self, enabled: bool) -> Self { self.enabled = Some(enabled); self } pub fn threshold_amount(mut self, amount: Decimal) -> Self { self.threshold_amount = Some(amount); self } pub fn topup_amount(mut self, amount: Decimal) -> Self { self.topup_amount = Some(amount); self } pub fn payment_method_id(mut self, id: impl Into) -> Self { self.payment_method_id = Some(id.into()); self } pub fn daily_limit(mut self, limit: Decimal) -> Self { self.daily_limit = Some(limit); self } pub fn monthly_limit(mut self, limit: Decimal) -> Self { self.monthly_limit = Some(limit); self } pub fn build(self) -> Result { Ok(crate::services::user_persistence::AutoTopUpSettings { enabled: self.enabled.unwrap_or(false), threshold_amount_usd: self.threshold_amount.unwrap_or(dec!(10.0)), topup_amount_usd: self.topup_amount.unwrap_or(dec!(25.0)), payment_method_id: self.payment_method_id.ok_or("payment_method_id is required")?, daily_limit_usd: self.daily_limit, monthly_limit_usd: self.monthly_limit, created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), }) } } // ============================================================================= // SSH KEY BUILDERS // ============================================================================= /// Builder for SSH keys following established patterns #[derive(Default)] pub struct SSHKeyBuilder { id: Option, name: Option, public_key: Option, fingerprint: Option, key_type: Option, created_at: Option>, last_used: Option>, is_default: Option, comment: Option, } impl SSHKeyBuilder { pub fn new() -> Self { Self::default() } pub fn id(mut self, id: impl Into) -> Self { self.id = Some(id.into()); self } pub fn name(mut self, name: impl Into) -> Self { self.name = Some(name.into()); self } pub fn public_key(mut self, public_key: impl Into) -> Self { self.public_key = Some(public_key.into()); self } pub fn fingerprint(mut self, fingerprint: impl Into) -> Self { self.fingerprint = Some(fingerprint.into()); self } pub fn key_type(mut self, key_type: crate::models::ssh_key::SSHKeyType) -> Self { self.key_type = Some(key_type); self } pub fn created_at(mut self, created_at: DateTime) -> Self { self.created_at = Some(created_at); self } pub fn last_used(mut self, last_used: DateTime) -> Self { self.last_used = Some(last_used); self } pub fn is_default(mut self, is_default: bool) -> Self { self.is_default = Some(is_default); self } pub fn comment(mut self, comment: impl Into) -> Self { self.comment = Some(comment.into()); self } pub fn build(self) -> Result { Ok(crate::models::ssh_key::SSHKey { id: self.id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string()), name: self.name.ok_or("name is required")?, public_key: self.public_key.ok_or("public_key is required")?, fingerprint: self.fingerprint.ok_or("fingerprint is required")?, key_type: self.key_type.ok_or("key_type is required")?, created_at: self.created_at.unwrap_or_else(|| Utc::now()), last_used: self.last_used, is_default: self.is_default.unwrap_or(false), comment: self.comment, }) } } impl crate::models::ssh_key::SSHKey { pub fn builder() -> SSHKeyBuilder { SSHKeyBuilder::new() } }