3388 lines
110 KiB
Rust
3388 lines
110 KiB
Rust
//! 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<String>,
|
|
name: Option<String>,
|
|
category: Option<String>,
|
|
version: Option<String>,
|
|
status: Option<String>,
|
|
deployments: Option<i32>,
|
|
rating: Option<f32>,
|
|
monthly_revenue: Option<i32>,
|
|
last_updated: Option<String>,
|
|
auto_healing: Option<bool>,
|
|
}
|
|
|
|
impl PublishedAppBuilder {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn id(mut self, id: impl Into<String>) -> Self {
|
|
self.id = Some(id.into());
|
|
self
|
|
}
|
|
|
|
pub fn name(mut self, name: impl Into<String>) -> Self {
|
|
self.name = Some(name.into());
|
|
self
|
|
}
|
|
|
|
pub fn category(mut self, category: impl Into<String>) -> Self {
|
|
self.category = Some(category.into());
|
|
self
|
|
}
|
|
|
|
pub fn version(mut self, version: impl Into<String>) -> Self {
|
|
self.version = Some(version.into());
|
|
self
|
|
}
|
|
|
|
pub fn status(mut self, status: impl Into<String>) -> 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<String>) -> 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<PublishedApp, String> {
|
|
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<String>, name: impl Into<String>) -> Self {
|
|
Self::builder()
|
|
.id(id)
|
|
.name(name)
|
|
.category("Analytics")
|
|
.status("Active")
|
|
.auto_healing(true)
|
|
.build()
|
|
.unwrap()
|
|
}
|
|
|
|
pub fn database_template(id: impl Into<String>, name: impl Into<String>) -> 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<String>, name: impl Into<String>) -> 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<String>) -> Self {
|
|
self.version = version.into();
|
|
self
|
|
}
|
|
|
|
pub fn with_last_updated(mut self, updated: chrono::DateTime<chrono::Utc>) -> Self {
|
|
self.last_updated = updated;
|
|
self
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct DeploymentStatBuilder {
|
|
app_name: Option<String>,
|
|
region: Option<String>,
|
|
instances: Option<i32>,
|
|
status: Option<String>,
|
|
resource_usage: Option<ResourceUtilization>,
|
|
customer_name: Option<String>,
|
|
deployed_date: Option<String>,
|
|
deployment_id: Option<String>,
|
|
auto_healing: Option<bool>,
|
|
active_instances: Option<i32>,
|
|
total_instances: Option<i32>,
|
|
avg_response_time_ms: Option<f32>,
|
|
uptime_percentage: Option<f32>,
|
|
last_deployment: Option<chrono::DateTime<chrono::Utc>>,
|
|
}
|
|
|
|
impl DeploymentStatBuilder {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn app_name(mut self, name: impl Into<String>) -> Self {
|
|
self.app_name = Some(name.into());
|
|
self
|
|
}
|
|
|
|
pub fn region(mut self, region: impl Into<String>) -> 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<String>) -> 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<String>) -> Self {
|
|
self.customer_name = Some(name.into());
|
|
self
|
|
}
|
|
|
|
pub fn deployed_date(mut self, date: impl Into<String>) -> Self {
|
|
self.deployed_date = Some(date.into());
|
|
self
|
|
}
|
|
|
|
pub fn deployment_id(mut self, id: impl Into<String>) -> 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<DeploymentStat, String> {
|
|
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<i32>,
|
|
name: Option<String>,
|
|
email: Option<String>,
|
|
role: Option<UserRole>,
|
|
country: Option<String>,
|
|
timezone: Option<String>,
|
|
created_at: Option<DateTime<Utc>>,
|
|
updated_at: Option<DateTime<Utc>>,
|
|
}
|
|
|
|
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<String>) -> Self {
|
|
self.name = Some(name.into());
|
|
self
|
|
}
|
|
|
|
pub fn email(mut self, email: impl Into<String>) -> 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<String>) -> Self {
|
|
self.country = Some(country.into());
|
|
self
|
|
}
|
|
|
|
pub fn timezone(mut self, timezone: impl Into<String>) -> Self {
|
|
self.timezone = Some(timezone.into());
|
|
self
|
|
}
|
|
|
|
|
|
pub fn build(self) -> Result<User, String> {
|
|
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<String>, email: impl Into<String>) -> Self {
|
|
Self::builder()
|
|
.name(name)
|
|
.email(email)
|
|
.role(UserRole::User)
|
|
.build()
|
|
.unwrap()
|
|
}
|
|
|
|
pub fn admin_template(name: impl Into<String>, email: impl Into<String>) -> Self {
|
|
Self::builder()
|
|
.name(name)
|
|
.email(email)
|
|
.role(UserRole::Admin)
|
|
.build()
|
|
.unwrap()
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct AppDeploymentBuilder {
|
|
id: Option<String>,
|
|
app_id: Option<String>,
|
|
app_name: Option<String>,
|
|
customer_name: Option<String>,
|
|
customer_email: Option<String>,
|
|
deployed_date: Option<String>,
|
|
status: Option<String>,
|
|
health_score: Option<f32>,
|
|
region: Option<String>,
|
|
instances: Option<i32>,
|
|
resource_usage: Option<ResourceUtilization>,
|
|
monthly_revenue: Option<i32>,
|
|
last_updated: Option<String>,
|
|
auto_healing: Option<bool>,
|
|
}
|
|
|
|
impl AppDeploymentBuilder {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn id(mut self, id: impl Into<String>) -> Self {
|
|
self.id = Some(id.into());
|
|
self
|
|
}
|
|
|
|
pub fn app_id(mut self, app_id: impl Into<String>) -> Self {
|
|
self.app_id = Some(app_id.into());
|
|
self
|
|
}
|
|
|
|
pub fn app_name(mut self, name: impl Into<String>) -> Self {
|
|
self.app_name = Some(name.into());
|
|
self
|
|
}
|
|
|
|
pub fn customer_name(mut self, name: impl Into<String>) -> Self {
|
|
self.customer_name = Some(name.into());
|
|
self
|
|
}
|
|
|
|
pub fn customer_email(mut self, email: impl Into<String>) -> Self {
|
|
self.customer_email = Some(email.into());
|
|
self
|
|
}
|
|
|
|
pub fn deployed_date(mut self, date: impl Into<String>) -> Self {
|
|
self.deployed_date = Some(date.into());
|
|
self
|
|
}
|
|
|
|
pub fn status(mut self, status: impl Into<String>) -> 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<String>) -> 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<String>) -> 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<AppDeployment, String> {
|
|
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<String>,
|
|
name: Option<String>,
|
|
category_id: Option<String>,
|
|
description: Option<String>,
|
|
base_price: Option<Decimal>,
|
|
base_currency: Option<String>,
|
|
attributes: HashMap<String, ProductAttribute>,
|
|
provider_id: Option<String>,
|
|
provider_name: Option<String>,
|
|
availability: Option<ProductAvailability>,
|
|
metadata: Option<ProductMetadata>,
|
|
created_at: Option<DateTime<Utc>>,
|
|
updated_at: Option<DateTime<Utc>>,
|
|
}
|
|
|
|
impl ProductBuilder {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn id(mut self, id: impl Into<String>) -> Self {
|
|
self.id = Some(id.into());
|
|
self
|
|
}
|
|
|
|
pub fn name(mut self, name: impl Into<String>) -> Self {
|
|
self.name = Some(name.into());
|
|
self
|
|
}
|
|
|
|
pub fn category_id(mut self, category_id: impl Into<String>) -> Self {
|
|
self.category_id = Some(category_id.into());
|
|
self
|
|
}
|
|
|
|
pub fn description(mut self, description: impl Into<String>) -> 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<String>) -> Self {
|
|
self.base_currency = Some(currency.into());
|
|
self
|
|
}
|
|
|
|
pub fn add_attribute(mut self, key: impl Into<String>, attribute: ProductAttribute) -> Self {
|
|
self.attributes.insert(key.into(), attribute);
|
|
self
|
|
}
|
|
|
|
pub fn provider_id(mut self, provider_id: impl Into<String>) -> Self {
|
|
self.provider_id = Some(provider_id.into());
|
|
self
|
|
}
|
|
|
|
pub fn provider_name(mut self, provider_name: impl Into<String>) -> 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<Product, String> {
|
|
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<String>,
|
|
user_id: Option<String>,
|
|
items: Vec<OrderItem>,
|
|
subtotal_base: Option<Decimal>,
|
|
total_base: Option<Decimal>,
|
|
base_currency: Option<String>,
|
|
currency_used: Option<String>,
|
|
currency_total: Option<Decimal>,
|
|
conversion_rate: Option<Decimal>,
|
|
status: Option<OrderStatus>,
|
|
payment_method: Option<String>,
|
|
payment_details: Option<PaymentDetails>,
|
|
billing_address: Option<Address>,
|
|
shipping_address: Option<Address>,
|
|
notes: Option<String>,
|
|
purchase_type: Option<PurchaseType>,
|
|
created_at: Option<DateTime<Utc>>,
|
|
updated_at: Option<DateTime<Utc>>,
|
|
}
|
|
|
|
impl OrderBuilder {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn id(mut self, id: impl Into<String>) -> Self {
|
|
self.id = Some(id.into());
|
|
self
|
|
}
|
|
|
|
pub fn user_id(mut self, user_id: impl Into<String>) -> 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<OrderItem>) -> 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<String>) -> Self {
|
|
self.base_currency = Some(currency.into());
|
|
self
|
|
}
|
|
|
|
pub fn currency_used(mut self, currency: impl Into<String>) -> 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<String>) -> 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<String>) -> 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<Order, String> {
|
|
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<String>,
|
|
product_name: Option<String>,
|
|
product_category: Option<String>,
|
|
quantity: Option<u32>,
|
|
unit_price_base: Option<Decimal>,
|
|
total_price_base: Option<Decimal>,
|
|
specifications: HashMap<String, Value>,
|
|
provider_id: Option<String>,
|
|
provider_name: Option<String>,
|
|
}
|
|
|
|
impl OrderItemBuilder {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn product_id(mut self, id: impl Into<String>) -> Self {
|
|
self.product_id = Some(id.into());
|
|
self
|
|
}
|
|
|
|
pub fn product_name(mut self, name: impl Into<String>) -> Self {
|
|
self.product_name = Some(name.into());
|
|
self
|
|
}
|
|
|
|
pub fn product_category(mut self, category: impl Into<String>) -> 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<String>, value: Value) -> Self {
|
|
self.specifications.insert(key.into(), value);
|
|
self
|
|
}
|
|
|
|
pub fn provider_id(mut self, id: impl Into<String>) -> Self {
|
|
self.provider_id = Some(id.into());
|
|
self
|
|
}
|
|
|
|
pub fn provider_name(mut self, name: impl Into<String>) -> Self {
|
|
self.provider_name = Some(name.into());
|
|
self
|
|
}
|
|
|
|
pub fn build(self) -> Result<OrderItem, String> {
|
|
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<u64>,
|
|
base_currency: Option<String>,
|
|
display_currency: Option<String>,
|
|
auto_update: Option<bool>,
|
|
fallback_rates: Option<HashMap<String, Decimal>>,
|
|
}
|
|
|
|
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<String>) -> 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<String, Decimal>) -> Self {
|
|
self.fallback_rates = Some(rates);
|
|
self
|
|
}
|
|
|
|
pub fn display_currency(mut self, currency: impl Into<String>) -> Self {
|
|
self.display_currency = Some(currency.into());
|
|
self
|
|
}
|
|
|
|
pub fn build(self) -> Result<crate::services::currency::CurrencyService, String> {
|
|
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<crate::services::currency::CurrencyService>,
|
|
cache_enabled: Option<bool>,
|
|
default_category: Option<String>,
|
|
include_slice_products: Option<bool>,
|
|
}
|
|
|
|
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<String>) -> 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<crate::services::product::ProductService, String> {
|
|
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<crate::services::currency::CurrencyService>,
|
|
product_service: Option<crate::services::product::ProductService>,
|
|
auto_save: Option<bool>,
|
|
}
|
|
|
|
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<crate::services::order::OrderService, String> {
|
|
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<HashMap<String, crate::models::pool::LiquidityPool>>,
|
|
analytics_enabled: Option<bool>,
|
|
}
|
|
|
|
impl PoolServiceBuilder {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn initial_pools(mut self, pools: HashMap<String, crate::models::pool::LiquidityPool>) -> 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<crate::services::pool_service::PoolService, String> {
|
|
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<String>,
|
|
active_section: Option<String>,
|
|
gitea_enabled: Option<bool>,
|
|
enable_mock_data: Option<bool>,
|
|
user: Option<crate::models::user::User>,
|
|
user_json: Option<String>,
|
|
custom_data: HashMap<String, serde_json::Value>,
|
|
}
|
|
|
|
impl ContextBuilder {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn active_page(mut self, page: impl Into<String>) -> Self {
|
|
self.active_page = Some(page.into());
|
|
self
|
|
}
|
|
|
|
pub fn active_section(mut self, section: impl Into<String>) -> 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<String>) -> Self {
|
|
self.user_json = Some(json.into());
|
|
self
|
|
}
|
|
|
|
pub fn custom(mut self, key: impl Into<String>, 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::<String>("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<Decimal>,
|
|
transactions: Option<Vec<crate::models::user::Transaction>>,
|
|
staked_amount: Option<Decimal>,
|
|
pool_positions: Option<HashMap<String, crate::services::user_persistence::PoolPosition>>,
|
|
name: Option<String>,
|
|
country: Option<String>,
|
|
timezone: Option<String>,
|
|
}
|
|
|
|
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<String>) -> 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<String>, 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<String>) -> 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<String>) -> 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<crate::models::user::Transaction>) -> 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<String, crate::services::user_persistence::PoolPosition>) -> Self {
|
|
self.pool_positions = Some(positions);
|
|
self
|
|
}
|
|
|
|
pub fn name(mut self, name: impl Into<String>) -> Self {
|
|
self.name = Some(name.into());
|
|
self
|
|
}
|
|
|
|
pub fn country(mut self, country: impl Into<String>) -> Self {
|
|
self.country = Some(country.into());
|
|
self
|
|
}
|
|
|
|
pub fn timezone(mut self, timezone: impl Into<String>) -> 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<i32>,
|
|
online_nodes: Option<i32>,
|
|
total_capacity: Option<crate::models::user::NodeCapacity>,
|
|
used_capacity: Option<crate::models::user::NodeCapacity>,
|
|
monthly_earnings: Option<i32>,
|
|
total_earnings: Option<i32>,
|
|
uptime_percentage: Option<f32>,
|
|
nodes: Option<Vec<crate::models::user::FarmNode>>,
|
|
earnings_history: Option<Vec<crate::models::user::EarningsRecord>>,
|
|
active_slices: Option<i32>,
|
|
}
|
|
|
|
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<crate::models::user::FarmNode>) -> Self {
|
|
self.nodes = Some(nodes);
|
|
self
|
|
}
|
|
|
|
pub fn earnings_history(mut self, history: Vec<crate::models::user::EarningsRecord>) -> Self {
|
|
self.earnings_history = Some(history);
|
|
self
|
|
}
|
|
|
|
pub fn earnings(mut self, earnings: Vec<crate::models::user::EarningsRecord>) -> 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::<f32>() / 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::<i32>().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<crate::models::user::ResourceProviderData, String> {
|
|
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<String>,
|
|
resource_provider_name: Option<String>,
|
|
slice_name: Option<String>,
|
|
cpu_cores: Option<i32>,
|
|
memory_gb: Option<i32>,
|
|
storage_gb: Option<i32>,
|
|
bandwidth_mbps: Option<i32>,
|
|
min_uptime_sla: Option<f32>,
|
|
public_ips: Option<i32>,
|
|
node_id: Option<String>,
|
|
slice_type: Option<crate::models::product::SliceType>,
|
|
price_per_hour: Option<rust_decimal::Decimal>,
|
|
}
|
|
|
|
impl SliceProductBuilder {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn resource_provider_id(mut self, resource_provider_id: impl Into<String>) -> Self {
|
|
self.resource_provider_id = Some(resource_provider_id.into());
|
|
self
|
|
}
|
|
|
|
pub fn resource_provider_name(mut self, resource_provider_name: impl Into<String>) -> Self {
|
|
self.resource_provider_name = Some(resource_provider_name.into());
|
|
self
|
|
}
|
|
|
|
pub fn slice_name(mut self, slice_name: impl Into<String>) -> 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<String>) -> 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<crate::models::product::Product, String> {
|
|
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<String>,
|
|
name: Option<String>,
|
|
location: Option<String>,
|
|
status: Option<crate::models::user::NodeStatus>,
|
|
capacity: Option<crate::models::user::NodeCapacity>,
|
|
used_capacity: Option<crate::models::user::NodeCapacity>,
|
|
uptime_percentage: Option<f32>,
|
|
farming_start_date: Option<chrono::DateTime<chrono::Utc>>,
|
|
last_updated: Option<chrono::DateTime<chrono::Utc>>,
|
|
utilization_7_day_avg: Option<f32>,
|
|
slice_formats_supported: Option<Vec<String>>,
|
|
earnings_today: Option<rust_decimal::Decimal>,
|
|
last_seen: Option<chrono::DateTime<chrono::Utc>>,
|
|
health_score: Option<f32>,
|
|
region: Option<String>,
|
|
node_type: Option<String>,
|
|
rental_options: Option<crate::models::user::NodeRentalOptions>,
|
|
availability_status: Option<crate::models::user::NodeAvailabilityStatus>,
|
|
grid_node_id: Option<u32>,
|
|
grid_data: Option<crate::models::user::GridNodeData>,
|
|
node_group_id: Option<String>,
|
|
group_assignment_date: Option<chrono::DateTime<chrono::Utc>>,
|
|
group_slice_format: Option<String>,
|
|
group_slice_price: Option<rust_decimal::Decimal>,
|
|
}
|
|
|
|
impl FarmNodeBuilder {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn id(mut self, id: impl Into<String>) -> Self {
|
|
self.id = Some(id.into());
|
|
self
|
|
}
|
|
|
|
pub fn name(mut self, name: impl Into<String>) -> Self {
|
|
self.name = Some(name.into());
|
|
self
|
|
}
|
|
|
|
pub fn location(mut self, location: impl Into<String>) -> 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<String>) -> Self {
|
|
self.node_group_id = Some(group_id.into());
|
|
self
|
|
}
|
|
|
|
pub fn group_assignment_date(mut self, date: chrono::DateTime<chrono::Utc>) -> Self {
|
|
self.group_assignment_date = Some(date);
|
|
self
|
|
}
|
|
|
|
pub fn group_slice_format(mut self, format: impl Into<String>) -> 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<chrono::Utc>) -> 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<String>) -> Self {
|
|
self.region = Some(region.into());
|
|
self
|
|
}
|
|
|
|
pub fn node_type(mut self, node_type: impl Into<String>) -> 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<crate::models::user::FarmNode, String> {
|
|
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<String>,
|
|
name: Option<String>,
|
|
description: Option<String>,
|
|
group_type: Option<crate::models::user::NodeGroupType>,
|
|
node_ids: Option<Vec<String>>,
|
|
group_config: Option<crate::models::user::NodeGroupConfig>,
|
|
created_at: Option<chrono::DateTime<chrono::Utc>>,
|
|
updated_at: Option<chrono::DateTime<chrono::Utc>>,
|
|
}
|
|
|
|
impl NodeGroupBuilder {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn id(mut self, id: impl Into<String>) -> Self {
|
|
self.id = Some(id.into());
|
|
self
|
|
}
|
|
|
|
pub fn name(mut self, name: impl Into<String>) -> Self {
|
|
self.name = Some(name.into());
|
|
self
|
|
}
|
|
|
|
pub fn description(mut self, description: impl Into<String>) -> 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<String>) -> 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<chrono::Utc>) -> Self {
|
|
self.created_at = Some(created_at);
|
|
self
|
|
}
|
|
|
|
pub fn updated_at(mut self, updated_at: chrono::DateTime<chrono::Utc>) -> 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<String>, name: impl Into<String>) -> 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<crate::models::user::NodeGroup, String> {
|
|
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<u32>,
|
|
city: Option<String>,
|
|
country: Option<String>,
|
|
farm_name: Option<String>,
|
|
farm_id: Option<u32>,
|
|
public_ips: Option<u32>,
|
|
total_resources: Option<crate::models::user::NodeCapacity>,
|
|
used_resources: Option<crate::models::user::NodeCapacity>,
|
|
certification_type: Option<String>,
|
|
farming_policy_id: Option<u32>,
|
|
last_updated: Option<chrono::DateTime<chrono::Utc>>,
|
|
}
|
|
|
|
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<String>) -> Self {
|
|
self.city = Some(city.into());
|
|
self
|
|
}
|
|
|
|
pub fn country(mut self, country: impl Into<String>) -> Self {
|
|
self.country = Some(country.into());
|
|
self
|
|
}
|
|
|
|
pub fn farm_name(mut self, farm_name: impl Into<String>) -> 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<String>) -> 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<chrono::Utc>) -> Self {
|
|
self.last_updated = Some(last_updated);
|
|
self
|
|
}
|
|
|
|
pub fn build(self) -> Result<crate::models::user::GridNodeData, String> {
|
|
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<String>,
|
|
node_id: Option<String>,
|
|
renter_email: Option<String>,
|
|
rental_type: Option<crate::models::user::NodeRentalType>,
|
|
monthly_cost: Option<Decimal>,
|
|
start_date: Option<DateTime<Utc>>,
|
|
end_date: Option<DateTime<Utc>>,
|
|
status: Option<crate::models::user::NodeRentalStatus>,
|
|
auto_renewal: Option<bool>,
|
|
payment_method: Option<String>,
|
|
metadata: HashMap<String, Value>,
|
|
}
|
|
|
|
impl NodeRentalBuilder {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn id(mut self, id: impl Into<String>) -> Self {
|
|
self.id = Some(id.into());
|
|
self
|
|
}
|
|
|
|
pub fn node_id(mut self, node_id: impl Into<String>) -> Self {
|
|
self.node_id = Some(node_id.into());
|
|
self
|
|
}
|
|
|
|
pub fn renter_email(mut self, email: impl Into<String>) -> 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<Utc>) -> Self {
|
|
self.start_date = Some(date);
|
|
self
|
|
}
|
|
|
|
pub fn end_date(mut self, date: DateTime<Utc>) -> 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<String>) -> Self {
|
|
self.payment_method = Some(method.into());
|
|
self
|
|
}
|
|
|
|
pub fn metadata(mut self, key: impl Into<String>, value: Value) -> Self {
|
|
self.metadata.insert(key.into(), value);
|
|
self
|
|
}
|
|
|
|
pub fn build(self) -> Result<crate::models::user::NodeRental, String> {
|
|
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<String>,
|
|
node_id: Option<String>,
|
|
rental_id: Option<String>,
|
|
renter_email: Option<String>,
|
|
amount: Option<Decimal>,
|
|
currency: Option<String>,
|
|
earning_date: Option<DateTime<Utc>>,
|
|
rental_type: Option<crate::models::user::NodeRentalType>,
|
|
payment_status: Option<crate::models::user::PaymentStatus>,
|
|
}
|
|
|
|
impl ResourceProviderRentalEarningBuilder {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn id(mut self, id: impl Into<String>) -> Self {
|
|
self.id = Some(id.into());
|
|
self
|
|
}
|
|
|
|
pub fn node_id(mut self, node_id: impl Into<String>) -> Self {
|
|
self.node_id = Some(node_id.into());
|
|
self
|
|
}
|
|
|
|
pub fn rental_id(mut self, rental_id: impl Into<String>) -> Self {
|
|
self.rental_id = Some(rental_id.into());
|
|
self
|
|
}
|
|
|
|
pub fn renter_email(mut self, email: impl Into<String>) -> 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<String>) -> Self {
|
|
self.currency = Some(currency.into());
|
|
self
|
|
}
|
|
|
|
pub fn earning_date(mut self, date: DateTime<Utc>) -> 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<crate::models::user::ResourceProviderRentalEarning, String> {
|
|
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<String>,
|
|
user_email: Option<String>,
|
|
activity_type: Option<crate::models::user::ActivityType>,
|
|
description: Option<String>,
|
|
timestamp: Option<chrono::DateTime<chrono::Utc>>,
|
|
metadata: Option<serde_json::Value>,
|
|
category: Option<String>,
|
|
importance: Option<crate::models::user::ActivityImportance>,
|
|
ip_address: Option<String>,
|
|
user_agent: Option<String>,
|
|
session_id: Option<String>,
|
|
}
|
|
|
|
impl UserActivityBuilder {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn id(mut self, id: impl Into<String>) -> 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<String>) -> Self {
|
|
self.description = Some(description.into());
|
|
self
|
|
}
|
|
|
|
pub fn timestamp(mut self, timestamp: chrono::DateTime<chrono::Utc>) -> Self {
|
|
self.timestamp = Some(timestamp);
|
|
self
|
|
}
|
|
|
|
pub fn metadata(mut self, metadata: std::collections::HashMap<String, serde_json::Value>) -> Self {
|
|
self.metadata = Some(serde_json::to_value(metadata).unwrap_or_default());
|
|
self
|
|
}
|
|
|
|
pub fn category(mut self, category: impl Into<String>) -> 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<crate::models::user::UserActivity, String> {
|
|
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<crate::models::user::UserInfo>,
|
|
activities: Option<Vec<crate::models::user::UserActivity>>,
|
|
statistics: Option<crate::models::user::UsageStatistics>,
|
|
services: Option<Vec<crate::models::user::Service>>,
|
|
active_deployments: Option<i32>,
|
|
wallet_summary: Option<crate::models::user::WalletSummary>,
|
|
recommendations: Option<Vec<crate::models::user::Recommendation>>,
|
|
quick_actions: Option<Vec<crate::models::user::QuickAction>>,
|
|
}
|
|
|
|
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<crate::models::user::UserActivity>) -> Self {
|
|
self.activities = Some(activities);
|
|
self
|
|
}
|
|
|
|
pub fn statistics(mut self, statistics: Option<crate::models::user::UsageStatistics>) -> Self {
|
|
self.statistics = statistics;
|
|
self
|
|
}
|
|
|
|
pub fn services(mut self, services: Vec<crate::models::user::Service>) -> 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<crate::models::user::Recommendation>) -> Self {
|
|
self.recommendations = Some(recommendations);
|
|
self
|
|
}
|
|
|
|
pub fn quick_actions(mut self, quick_actions: Vec<crate::models::user::QuickAction>) -> Self {
|
|
self.quick_actions = Some(quick_actions);
|
|
self
|
|
}
|
|
|
|
pub fn build(self) -> Result<std::collections::HashMap<String, serde_json::Value>, 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<crate::services::currency::CurrencyService>,
|
|
include_offline_nodes: Option<bool>,
|
|
price_calculation_method: Option<String>,
|
|
cache_enabled: Option<bool>,
|
|
}
|
|
|
|
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<String>) -> 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<crate::services::node_marketplace::NodeMarketplaceService, String> {
|
|
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<String>,
|
|
location: Option<String>,
|
|
cpu_cores: Option<i32>,
|
|
memory_gb: Option<i32>,
|
|
storage_gb: Option<i32>,
|
|
bandwidth_mbps: Option<i32>,
|
|
region: Option<String>,
|
|
node_type: Option<String>,
|
|
slice_formats: Option<Vec<String>>,
|
|
rental_options: Option<crate::models::user::NodeRentalOptions>,
|
|
slice_prices: Option<std::collections::HashMap<String, rust_decimal::Decimal>>,
|
|
}
|
|
|
|
impl NodeCreationDataBuilder {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn name(mut self, name: impl Into<String>) -> Self {
|
|
self.name = Some(name.into());
|
|
self
|
|
}
|
|
|
|
pub fn location(mut self, location: impl Into<String>) -> 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<String>) -> Self {
|
|
self.region = Some(region.into());
|
|
self
|
|
}
|
|
|
|
pub fn node_type(mut self, node_type: impl Into<String>) -> Self {
|
|
self.node_type = Some(node_type.into());
|
|
self
|
|
}
|
|
|
|
pub fn slice_formats(mut self, formats: Vec<String>) -> 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<String, rust_decimal::Decimal>) -> Self {
|
|
self.slice_prices = Some(prices);
|
|
self
|
|
}
|
|
|
|
pub fn build(self) -> Result<crate::services::resource_provider::NodeCreationData, String> {
|
|
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<rust_decimal::Decimal>,
|
|
daily: Option<rust_decimal::Decimal>,
|
|
monthly: Option<rust_decimal::Decimal>,
|
|
yearly: Option<rust_decimal::Decimal>,
|
|
auto_calculate: Option<bool>,
|
|
daily_discount_percent: Option<f32>,
|
|
monthly_discount_percent: Option<f32>,
|
|
yearly_discount_percent: Option<f32>,
|
|
}
|
|
|
|
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<crate::models::user::FullNodePricing, String> {
|
|
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<Vec<String>>,
|
|
pricing: Option<crate::models::user::FullNodePricing>,
|
|
slice_rental_enabled: Option<bool>,
|
|
full_node_rental_enabled: Option<bool>,
|
|
full_node_pricing: Option<crate::models::user::FullNodePricing>,
|
|
minimum_rental_days: Option<u32>,
|
|
maximum_rental_days: Option<u32>,
|
|
auto_renewal_enabled: Option<bool>,
|
|
}
|
|
|
|
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<crate::models::user::NodeRentalOptions, String> {
|
|
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<String>,
|
|
name: Option<String>,
|
|
description: Option<String>,
|
|
category: Option<String>,
|
|
price_usd: Option<rust_decimal::Decimal>,
|
|
hourly_rate_usd: Option<rust_decimal::Decimal>,
|
|
availability: Option<bool>,
|
|
}
|
|
|
|
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<f64>) -> 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<crate::models::user::Service, String> {
|
|
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<String>,
|
|
customer_email: Option<String>,
|
|
service_id: Option<String>,
|
|
description: Option<String>,
|
|
status: Option<String>,
|
|
estimated_hours: Option<i32>,
|
|
hourly_rate_usd: Option<rust_decimal::Decimal>,
|
|
total_cost_usd: Option<rust_decimal::Decimal>,
|
|
progress_percentage: Option<f32>,
|
|
created_date: Option<String>,
|
|
completed_date: Option<String>,
|
|
}
|
|
|
|
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<crate::models::user::ServiceRequest, String> {
|
|
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<bool>,
|
|
minimum_stake: Option<Decimal>,
|
|
reward_rate: Option<f32>,
|
|
staked_amount: Option<Decimal>,
|
|
staking_start_date: Option<DateTime<Utc>>,
|
|
staking_period_months: Option<u32>,
|
|
early_withdrawal_allowed: Option<bool>,
|
|
early_withdrawal_penalty_percent: Option<f32>,
|
|
}
|
|
|
|
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<Utc>) -> 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<crate::models::user::NodeStakingOptions, String> {
|
|
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<String>,
|
|
app_name: Option<String>,
|
|
status: Option<crate::models::user::DeploymentStatus>,
|
|
cost_per_month: Option<rust_decimal::Decimal>,
|
|
deployed_at: Option<chrono::DateTime<chrono::Utc>>,
|
|
provider: Option<String>,
|
|
region: Option<String>,
|
|
resource_usage: Option<crate::models::user::ResourceUtilization>,
|
|
}
|
|
|
|
impl UserDeploymentBuilder {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn id(mut self, id: impl Into<String>) -> Self {
|
|
self.id = Some(id.into());
|
|
self
|
|
}
|
|
|
|
pub fn app_name(mut self, name: impl Into<String>) -> 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<chrono::Utc>) -> Self {
|
|
self.deployed_at = Some(date);
|
|
self
|
|
}
|
|
|
|
pub fn provider(mut self, provider: impl Into<String>) -> Self {
|
|
self.provider = Some(provider.into());
|
|
self
|
|
}
|
|
|
|
pub fn region(mut self, region: impl Into<String>) -> 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<crate::models::user::UserDeployment, String> {
|
|
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<String>,
|
|
service_id: Option<String>,
|
|
service_name: Option<String>,
|
|
provider_email: Option<String>,
|
|
customer_email: Option<String>,
|
|
budget: Option<Decimal>,
|
|
estimated_hours: Option<i32>,
|
|
hourly_rate_usd: Option<Decimal>,
|
|
progress_percentage: Option<f32>,
|
|
status: Option<String>,
|
|
requested_date: Option<String>,
|
|
priority: Option<String>,
|
|
description: Option<String>,
|
|
booking_date: Option<String>,
|
|
}
|
|
|
|
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<String>) -> 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<ServiceBooking, String> {
|
|
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<i32>,
|
|
completed_bookings: Option<i32>,
|
|
total_spent: Option<Decimal>,
|
|
monthly_spending: Option<Decimal>,
|
|
average_rating_given: Option<f32>,
|
|
service_bookings: Option<Vec<crate::models::user::ServiceBooking>>,
|
|
favorite_providers: Option<Vec<String>>,
|
|
spending_history: Option<Vec<crate::models::user::SpendingRecord>>,
|
|
}
|
|
|
|
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<crate::models::user::ServiceBooking>) -> Self {
|
|
self.service_bookings = Some(bookings);
|
|
self
|
|
}
|
|
|
|
pub fn favorite_providers(mut self, providers: Vec<String>) -> Self {
|
|
self.favorite_providers = Some(providers);
|
|
self
|
|
}
|
|
|
|
pub fn spending_history(mut self, history: Vec<crate::models::user::SpendingRecord>) -> Self {
|
|
self.spending_history = Some(history);
|
|
self
|
|
}
|
|
|
|
pub fn build(self) -> Result<crate::models::user::CustomerServiceData, String> {
|
|
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::<i32>().unwrap_or(0),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl crate::models::user::CustomerServiceData {
|
|
pub fn builder() -> CustomerServiceDataBuilder {
|
|
CustomerServiceDataBuilder::new()
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct SpendingRecordBuilder {
|
|
date: Option<String>,
|
|
amount: Option<f32>,
|
|
service_name: Option<String>,
|
|
provider_name: Option<String>,
|
|
}
|
|
|
|
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<crate::models::user::SpendingRecord, String> {
|
|
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<bool>,
|
|
threshold_amount: Option<Decimal>,
|
|
topup_amount: Option<Decimal>,
|
|
payment_method_id: Option<String>,
|
|
daily_limit: Option<Decimal>,
|
|
monthly_limit: Option<Decimal>,
|
|
}
|
|
|
|
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<String>) -> 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<crate::services::user_persistence::AutoTopUpSettings, String> {
|
|
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<String>,
|
|
name: Option<String>,
|
|
public_key: Option<String>,
|
|
fingerprint: Option<String>,
|
|
key_type: Option<crate::models::ssh_key::SSHKeyType>,
|
|
created_at: Option<DateTime<Utc>>,
|
|
last_used: Option<DateTime<Utc>>,
|
|
is_default: Option<bool>,
|
|
comment: Option<String>,
|
|
}
|
|
|
|
impl SSHKeyBuilder {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn id(mut self, id: impl Into<String>) -> Self {
|
|
self.id = Some(id.into());
|
|
self
|
|
}
|
|
|
|
pub fn name(mut self, name: impl Into<String>) -> Self {
|
|
self.name = Some(name.into());
|
|
self
|
|
}
|
|
|
|
pub fn public_key(mut self, public_key: impl Into<String>) -> Self {
|
|
self.public_key = Some(public_key.into());
|
|
self
|
|
}
|
|
|
|
pub fn fingerprint(mut self, fingerprint: impl Into<String>) -> 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<Utc>) -> Self {
|
|
self.created_at = Some(created_at);
|
|
self
|
|
}
|
|
|
|
pub fn last_used(mut self, last_used: DateTime<Utc>) -> 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<String>) -> Self {
|
|
self.comment = Some(comment.into());
|
|
self
|
|
}
|
|
|
|
pub fn build(self) -> Result<crate::models::ssh_key::SSHKey, String> {
|
|
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()
|
|
}
|
|
} |