Compare commits
1 Commits
developmen
...
developmen
Author | SHA1 | Date | |
---|---|---|---|
|
6569e819ae |
115
heromodels/src/models/tfmarketplace/activity.rs
Normal file
115
heromodels/src/models/tfmarketplace/activity.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
use heromodels_core::BaseModelData;
|
||||
use crate::models::tfmarketplace::user::ResourceUtilization;
|
||||
#[derive(Default)]
|
||||
pub struct UserActivityBuilder {
|
||||
base_data: BaseModelData::new(),
|
||||
// id: Option<String> - moved to base_data,
|
||||
activity_type: Option<crate::models::user::ActivityType>,
|
||||
description: Option<String>,
|
||||
timestamp: Option<chrono::DateTime<chrono::Utc>>,
|
||||
metadata: Option<std::collections::HashMap<String, serde_json::Value>>,
|
||||
category: Option<String>,
|
||||
importance: Option<crate::models::user::ActivityImportance>,
|
||||
}
|
||||
|
||||
impl UserActivityBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn id(mut self) -> Self{
|
||||
self.base_data.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(metadata);
|
||||
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 {
|
||||
base_data: BaseModelData::new(),
|
||||
// id: self.base_data.id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string()) - moved to base_data,
|
||||
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.unwrap_or_default(),
|
||||
category: self.category.unwrap_or_else(|| "General".to_string()),
|
||||
importance: self.importance.unwrap_or(crate::models::user::ActivityImportance::Medium),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// User Activity Tracking
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UserActivity {
|
||||
|
||||
|
||||
/// Base model data (includes id, created_at, updated_at)
|
||||
pub base_data: BaseModelData,
|
||||
pub activity_type: ActivityType,
|
||||
pub description: String,
|
||||
#[serde(deserialize_with = "deserialize_datetime")]
|
||||
pub timestamp: DateTime<Utc>,
|
||||
pub metadata: std::collections::HashMap<String, serde_json::Value>,
|
||||
pub category: String,
|
||||
pub importance: ActivityImportance,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum ActivityType {
|
||||
Login,
|
||||
Purchase,
|
||||
Deployment,
|
||||
ServiceCreated,
|
||||
AppPublished,
|
||||
NodeAdded,
|
||||
NodeUpdated,
|
||||
WalletTransaction,
|
||||
ProfileUpdate,
|
||||
SettingsChange,
|
||||
MarketplaceView,
|
||||
SliceCreated,
|
||||
SliceAllocated,
|
||||
SliceReleased,
|
||||
SliceRentalStarted,
|
||||
SliceRentalStopped,
|
||||
SliceRentalRestarted,
|
||||
SliceRentalCancelled,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum ActivityImportance {
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
Critical,
|
||||
}
|
361
heromodels/src/models/tfmarketplace/app.rs
Normal file
361
heromodels/src/models/tfmarketplace/app.rs
Normal file
@@ -0,0 +1,361 @@
|
||||
use heromodels_core::BaseModelData;
|
||||
use chrono::{DateTime, Utc};
|
||||
use rust_decimal::Decimal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Unified App struct that can represent published apps, deployments, and deployment stats
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct App {
|
||||
/// Base model data (includes id, created_at, updated_at)
|
||||
pub base_data: BaseModelData,
|
||||
|
||||
// Core app information
|
||||
pub name: String,
|
||||
pub category: Option<String>,
|
||||
pub version: Option<String>,
|
||||
pub status: String,
|
||||
|
||||
// Deployment information
|
||||
pub customer_name: Option<String>,
|
||||
pub customer_email: Option<String>,
|
||||
pub deployed_date: Option<String>,
|
||||
pub health_score: Option<f32>,
|
||||
pub region: Option<String>,
|
||||
pub instances: Option<i32>,
|
||||
pub resource_usage: Option<ResourceUtilization>,
|
||||
|
||||
// Business metrics
|
||||
pub deployments: Option<i32>,
|
||||
pub rating: Option<f32>,
|
||||
pub monthly_revenue_usd: Option<i32>,
|
||||
pub cost_per_month: Option<Decimal>,
|
||||
|
||||
// Metadata
|
||||
pub last_updated: Option<String>,
|
||||
pub auto_healing: Option<bool>,
|
||||
pub provider: Option<String>,
|
||||
pub deployed_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
/// Convenience method to get the app ID
|
||||
pub fn id(&self) -> &u32 {
|
||||
&self.base_data.id
|
||||
}
|
||||
|
||||
|
||||
/// Get category with default
|
||||
pub fn category_or_default(&self) -> String {
|
||||
self.category.clone().unwrap_or_else(|| "Application".to_string())
|
||||
}
|
||||
|
||||
/// Get version with default
|
||||
pub fn version_or_default(&self) -> String {
|
||||
self.version.clone().unwrap_or_else(|| "1.0.0".to_string())
|
||||
}
|
||||
|
||||
/// Get deployments count with default
|
||||
pub fn deployments_or_default(&self) -> i32 {
|
||||
self.deployments.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Get rating with default
|
||||
pub fn rating_or_default(&self) -> f32 {
|
||||
self.rating.unwrap_or(4.0)
|
||||
}
|
||||
|
||||
/// Get monthly revenue with default
|
||||
pub fn monthly_revenue_usd_or_default(&self) -> i32 {
|
||||
self.monthly_revenue_usd.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Get last updated with default
|
||||
pub fn last_updated_or_default(&self) -> String {
|
||||
self.last_updated.clone().unwrap_or_else(|| "Unknown".to_string())
|
||||
}
|
||||
|
||||
/// Get auto healing with default
|
||||
pub fn auto_healing_or_default(&self) -> bool {
|
||||
self.auto_healing.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Deployment {
|
||||
pub base_data: BaseModelData,
|
||||
pub app_id: String,
|
||||
pub instance_id: String,
|
||||
pub status: String,
|
||||
pub region: String,
|
||||
pub health_score: Option<f32>,
|
||||
pub resource_usage: Option<ResourceUtilization>,
|
||||
pub deployed_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
/// Resource utilization information
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct ResourceUtilization {
|
||||
pub cpu: i32,
|
||||
pub memory: i32,
|
||||
pub storage: i32,
|
||||
pub network: i32,
|
||||
}
|
||||
|
||||
/// Deployment status enumeration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub enum DeploymentStatus {
|
||||
#[default]
|
||||
Running,
|
||||
Stopped,
|
||||
Failed,
|
||||
Pending,
|
||||
Maintenance,
|
||||
}
|
||||
|
||||
/// Unified App builder
|
||||
#[derive(Default)]
|
||||
pub struct AppBuilder {
|
||||
base_data: BaseModelData,
|
||||
name: Option<String>,
|
||||
category: Option<String>,
|
||||
version: Option<String>,
|
||||
status: Option<String>,
|
||||
customer_name: Option<String>,
|
||||
customer_email: Option<String>,
|
||||
deployed_date: Option<String>,
|
||||
health_score: Option<f32>,
|
||||
region: Option<String>,
|
||||
instances: Option<i32>,
|
||||
resource_usage: Option<ResourceUtilization>,
|
||||
deployments: Option<i32>,
|
||||
rating: Option<f32>,
|
||||
monthly_revenue_usd: Option<i32>,
|
||||
cost_per_month: Option<Decimal>,
|
||||
last_updated: Option<String>,
|
||||
auto_healing: Option<bool>,
|
||||
provider: Option<String>,
|
||||
deployed_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl AppBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
base_data: BaseModelData::new(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
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 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 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 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_usd = Some(revenue);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn cost_per_month(mut self, cost: Decimal) -> Self {
|
||||
self.cost_per_month = Some(cost);
|
||||
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 provider(mut self, provider: impl Into<String>) -> Self {
|
||||
self.provider = Some(provider.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn deployed_at(mut self, date: DateTime<Utc>) -> Self {
|
||||
self.deployed_at = Some(date);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Result<App, String> {
|
||||
Ok(App {
|
||||
base_data: self.base_data,
|
||||
name: self.name.ok_or("name is required")?,
|
||||
category: self.category,
|
||||
version: self.version,
|
||||
status: self.status.unwrap_or_else(|| "Active".to_string()),
|
||||
customer_name: self.customer_name,
|
||||
customer_email: self.customer_email,
|
||||
deployed_date: self.deployed_date,
|
||||
health_score: self.health_score,
|
||||
region: self.region,
|
||||
instances: self.instances,
|
||||
resource_usage: self.resource_usage,
|
||||
deployments: self.deployments,
|
||||
rating: self.rating,
|
||||
monthly_revenue_usd: self.monthly_revenue_usd,
|
||||
cost_per_month: self.cost_per_month,
|
||||
last_updated: self.last_updated,
|
||||
auto_healing: self.auto_healing,
|
||||
provider: self.provider,
|
||||
deployed_at: self.deployed_at,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn builder() -> AppBuilder {
|
||||
AppBuilder::new()
|
||||
}
|
||||
|
||||
// Template methods for common app types
|
||||
pub fn analytics_template(name: &str) -> Self {
|
||||
Self::builder()
|
||||
.name(name)
|
||||
.category("Analytics")
|
||||
.version("1.0.0")
|
||||
.status("Active")
|
||||
.rating(4.5)
|
||||
.auto_healing(true)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn database_template(name: &str) -> Self {
|
||||
Self::builder()
|
||||
.name(name)
|
||||
.category("Database")
|
||||
.version("1.0.0")
|
||||
.status("Active")
|
||||
.rating(4.2)
|
||||
.auto_healing(false) // Databases need manual intervention
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn web_template(name: &str) -> Self {
|
||||
Self::builder()
|
||||
.name(name)
|
||||
.category("Web")
|
||||
.version("1.0.0")
|
||||
.status("Active")
|
||||
.rating(4.0)
|
||||
.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 = Some(deployments);
|
||||
self.rating = Some(rating);
|
||||
self.monthly_revenue_usd = Some(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 = Some(version.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_last_updated(mut self, updated: impl Into<String>) -> Self {
|
||||
self.last_updated = Some(updated.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_deployment_info(mut self, customer_name: &str, customer_email: &str, region: &str) -> Self {
|
||||
self.customer_name = Some(customer_name.to_string());
|
||||
self.customer_email = Some(customer_email.to_string());
|
||||
self.region = Some(region.to_string());
|
||||
self.deployed_at = Some(Utc::now());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_resource_usage(mut self, cpu: i32, memory: i32, storage: i32, network: i32) -> Self {
|
||||
self.resource_usage = Some(ResourceUtilization {
|
||||
cpu,
|
||||
memory,
|
||||
storage,
|
||||
network,
|
||||
});
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// Type aliases for backward compatibility
|
||||
pub type PublishedApp = App;
|
||||
pub type AppDeployment = App;
|
||||
pub type DeploymentStat = App;
|
||||
pub type UserDeployment = App;
|
||||
|
||||
pub type PublishedAppBuilder = AppBuilder;
|
||||
pub type AppDeploymentBuilder = AppBuilder;
|
||||
pub type DeploymentStatBuilder = AppBuilder;
|
||||
pub type UserDeploymentBuilder = AppBuilder;
|
351
heromodels/src/models/tfmarketplace/builders.rs
Normal file
351
heromodels/src/models/tfmarketplace/builders.rs
Normal file
@@ -0,0 +1,351 @@
|
||||
//! 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, MockUserData, ServiceBooking},
|
||||
product::{Product, ProductAttribute, ProductAvailability, ProductMetadata},
|
||||
order::{Order, OrderItem, OrderStatus, PaymentDetails, Address, PurchaseType},
|
||||
};
|
||||
use crate::services::user_persistence::AppDeployment;
|
||||
use heromodels_core::BaseModelData;
|
||||
|
||||
// =============================================================================
|
||||
// USER MODEL BUILDERS
|
||||
// =============================================================================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MockDataBuilder {
|
||||
user_type: Option<String>,
|
||||
include_farmer_data: Option<bool>,
|
||||
include_service_data: Option<bool>,
|
||||
include_app_data: Option<bool>,
|
||||
}
|
||||
|
||||
impl MockDataBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn user_type(mut self, user_type: impl Into<String>) -> Self {
|
||||
self.user_type = Some(user_type.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn include_farmer_data(mut self, include: bool) -> Self {
|
||||
self.include_farmer_data = Some(include);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn include_service_data(mut self, include: bool) -> Self {
|
||||
self.include_service_data = Some(include);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn include_app_data(mut self, include: bool) -> Self {
|
||||
self.include_app_data = Some(include);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> crate::models::user::MockUserData {
|
||||
// This would create appropriate mock data based on configuration
|
||||
// For now, return a default instance
|
||||
crate::models::user::MockUserData::new_user()
|
||||
}
|
||||
}
|
||||
// =============================================================================
|
||||
// FARMER DATA BUILDER
|
||||
// =============================================================================
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FarmerDataBuilder {
|
||||
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 FarmerDataBuilder {
|
||||
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,
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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::FarmerData, String> {
|
||||
Ok(crate::models::user::FarmerData {
|
||||
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,
|
||||
}),
|
||||
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,
|
||||
}),
|
||||
monthly_earnings_usd: self.monthly_earnings.unwrap_or(0),
|
||||
total_earnings_usd: self.total_earnings.unwrap_or(0),
|
||||
uptime_percentage: self.uptime_percentage.unwrap_or(0.0),
|
||||
nodes: self.nodes.unwrap_or_default(),
|
||||
earnings_history: self.earnings_history.unwrap_or_default(),
|
||||
slice_templates: Vec::default(), // Will be populated separately
|
||||
active_slices: self.active_slices.unwrap_or(0),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SERVICE BOOKING BUILDER
|
||||
// =============================================================================
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SpendingRecordBuilder {
|
||||
date: Option<String>,
|
||||
amount: Option<i32>,
|
||||
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: i32) -> 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),
|
||||
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_base_data: BaseModelData::new(),
|
||||
// id: Option<String> - moved to base_data,
|
||||
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) -> 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_base_data: BaseModelData::new(),
|
||||
// id: self.payment_method_id.ok_or("payment_method_id is required")? - moved to base_data,
|
||||
daily_limit_usd: self.daily_limit,
|
||||
monthly_limit_usd: self.monthly_limit,
|
||||
// created_at: chrono::Utc::now() - moved to base_data,
|
||||
// updated_at: chrono::Utc::now() - moved to base_data,
|
||||
})
|
||||
}
|
||||
}
|
105
heromodels/src/models/tfmarketplace/cart.rs
Normal file
105
heromodels/src/models/tfmarketplace/cart.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use chrono::{DateTime, Utc};
|
||||
use rust_decimal::Decimal;
|
||||
use std::collections::HashMap;
|
||||
use heromodels_core::BaseModelData;
|
||||
use crate::models::tfmarketplace::user::ResourceUtilization;
|
||||
|
||||
/// Shopping Cart Models
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CartItem {
|
||||
pub product_id: u32,
|
||||
pub quantity: u32,
|
||||
pub selected_specifications: HashMap<String, serde_json::Value>,
|
||||
pub added_at: DateTime<Utc>,
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Cart {
|
||||
pub base_data: BaseModelData,
|
||||
pub items: Vec<CartItem>,
|
||||
}
|
||||
|
||||
impl Cart {
|
||||
pub fn new() -> Self{
|
||||
let now = Utc::now();
|
||||
Self {
|
||||
base_data: BaseModelData::new(),
|
||||
items: Vec::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_item(&mut self, item: CartItem) {
|
||||
// Check if item already exists and update quantity
|
||||
if let Some(existing_item) = self.items.iter_mut()
|
||||
.find(|i| i.product_id == item.product_id && i.selected_specifications == item.selected_specifications) {
|
||||
existing_item.quantity += item.quantity;
|
||||
} else {
|
||||
self.items.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_item(&mut self, product_id: &str, name: &str) -> bool{
|
||||
let initial_len = self.items.len();
|
||||
self.items.retain(|item| item.product_id != product_id);
|
||||
if self.items.len() != initial_len {
|
||||
self.base_data.updated_at = Utc::now();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_item_quantity(&mut self, product_id: &str, name: &str) -> bool {
|
||||
if let Some(item) = self.items.iter_mut().find(|i| i.product_id == product_id) {
|
||||
if quantity == 0 {
|
||||
return self.remove_item(product_id);
|
||||
}
|
||||
item.quantity = quantity;
|
||||
item.updated_at = Utc::now();
|
||||
self.base_data.updated_at = Utc::now();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.items.clear();
|
||||
self.base_data.updated_at = Utc::now();
|
||||
}
|
||||
|
||||
pub fn get_total_items(&self) -> u32 {
|
||||
self.items.iter().map(|item| item.quantity).sum()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.items.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl CartItem {
|
||||
pub fn new(product_id: &str, name: &str) -> Self {
|
||||
let now = Utc::now();
|
||||
Self {
|
||||
product_id,
|
||||
quantity,
|
||||
selected_specifications: HashMap::default(),
|
||||
added_at: now,
|
||||
// updated_at: now - moved to base_data,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_specifications(
|
||||
product_id: &str, name: &str) -> Self {
|
||||
let now = Utc::now();
|
||||
Self {
|
||||
product_id,
|
||||
quantity,
|
||||
selected_specifications: specifications,
|
||||
added_at: now,
|
||||
// updated_at: now - moved to base_data,
|
||||
}
|
||||
}
|
||||
}
|
90
heromodels/src/models/tfmarketplace/currency.rs
Normal file
90
heromodels/src/models/tfmarketplace/currency.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use chrono::{DateTime, Utc};
|
||||
use rust_decimal::Decimal;
|
||||
use std::collections::HashMap;
|
||||
use heromodels_core::BaseModelData;
|
||||
use heromodels_derive::model;
|
||||
use rhai::CustomType;
|
||||
use crate::models::tfmarketplace::user::ResourceUtilization;
|
||||
|
||||
/// Configurable currency support for any currency type
|
||||
#[model]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)]
|
||||
pub struct Currency {
|
||||
/// Base model data (includes id, created_at, updated_at)
|
||||
pub base_data: BaseModelData,
|
||||
#[index]
|
||||
pub code: String, // USD, EUR, BTC, ETH, etc.
|
||||
pub name: String,
|
||||
pub symbol: String,
|
||||
pub currency_type: CurrencyType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum CurrencyType {
|
||||
Fiat,
|
||||
Cryptocurrency,
|
||||
Token,
|
||||
Points, // For loyalty/reward systems
|
||||
Custom(String), // For marketplace-specific currencies
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Price {
|
||||
pub base_amount: Decimal, // Amount in marketplace base currency
|
||||
pub base_currency: String,
|
||||
pub display_currency: String,
|
||||
pub display_amount: Decimal,
|
||||
pub formatted_display: String,
|
||||
pub conversion_rate: Decimal,
|
||||
pub conversion_timestamp: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl Currency {
|
||||
pub fn new(
|
||||
code: String,
|
||||
name: String,
|
||||
symbol: String,
|
||||
currency_type: CurrencyType,
|
||||
) -> Self {
|
||||
Self {
|
||||
base_data: BaseModelData::new(),
|
||||
code,
|
||||
name,
|
||||
symbol,
|
||||
currency_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Price {
|
||||
pub fn new(
|
||||
base_amount: Decimal,
|
||||
base_currency: String,
|
||||
display_currency: String,
|
||||
conversion_rate: Decimal,
|
||||
) -> Self {
|
||||
let display_amount = base_amount * conversion_rate;
|
||||
// Use proper currency symbol formatting - this will be updated by the currency service
|
||||
Self {
|
||||
base_amount,
|
||||
base_currency: base_currency.clone(),
|
||||
display_currency: display_currency.clone(),
|
||||
display_amount,
|
||||
formatted_display: format!("{} {}", display_amount.round_dp(2), display_currency),
|
||||
conversion_rate,
|
||||
conversion_timestamp: Utc::now(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_with_symbol(&self, symbol: &str) -> String {
|
||||
format!("{} {}",
|
||||
self.display_amount.round_dp(2),
|
||||
symbol
|
||||
)
|
||||
}
|
||||
|
||||
pub fn update_formatted_display(&mut self, formatted: String) {
|
||||
self.formatted_display = formatted;
|
||||
}
|
||||
}
|
30
heromodels/src/models/tfmarketplace/farmer.rs
Normal file
30
heromodels/src/models/tfmarketplace/farmer.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
|
||||
/// Farmer-specific data
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FarmerData {
|
||||
pub total_nodes: i32,
|
||||
pub online_nodes: i32,
|
||||
pub total_capacity: NodeCapacity,
|
||||
pub used_capacity: NodeCapacity,
|
||||
pub monthly_earnings_usd: i32,
|
||||
pub total_earnings_usd: i32,
|
||||
pub uptime_percentage: f32,
|
||||
pub nodes: Vec<FarmNode>,
|
||||
pub earnings_history: Vec<EarningsRecord>,
|
||||
pub slice_templates: Vec<crate::models::product::Product>,
|
||||
pub active_slices: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FarmerSettings {
|
||||
#[serde(default)]
|
||||
pub auto_accept_deployments: bool,
|
||||
#[serde(default = "default_maintenance_window")]
|
||||
pub maintenance_window: String,
|
||||
#[serde(default)]
|
||||
pub notification_preferences: NotificationSettings,
|
||||
pub minimum_deployment_duration: i32, // hours
|
||||
pub preferred_regions: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub default_slice_customizations: Option<std::collections::HashMap<String, serde_json::Value>>, // Placeholder for DefaultSliceFormat
|
||||
}
|
17
heromodels/src/models/tfmarketplace/mod.rs
Normal file
17
heromodels/src/models/tfmarketplace/mod.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
// Export models - starting with basic models first
|
||||
// pub mod user;
|
||||
// pub mod product;
|
||||
// pub mod currency;
|
||||
// pub mod order;
|
||||
// pub mod pool;
|
||||
// pub mod builders; // Re-enabled with essential builders only
|
||||
// pub mod cart;
|
||||
// pub mod payment;
|
||||
// pub mod service;
|
||||
// pub mod slice;
|
||||
// pub mod node;
|
||||
pub mod app;
|
||||
|
||||
// Re-export commonly used types for easier access
|
||||
pub use app::{App, PublishedApp, PublishedAppBuilder, ResourceUtilization, AppBuilder, DeploymentStatus};
|
||||
// pub mod node; // Temporarily disabled - has many service dependencies
|
1660
heromodels/src/models/tfmarketplace/node.rs
Normal file
1660
heromodels/src/models/tfmarketplace/node.rs
Normal file
File diff suppressed because it is too large
Load Diff
8
heromodels/src/models/tfmarketplace/notes.md
Normal file
8
heromodels/src/models/tfmarketplace/notes.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Notes
|
||||
|
||||
all id's of base objects are u32
|
||||
Cart is front end specific,
|
||||
currency and exchange rates should be calculated by client
|
||||
stuff such as decomal numbers related to presentation shouldnt be in base models
|
||||
purchase doesnt need to now wether it is instant or cart
|
||||
all base objects contain created_at and updated_at, so not needed to be added to every model
|
402
heromodels/src/models/tfmarketplace/order.rs
Normal file
402
heromodels/src/models/tfmarketplace/order.rs
Normal file
@@ -0,0 +1,402 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use chrono::{DateTime, Utc};
|
||||
use rust_decimal::Decimal;
|
||||
use std::collections::HashMap;
|
||||
use heromodels_core::BaseModelData;
|
||||
use heromodels_derive::model;
|
||||
use rhai::CustomType;
|
||||
use crate::models::tfmarketplace::user::ResourceUtilization;
|
||||
|
||||
#[model]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)]
|
||||
pub struct Order {
|
||||
/// Base model data (includes id, created_at, updated_at)
|
||||
pub base_data: BaseModelData,
|
||||
#[index]
|
||||
pub user_base_data: BaseModelData::new(),
|
||||
// id: String - moved to base_data,
|
||||
pub items: Vec<OrderItem>,
|
||||
pub subtotal_base: Decimal, // In base currency
|
||||
pub total_base: Decimal, // In base currency
|
||||
pub base_currency: String,
|
||||
pub currency_used: String, // Currency user paid in
|
||||
pub currency_total: Decimal, // Amount in user's currency
|
||||
pub conversion_rate: Decimal, // Rate used for conversion
|
||||
pub status: OrderStatus,
|
||||
pub payment_method: String,
|
||||
pub payment_details: Option<PaymentDetails>,
|
||||
pub billing_address: Option<Address>,
|
||||
pub shipping_address: Option<Address>,
|
||||
pub notes: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct OrderItem {
|
||||
pub product_base_data: BaseModelData::new(),
|
||||
// id: String - moved to base_data,
|
||||
pub product_name: String,
|
||||
pub product_category: String,
|
||||
pub quantity: u32,
|
||||
pub unit_price_base: Decimal, // In base currency
|
||||
pub total_price_base: Decimal, // In base currency
|
||||
pub specifications: HashMap<String, serde_json::Value>,
|
||||
pub provider_base_data: BaseModelData::new(),
|
||||
// id: String - moved to base_data,
|
||||
pub provider_name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum OrderStatus {
|
||||
Pending,
|
||||
Confirmed,
|
||||
Processing,
|
||||
Deployed,
|
||||
Completed,
|
||||
Cancelled,
|
||||
Refunded,
|
||||
Failed,
|
||||
}
|
||||
|
||||
/// Order summary for display purposes
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct OrderSummary {
|
||||
pub subtotal: Decimal,
|
||||
pub tax: Decimal,
|
||||
pub shipping: Decimal,
|
||||
pub discount: Decimal,
|
||||
pub total: Decimal,
|
||||
pub currency: String,
|
||||
pub item_count: u32,
|
||||
}
|
||||
|
||||
impl Order {
|
||||
pub fn new(
|
||||
base_data: BaseModelData::new(),
|
||||
// id: String - moved to base_data,
|
||||
user_base_data: BaseModelData::new(),
|
||||
// id: String - moved to base_data,
|
||||
base_currency: String,
|
||||
currency_used: String,
|
||||
conversion_rate: Decimal,
|
||||
) -> Self {
|
||||
Self {
|
||||
base_data: BaseModelData::new(),
|
||||
user_id,
|
||||
items: Vec::default(),
|
||||
subtotal_base: Decimal::from(0),
|
||||
total_base: Decimal::from(0),
|
||||
base_currency,
|
||||
currency_used,
|
||||
currency_total: Decimal::from(0),
|
||||
conversion_rate,
|
||||
status: OrderStatus::Pending,
|
||||
payment_method: String::new(),
|
||||
payment_details: None,
|
||||
billing_address: None,
|
||||
shipping_address: None,
|
||||
notes: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_item(&mut self, item: OrderItem) {
|
||||
self.items.push(item);
|
||||
self.calculate_totals();
|
||||
}
|
||||
|
||||
pub fn calculate_totals(&mut self) {
|
||||
self.subtotal_base = self.items.iter()
|
||||
.map(|item| item.total_price_base)
|
||||
.sum();
|
||||
self.total_base = self.subtotal_base; // Add taxes, fees, etc. here
|
||||
self.currency_total = self.total_base * self.conversion_rate;
|
||||
self.base_data.modified_at = Utc::now().timestamp();
|
||||
}
|
||||
|
||||
pub fn update_status(&mut self, status: OrderStatus) {
|
||||
self.status = status;
|
||||
self.base_data.modified_at = Utc::now().timestamp();
|
||||
}
|
||||
|
||||
pub fn set_payment_details(&mut self, payment_details: PaymentDetails) {
|
||||
self.payment_details = Some(payment_details);
|
||||
self.base_data.modified_at = Utc::now().timestamp();
|
||||
}
|
||||
|
||||
pub fn get_item_count(&self) -> u32 {
|
||||
self.items.iter().map(|item| item.quantity).sum()
|
||||
}
|
||||
}
|
||||
|
||||
impl OrderItem {
|
||||
pub fn new(
|
||||
product_base_data: BaseModelData::new(),
|
||||
// id: String - moved to base_data,
|
||||
product_name: String,
|
||||
product_category: String,
|
||||
quantity: u32,
|
||||
unit_price_base: Decimal,
|
||||
provider_base_data: BaseModelData::new(),
|
||||
// id: String - moved to base_data,
|
||||
provider_name: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
product_id,
|
||||
product_name,
|
||||
product_category,
|
||||
quantity,
|
||||
unit_price_base,
|
||||
total_price_base: unit_price_base * Decimal::from(quantity),
|
||||
specifications: HashMap::default(),
|
||||
provider_id,
|
||||
provider_name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_specification(&mut self, key: String, value: serde_json::Value) {
|
||||
self.specifications.insert(key, value);
|
||||
}
|
||||
|
||||
pub fn update_quantity(&mut self, quantity: u32) {
|
||||
self.quantity = quantity;
|
||||
self.total_price_base = self.unit_price_base * Decimal::from(quantity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct OrderBuilder {
|
||||
base_data: BaseModelData::new(),
|
||||
// id: Option<String> - moved to base_data,
|
||||
user_base_data: BaseModelData::new(),
|
||||
// id: Option<String> - moved to base_data,
|
||||
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>> - moved to base_data,
|
||||
// updated_at: Option<DateTime<Utc>> - moved to base_data,
|
||||
}
|
||||
|
||||
impl OrderBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn id(mut self) -> Self{
|
||||
self.base_data.id = Some(id.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn user_id(mut self, user_id: &str, name: &str) -> 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 {
|
||||
base_data: BaseModelData::new(),
|
||||
// id: self.base_data.id.ok_or("id is required")? - moved to base_data,
|
||||
user_base_data: BaseModelData::new(),
|
||||
// id: self.user_id.ok_or("user_id is required")? - moved to base_data,
|
||||
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.base_data.created_at.unwrap_or(now) - moved to base_data,
|
||||
// updated_at: self.base_data.updated_at.unwrap_or(now) - moved to base_data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Order {
|
||||
pub fn builder() -> OrderBuilder {
|
||||
OrderBuilder::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct OrderItemBuilder {
|
||||
product_base_data: BaseModelData::new(),
|
||||
// id: Option<String> - moved to base_data,
|
||||
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_base_data: BaseModelData::new(),
|
||||
// id: Option<String> - moved to base_data,
|
||||
provider_name: Option<String>,
|
||||
}
|
||||
|
||||
impl OrderItemBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn product_id(mut self) -> 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) -> 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_base_data: BaseModelData::new(),
|
||||
// id: self.product_id.ok_or("product_id is required")? - moved to base_data,
|
||||
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_base_data: BaseModelData::new(),
|
||||
// id: self.provider_id.ok_or("provider_id is required")? - moved to base_data,
|
||||
provider_name: self.provider_name.ok_or("provider_name is required")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl OrderItem {
|
||||
pub fn builder() -> OrderItemBuilder {
|
||||
OrderItemBuilder::new()
|
||||
}
|
||||
}
|
77
heromodels/src/models/tfmarketplace/payment.rs
Normal file
77
heromodels/src/models/tfmarketplace/payment.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use chrono::{DateTime, Utc};
|
||||
use rust_decimal::Decimal;
|
||||
use std::collections::HashMap;
|
||||
use heromodels_core::BaseModelData;
|
||||
use crate::models::tfmarketplace::user::ResourceUtilization;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PaymentDetails {
|
||||
pub payment_base_data: BaseModelData::new(),
|
||||
// id: String - moved to base_data,
|
||||
pub payment_method: PaymentMethod,
|
||||
pub transaction_base_data: BaseModelData::new(),
|
||||
// id: Option<String> - moved to base_data,
|
||||
pub payment_status: PaymentStatus,
|
||||
pub payment_timestamp: Option<DateTime<Utc>>,
|
||||
pub failure_reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum PaymentMethod {
|
||||
CreditCard {
|
||||
last_four: String,
|
||||
card_type: String,
|
||||
},
|
||||
BankTransfer {
|
||||
bank_name: String,
|
||||
account_last_four: String,
|
||||
},
|
||||
Cryptocurrency {
|
||||
currency: String,
|
||||
wallet_address: String,
|
||||
},
|
||||
Token {
|
||||
token_type: String,
|
||||
wallet_address: String,
|
||||
},
|
||||
Mock {
|
||||
method_name: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum PaymentStatus {
|
||||
Pending,
|
||||
Processing,
|
||||
Completed,
|
||||
Failed,
|
||||
Cancelled,
|
||||
Refunded,
|
||||
}
|
||||
|
||||
impl PaymentDetails {
|
||||
pub fn new(payment_id: &str, name: &str) -> Self {
|
||||
Self {
|
||||
payment_id,
|
||||
payment_method,
|
||||
transaction_base_data: BaseModelData::new(),
|
||||
// id: None - moved to base_data,
|
||||
payment_status: PaymentStatus::Pending,
|
||||
payment_timestamp: None,
|
||||
failure_reason: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mark_completed(&mut self, transaction_id: String) { - moved to base_data
|
||||
self.transaction_id = Some(transaction_id);
|
||||
self.payment_status = PaymentStatus::Completed;
|
||||
self.payment_timestamp = Some(Utc::now());
|
||||
}
|
||||
|
||||
pub fn mark_failed(&mut self, reason: String) {
|
||||
self.payment_status = PaymentStatus::Failed;
|
||||
self.failure_reason = Some(reason);
|
||||
self.payment_timestamp = Some(Utc::now());
|
||||
}
|
||||
}
|
105
heromodels/src/models/tfmarketplace/pool.rs
Normal file
105
heromodels/src/models/tfmarketplace/pool.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use rust_decimal::Decimal;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use heromodels_core::BaseModelData;
|
||||
use crate::models::tfmarketplace::user::ResourceUtilization;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LiquidityPool {
|
||||
|
||||
|
||||
/// Base model data (includes id, created_at, updated_at)
|
||||
pub base_data: BaseModelData,
|
||||
pub name: String,
|
||||
pub token_a: String,
|
||||
pub token_b: String,
|
||||
pub reserve_a: Decimal,
|
||||
pub reserve_b: Decimal,
|
||||
pub exchange_rate: Decimal,
|
||||
pub liquidity: Decimal,
|
||||
pub volume_24h: Decimal,
|
||||
pub fee_percentage: Decimal,
|
||||
pub status: PoolStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum PoolStatus {
|
||||
Active,
|
||||
Paused,
|
||||
Maintenance,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ExchangeRequest {
|
||||
pub pool_base_data: BaseModelData::new(),
|
||||
// id: String - moved to base_data,
|
||||
pub from_token: String,
|
||||
pub to_token: String,
|
||||
pub amount: Decimal,
|
||||
pub min_receive: Option<Decimal>,
|
||||
pub slippage_tolerance: Option<Decimal>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ExchangeResponse {
|
||||
pub success: bool,
|
||||
pub message: String,
|
||||
pub transaction_base_data: BaseModelData::new(),
|
||||
// id: Option<String> - moved to base_data,
|
||||
pub from_amount: Option<Decimal>,
|
||||
pub to_amount: Option<Decimal>,
|
||||
pub exchange_rate: Option<Decimal>,
|
||||
pub fee: Option<Decimal>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StakeRequest {
|
||||
pub amount: Decimal,
|
||||
pub duration_months: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StakePosition {
|
||||
|
||||
|
||||
/// Base model data (includes id, created_at, updated_at)
|
||||
pub base_data: BaseModelData,
|
||||
pub user_base_data: BaseModelData::new(),
|
||||
// id: String - moved to base_data,
|
||||
pub amount: Decimal,
|
||||
pub start_date: DateTime<Utc>,
|
||||
pub end_date: DateTime<Utc>,
|
||||
pub discount_percentage: Decimal,
|
||||
pub reputation_bonus: i32,
|
||||
pub status: StakeStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum StakeStatus {
|
||||
Active,
|
||||
Completed,
|
||||
Withdrawn,
|
||||
}
|
||||
|
||||
/// Pool analytics data
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PoolAnalytics {
|
||||
pub price_history: Vec<PricePoint>,
|
||||
pub volume_history: Vec<VolumePoint>,
|
||||
pub liquidity_distribution: HashMap<String, Decimal>,
|
||||
pub staking_distribution: HashMap<String, i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PricePoint {
|
||||
pub timestamp: DateTime<Utc>,
|
||||
pub price: Decimal,
|
||||
pub volume: Decimal,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VolumePoint {
|
||||
pub date: String,
|
||||
pub volume: Decimal,
|
||||
}
|
660
heromodels/src/models/tfmarketplace/product.rs
Normal file
660
heromodels/src/models/tfmarketplace/product.rs
Normal file
@@ -0,0 +1,660 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use chrono::{DateTime, Utc};
|
||||
use rust_decimal::Decimal;
|
||||
use std::collections::HashMap;
|
||||
use heromodels_core::BaseModelData;
|
||||
use heromodels_derive::model;
|
||||
use rhai::CustomType;
|
||||
|
||||
/// Generic product structure that can represent any marketplace item
|
||||
#[model]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, CustomType)]
|
||||
pub struct Product {
|
||||
/// Base model data (includes id, created_at, updated_at)
|
||||
pub base_data: BaseModelData,
|
||||
#[index]
|
||||
pub name: String,
|
||||
pub category: ProductCategory,
|
||||
pub description: String,
|
||||
pub price: Price,
|
||||
pub attributes: HashMap<String, ProductAttribute>, // Generic attributes
|
||||
pub provider_base_data: BaseModelData::new(),
|
||||
// id: String - moved to base_data,
|
||||
pub provider_name: String,
|
||||
pub availability: ProductAvailability,
|
||||
pub metadata: ProductMetadata, // Extensible metadata
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Price {
|
||||
pub base_amount: Decimal,
|
||||
pub currency: u32,
|
||||
}
|
||||
|
||||
/// Configurable product categories
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ProductCategory {
|
||||
|
||||
|
||||
/// Base model data (includes id, created_at, updated_at)
|
||||
pub base_data: BaseModelData,
|
||||
pub name: String,
|
||||
pub display_name: String,
|
||||
pub description: String,
|
||||
pub attribute_schema: Vec<AttributeDefinition>, // Defines allowed attributes
|
||||
pub parent_category: Option<String>,
|
||||
pub is_active: bool,
|
||||
}
|
||||
|
||||
/// Generic attribute system for any product type
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct ProductAttribute {
|
||||
pub key: String,
|
||||
pub value: serde_json::Value,
|
||||
pub attribute_type: AttributeType,
|
||||
pub is_searchable: bool,
|
||||
pub is_filterable: bool,
|
||||
pub display_order: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum AttributeType {
|
||||
Text,
|
||||
Number,
|
||||
SliceConfiguration,
|
||||
Boolean,
|
||||
Select(Vec<String>), // Predefined options
|
||||
MultiSelect(Vec<String>),
|
||||
Range { min: f64, max: f64 },
|
||||
Custom(String), // For marketplace-specific types
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AttributeDefinition {
|
||||
pub key: String,
|
||||
pub name: String,
|
||||
pub attribute_type: AttributeType,
|
||||
pub is_required: bool,
|
||||
pub is_searchable: bool,
|
||||
pub is_filterable: bool,
|
||||
pub validation_rules: Vec<ValidationRule>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum ValidationRule {
|
||||
MinLength(usize),
|
||||
MaxLength(usize),
|
||||
MinValue(f64),
|
||||
MaxValue(f64),
|
||||
Pattern(String),
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum ProductAvailability {
|
||||
Available,
|
||||
Limited,
|
||||
Unavailable,
|
||||
PreOrder,
|
||||
Custom(String), // For marketplace-specific availability states
|
||||
}
|
||||
|
||||
impl Default for ProductAvailability {
|
||||
fn default() -> Self {
|
||||
ProductAvailability::Available
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum ProductVisibility {
|
||||
Public,
|
||||
Private,
|
||||
Draft,
|
||||
Archived,
|
||||
}
|
||||
|
||||
impl Default for ProductVisibility {
|
||||
fn default() -> Self {
|
||||
ProductVisibility::Public
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
|
||||
pub struct ProductMetadata {
|
||||
pub tags: Vec<String>,
|
||||
pub location: Option<String>,
|
||||
pub rating: Option<f32>,
|
||||
pub review_count: u32,
|
||||
pub featured: bool,
|
||||
pub last_updated: chrono::DateTime<chrono::Utc>,
|
||||
pub visibility: ProductVisibility,
|
||||
pub seo_keywords: Vec<String>,
|
||||
pub custom_fields: HashMap<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
/// Support for different pricing models
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum PricingModel {
|
||||
OneTime, // Single purchase
|
||||
Recurring { interval: String }, // Subscription
|
||||
UsageBased { unit: String }, // Pay per use
|
||||
Tiered(Vec<PriceTier>), // Volume discounts
|
||||
Custom(String), // Marketplace-specific
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PriceTier {
|
||||
pub min_quantity: u32,
|
||||
pub max_quantity: Option<u32>,
|
||||
pub price_per_unit: Decimal,
|
||||
pub discount_percentage: Option<f32>,
|
||||
}
|
||||
|
||||
impl Product {
|
||||
pub fn new(
|
||||
name: String,
|
||||
category: ProductCategory,
|
||||
description: String,
|
||||
price: Price,
|
||||
provider_base_data: BaseModelData::new(),
|
||||
// id: String - moved to base_data,
|
||||
provider_name: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
base_data: BaseModelData::new(),
|
||||
name,
|
||||
category,
|
||||
description,
|
||||
price,
|
||||
attributes: HashMap::default(),
|
||||
provider_id,
|
||||
provider_name,
|
||||
availability: ProductAvailability::Available,
|
||||
metadata: ProductMetadata {
|
||||
tags: Vec::default(),
|
||||
location: None,
|
||||
rating: None,
|
||||
review_count: 0,
|
||||
featured: false,
|
||||
last_updated: chrono::Utc::now(),
|
||||
visibility: ProductVisibility::Public,
|
||||
seo_keywords: Vec::new(),
|
||||
custom_fields: HashMap::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_attribute(&mut self, key: String, value: serde_json::Value, attribute_type: AttributeType) {
|
||||
let attribute = ProductAttribute {
|
||||
key: key.clone(),
|
||||
value,
|
||||
attribute_type,
|
||||
is_searchable: true,
|
||||
is_filterable: true,
|
||||
display_order: None,
|
||||
};
|
||||
self.attributes.insert(key, attribute);
|
||||
self.base_data.modified_at = Utc::now().timestamp();
|
||||
}
|
||||
|
||||
pub fn set_featured(&mut self, featured: bool) {
|
||||
self.metadata.featured = featured;
|
||||
self.base_data.modified_at = Utc::now().timestamp();
|
||||
}
|
||||
|
||||
pub fn add_tag(&mut self, tag: String) {
|
||||
if !self.metadata.tags.contains(&tag) {
|
||||
self.metadata.tags.push(tag);
|
||||
self.base_data.modified_at = Utc::now().timestamp();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_rating(&mut self, rating: f32, review_count: u32) {
|
||||
self.metadata.rating = Some(rating);
|
||||
self.metadata.review_count = review_count;
|
||||
self.base_data.modified_at = Utc::now().timestamp();
|
||||
}
|
||||
}
|
||||
|
||||
impl ProductCategory {
|
||||
pub fn new() -> Self {
|
||||
// id: String - moved to base_data, name: String, display_name: String, description: String) -> Self {
|
||||
Self {
|
||||
base_data: BaseModelData::new(),
|
||||
name,
|
||||
display_name,
|
||||
description,
|
||||
attribute_schema: Vec::default(),
|
||||
parent_category: None,
|
||||
is_active: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add attribute definition to category schema
|
||||
pub fn add_attribute_definition(&mut self, definition: AttributeDefinition) {
|
||||
self.attribute_schema.push(definition);
|
||||
}
|
||||
}
|
||||
|
||||
impl Product {
|
||||
/// Create a slice product from farmer configuration
|
||||
pub fn create_slice_product(
|
||||
base_data: BaseModelData::new(),
|
||||
// id: String - moved to base_data,
|
||||
farmer_name: String,
|
||||
slice_name: String,
|
||||
slice_config: SliceConfiguration,
|
||||
price_per_hour: Decimal,
|
||||
) -> Self {
|
||||
let category = ProductCategory {
|
||||
base_data: BaseModelData::new(),
|
||||
// id: "compute_slices".to_string() - moved to base_data,
|
||||
name: "Compute Slices".to_string(),
|
||||
display_name: "Compute Slices".to_string(),
|
||||
description: "Virtual compute resources".to_string(),
|
||||
attribute_schema: Vec::new(),
|
||||
parent_category: None,
|
||||
is_active: true,
|
||||
};
|
||||
let price = Price {
|
||||
base_amount: price_per_hour,
|
||||
currency: 1, // USD currency ID
|
||||
};
|
||||
let mut product = Self::new(
|
||||
base_data,
|
||||
slice_name,
|
||||
category,
|
||||
format!("Compute slice with {} vCPU, {}GB RAM, {}GB storage",
|
||||
slice_config.cpu_cores, slice_config.memory_gb, slice_config.storage_gb),
|
||||
price,
|
||||
farmer_id,
|
||||
farmer_name,
|
||||
);
|
||||
|
||||
// Add slice-specific attributes
|
||||
product.add_attribute(
|
||||
"cpu_cores".to_string(),
|
||||
serde_json::Value::Number(serde_json::Number::from(slice_config.cpu_cores)),
|
||||
AttributeType::Number,
|
||||
);
|
||||
|
||||
product.add_attribute(
|
||||
"memory_gb".to_string(),
|
||||
serde_json::Value::Number(serde_json::Number::from(slice_config.memory_gb)),
|
||||
AttributeType::Number,
|
||||
);
|
||||
|
||||
product.add_attribute(
|
||||
"storage_gb".to_string(),
|
||||
serde_json::Value::Number(serde_json::Number::from(slice_config.storage_gb)),
|
||||
AttributeType::Number,
|
||||
);
|
||||
|
||||
product.add_attribute(
|
||||
"bandwidth_mbps".to_string(),
|
||||
serde_json::Value::Number(serde_json::Number::from(slice_config.bandwidth_mbps)),
|
||||
AttributeType::Number,
|
||||
);
|
||||
|
||||
product.add_attribute(
|
||||
"min_uptime_sla".to_string(),
|
||||
serde_json::Value::Number(serde_json::Number::from_f64(slice_config.min_uptime_sla as f64).unwrap()),
|
||||
AttributeType::Number,
|
||||
);
|
||||
|
||||
product.add_attribute(
|
||||
"public_ips".to_string(),
|
||||
serde_json::Value::Number(serde_json::Number::from(slice_config.public_ips)),
|
||||
AttributeType::Number,
|
||||
);
|
||||
|
||||
if let Some(ref node_id) = slice_config.node_id {
|
||||
product.add_attribute(
|
||||
"node_id".to_string(),
|
||||
serde_json::Value::String(node_id.clone()),
|
||||
AttributeType::Text,
|
||||
);
|
||||
}
|
||||
|
||||
product.add_attribute(
|
||||
"slice_type".to_string(),
|
||||
serde_json::Value::String(format!("{:?}", slice_config.slice_type)),
|
||||
AttributeType::Text,
|
||||
);
|
||||
|
||||
// Add slice configuration as a complex attribute
|
||||
product.add_attribute(
|
||||
"slice_configuration".to_string(),
|
||||
serde_json::to_value(&slice_config).unwrap(),
|
||||
AttributeType::SliceConfiguration,
|
||||
);
|
||||
|
||||
// Add relevant tags
|
||||
product.add_tag("compute".to_string());
|
||||
product.add_tag("slice".to_string());
|
||||
product.add_tag(format!("{:?}", slice_config.slice_type).to_lowercase());
|
||||
|
||||
product
|
||||
}
|
||||
|
||||
/// Check if this product is a slice
|
||||
pub fn is_slice(&self) -> bool {
|
||||
self.category.id == "compute_slices" ||
|
||||
self.attributes.contains_key("slice_configuration")
|
||||
}
|
||||
|
||||
/// Get slice configuration from product attributes
|
||||
pub fn get_slice_configuration(&self) -> Option<SliceConfiguration> {
|
||||
self.attributes.get("slice_configuration")
|
||||
.and_then(|attr| serde_json::from_value(attr.value.clone()).ok())
|
||||
}
|
||||
|
||||
/// Update slice configuration
|
||||
pub fn update_slice_configuration(&mut self, config: SliceConfiguration) {
|
||||
if self.is_slice() {
|
||||
self.add_attribute(
|
||||
"slice_configuration".to_string(),
|
||||
serde_json::to_value(&config).unwrap(),
|
||||
AttributeType::SliceConfiguration,
|
||||
);
|
||||
|
||||
// Update individual attributes for searchability
|
||||
self.add_attribute(
|
||||
"cpu_cores".to_string(),
|
||||
serde_json::Value::Number(serde_json::Number::from(config.cpu_cores)),
|
||||
AttributeType::Number,
|
||||
);
|
||||
|
||||
self.add_attribute(
|
||||
"memory_gb".to_string(),
|
||||
serde_json::Value::Number(serde_json::Number::from(config.memory_gb)),
|
||||
AttributeType::Number,
|
||||
);
|
||||
|
||||
self.add_attribute(
|
||||
"storage_gb".to_string(),
|
||||
serde_json::Value::Number(serde_json::Number::from(config.storage_gb)),
|
||||
AttributeType::Number,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if slice fits within node capacity
|
||||
pub fn slice_fits_in_node(&self, node_capacity: &crate::models::user::NodeCapacity) -> bool {
|
||||
if let Some(config) = self.get_slice_configuration() {
|
||||
config.cpu_cores <= node_capacity.cpu_cores &&
|
||||
config.memory_gb <= node_capacity.memory_gb &&
|
||||
config.storage_gb <= node_capacity.storage_gb &&
|
||||
config.bandwidth_mbps <= node_capacity.bandwidth_mbps
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
/// Create a full node product from a FarmNode
|
||||
pub fn create_full_node_product(
|
||||
node: &crate::models::user::FarmNode,
|
||||
farmer_email: &str,
|
||||
farmer_name: &str,
|
||||
) -> Self {
|
||||
let category = ProductCategory {
|
||||
base_data: BaseModelData::new(),
|
||||
// id: "3nodes".to_string() - moved to base_data,
|
||||
name: "3Nodes".to_string(),
|
||||
display_name: "3Nodes".to_string(),
|
||||
description: "Full node rentals".to_string(),
|
||||
attribute_schema: Vec::new(),
|
||||
parent_category: None,
|
||||
is_active: true,
|
||||
};
|
||||
let price = Price {
|
||||
base_amount: node.rental_options
|
||||
.as_ref()
|
||||
.and_then(|opts| opts.full_node_pricing.as_ref())
|
||||
.map(|pricing| pricing.monthly)
|
||||
.unwrap_or_else(|| Decimal::from(200)), // Default price
|
||||
currency: 1, // USD currency ID
|
||||
};
|
||||
let mut product = Product {
|
||||
base_data: BaseModelData::new(),
|
||||
name: format!("Full Node: {}", node.name),
|
||||
category,
|
||||
description: format!(
|
||||
"Exclusive access to {} with {} CPU cores, {}GB RAM, {}GB storage in {}",
|
||||
node.name, node.capacity.cpu_cores, node.capacity.memory_gb,
|
||||
node.capacity.storage_gb, node.location
|
||||
),
|
||||
price,
|
||||
attributes: HashMap::new(),
|
||||
provider_base_data: BaseModelData::new(),
|
||||
// id: farmer_email.to_string() - moved to base_data,
|
||||
provider_name: farmer_name.to_string(),
|
||||
availability: match node.availability_status {
|
||||
crate::models::user::NodeAvailabilityStatus::Available => ProductAvailability::Available,
|
||||
crate::models::user::NodeAvailabilityStatus::PartiallyRented => ProductAvailability::Limited,
|
||||
_ => ProductAvailability::Unavailable,
|
||||
},
|
||||
metadata: ProductMetadata {
|
||||
tags: vec!["full-node".to_string(), "exclusive".to_string(), node.region.clone()],
|
||||
location: Some(node.location.clone()),
|
||||
rating: None,
|
||||
review_count: 0,
|
||||
featured: false,
|
||||
last_updated: chrono::Utc::now(),
|
||||
visibility: ProductVisibility::Public,
|
||||
seo_keywords: Vec::new(),
|
||||
custom_fields: HashMap::new(),
|
||||
},
|
||||
};
|
||||
|
||||
// Add node-specific attributes
|
||||
product.add_attribute(
|
||||
"node_id".to_string(),
|
||||
serde_json::Value::String(node.id.clone()),
|
||||
AttributeType::Text,
|
||||
);
|
||||
|
||||
product.add_attribute(
|
||||
"rental_type".to_string(),
|
||||
serde_json::Value::String("full_node".to_string()),
|
||||
AttributeType::Text,
|
||||
);
|
||||
|
||||
product.add_attribute(
|
||||
"cpu_cores".to_string(),
|
||||
serde_json::Value::Number(serde_json::Number::from(node.capacity.cpu_cores)),
|
||||
AttributeType::Number,
|
||||
);
|
||||
|
||||
product.add_attribute(
|
||||
"memory_gb".to_string(),
|
||||
serde_json::Value::Number(serde_json::Number::from(node.capacity.memory_gb)),
|
||||
AttributeType::Number,
|
||||
);
|
||||
|
||||
product.add_attribute(
|
||||
"storage_gb".to_string(),
|
||||
serde_json::Value::Number(serde_json::Number::from(node.capacity.storage_gb)),
|
||||
AttributeType::Number,
|
||||
);
|
||||
|
||||
product.add_attribute(
|
||||
"bandwidth_mbps".to_string(),
|
||||
serde_json::Value::Number(serde_json::Number::from(node.capacity.bandwidth_mbps)),
|
||||
AttributeType::Number,
|
||||
);
|
||||
|
||||
product.add_attribute(
|
||||
"location".to_string(),
|
||||
serde_json::Value::String(node.location.clone()),
|
||||
AttributeType::Text,
|
||||
);
|
||||
|
||||
product.add_attribute(
|
||||
"uptime_percentage".to_string(),
|
||||
serde_json::Value::Number(serde_json::Number::from_f64(node.uptime_percentage as f64).unwrap_or_else(|| serde_json::Number::from(0))),
|
||||
AttributeType::Number,
|
||||
);
|
||||
|
||||
product.add_attribute(
|
||||
"health_score".to_string(),
|
||||
serde_json::Value::Number(serde_json::Number::from_f64(node.health_score as f64).unwrap_or_else(|| serde_json::Number::from(0))),
|
||||
AttributeType::Number,
|
||||
);
|
||||
|
||||
product
|
||||
}
|
||||
|
||||
/// Check if this product represents a full node
|
||||
pub fn is_full_node(&self) -> bool {
|
||||
self.attributes.get("rental_type")
|
||||
.and_then(|attr| attr.value.as_str())
|
||||
.map(|s| s == "full_node")
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Get the node ID if this is a node product
|
||||
pub fn get_node_id(&self) -> Option<String> {
|
||||
self.attributes.get("node_id")
|
||||
.and_then(|attr| attr.value.as_str())
|
||||
.map(|s| s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl ProductCategory {
|
||||
pub fn set_parent_category(&mut self, parent_id: String) {
|
||||
self.parent_category = Some(parent_id);
|
||||
}
|
||||
}
|
||||
|
||||
impl AttributeDefinition {
|
||||
pub fn new(
|
||||
key: String,
|
||||
name: String,
|
||||
attribute_type: AttributeType,
|
||||
is_required: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
key,
|
||||
name,
|
||||
attribute_type,
|
||||
is_required,
|
||||
is_searchable: true,
|
||||
is_filterable: true,
|
||||
validation_rules: Vec::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_validation_rule(&mut self, rule: ValidationRule) {
|
||||
self.validation_rules.push(rule);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ProductBuilder {
|
||||
base_data: BaseModelData::new(),
|
||||
// id: Option<String> - moved to base_data,
|
||||
name: Option<String>,
|
||||
category_base_data: BaseModelData::new(),
|
||||
// id: Option<String> - moved to base_data,
|
||||
description: Option<String>,
|
||||
base_price: Option<Decimal>,
|
||||
base_currency: Option<String>,
|
||||
attributes: HashMap<String, ProductAttribute>,
|
||||
provider_base_data: BaseModelData::new(),
|
||||
// id: Option<String> - moved to base_data,
|
||||
provider_name: Option<String>,
|
||||
availability: Option<ProductAvailability>,
|
||||
metadata: Option<ProductMetadata>,
|
||||
// created_at: Option<DateTime<Utc>> - moved to base_data,
|
||||
// updated_at: Option<DateTime<Utc>> - moved to base_data,
|
||||
}
|
||||
|
||||
impl ProductBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn id(mut self, id: impl Into<String>) -> Self {
|
||||
self.base_data.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 {
|
||||
base_data: BaseModelData::new(),
|
||||
// id: self.base_data.id.ok_or("id is required")? - moved to base_data,
|
||||
name: self.name.ok_or("name is required")?,
|
||||
category_base_data: BaseModelData::new(),
|
||||
// id: self.category_id.ok_or("category_id is required")? - moved to base_data,
|
||||
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_base_data: BaseModelData::new(),
|
||||
// id: self.provider_id.ok_or("provider_id is required")? - moved to base_data,
|
||||
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.base_data.created_at.unwrap_or(now) - moved to base_data,
|
||||
// updated_at: self.base_data.updated_at.unwrap_or(now) - moved to base_data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Product {
|
||||
pub fn builder() -> ProductBuilder {
|
||||
ProductBuilder::new()
|
||||
}
|
||||
}
|
297
heromodels/src/models/tfmarketplace/service.rs
Normal file
297
heromodels/src/models/tfmarketplace/service.rs
Normal file
@@ -0,0 +1,297 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize, Deserializer};
|
||||
use rust_decimal::Decimal;
|
||||
use std::str::FromStr;
|
||||
use heromodels_core::BaseModelData;
|
||||
use crate::models::tfmarketplace::user::ResourceUtilization;
|
||||
|
||||
/// Service Provider-specific data
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServiceProviderData {
|
||||
pub active_services: i32,
|
||||
pub total_clients: i32,
|
||||
pub monthly_revenue_usd: i32,
|
||||
pub total_revenue_usd: i32,
|
||||
pub service_rating: f32,
|
||||
pub services: Vec<Service>,
|
||||
pub client_requests: Vec<ServiceRequest>,
|
||||
pub revenue_history: Vec<RevenueRecord>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Service {
|
||||
pub base_data: BaseModelData::new(),
|
||||
// id: String - moved to base_data,
|
||||
pub name: String,
|
||||
pub category: String,
|
||||
pub description: String,
|
||||
pub price_per_hour_usd: i32,
|
||||
pub status: String,
|
||||
pub clients: i32,
|
||||
pub rating: f32,
|
||||
pub total_hours: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServiceRequest {
|
||||
|
||||
|
||||
/// Base model data (includes id, created_at, updated_at)
|
||||
pub base_data: BaseModelData,
|
||||
pub client_name: String,
|
||||
pub service_name: String,
|
||||
pub status: String,
|
||||
pub requested_date: String,
|
||||
pub estimated_hours: i32,
|
||||
pub budget: i32,
|
||||
pub priority: String,
|
||||
#[serde(default)]
|
||||
pub progress: Option<i32>,
|
||||
#[serde(default)]
|
||||
pub completed_date: Option<String>,
|
||||
#[serde(default)]
|
||||
pub client_email: Option<String>,
|
||||
#[serde(default)]
|
||||
pub client_phone: Option<String>,
|
||||
#[serde(default)]
|
||||
pub description: Option<String>,
|
||||
#[serde(default)]
|
||||
pub created_date: Option<String>,
|
||||
}
|
||||
|
||||
/// Service booking record for customers who purchase services
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServiceBooking {
|
||||
pub base_data: BaseModelData::new(),
|
||||
// id: String - moved to base_data, // Same as ServiceRequest.id for cross-reference
|
||||
pub service_base_data: BaseModelData::new(),
|
||||
// id: String - moved to base_data, // Reference to original service
|
||||
pub service_name: String,
|
||||
pub provider_email: String, // Who provides the service
|
||||
pub customer_email: String, // Who booked the service
|
||||
pub budget: i32,
|
||||
pub estimated_hours: i32,
|
||||
pub status: String, // "Pending", "In Progress", "Completed"
|
||||
pub requested_date: String,
|
||||
pub priority: String,
|
||||
pub description: Option<String>,
|
||||
pub booking_date: String, // When customer booked
|
||||
pub client_phone: Option<String>,
|
||||
pub progress: Option<i32>,
|
||||
pub completed_date: Option<String>,
|
||||
}
|
||||
|
||||
/// Customer Service-specific data (for users who book services)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CustomerServiceData {
|
||||
pub active_bookings: i32,
|
||||
pub completed_bookings: i32,
|
||||
pub total_spent: i32,
|
||||
pub monthly_spending: i32,
|
||||
pub average_rating_given: f32,
|
||||
pub service_bookings: Vec<ServiceBooking>,
|
||||
pub favorite_providers: Vec<String>,
|
||||
pub spending_history: Vec<SpendingRecord>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SpendingRecord {
|
||||
pub date: String,
|
||||
pub amount: i32,
|
||||
pub service_name: String,
|
||||
pub provider_name: String,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ServiceBookingBuilder {
|
||||
base_data: BaseModelData::new(),
|
||||
// id: Option<String> - moved to base_data,
|
||||
service_base_data: BaseModelData::new(),
|
||||
// id: Option<String> - moved to base_data,
|
||||
service_name: Option<String>,
|
||||
provider_email: Option<String>,
|
||||
customer_email: Option<String>,
|
||||
budget: Option<i32>,
|
||||
estimated_hours: Option<i32>,
|
||||
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) -> Self{
|
||||
self.base_data.id = Some(id.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn service_id(mut self, service_id: &str, name: &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: i32) -> Self {
|
||||
self.budget = Some(budget);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn estimated_hours(mut self, hours: i32) -> Self {
|
||||
self.estimated_hours = Some(hours);
|
||||
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 {
|
||||
base_data: BaseModelData::new(),
|
||||
// id: self.base_data.id.ok_or("ID is required")? - moved to base_data,
|
||||
service_base_data: BaseModelData::new(),
|
||||
// id: self.service_id.ok_or("Service ID is required")? - moved to base_data,
|
||||
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(0),
|
||||
estimated_hours: self.estimated_hours.unwrap_or(0),
|
||||
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.unwrap_or_else(|| chrono::Utc::now().format("%Y-%m-%d").to_string()),
|
||||
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<i32>,
|
||||
monthly_spending: Option<i32>,
|
||||
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: i32) -> Self {
|
||||
self.total_spent = Some(amount);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn monthly_spending(mut self, amount: i32) -> 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(0),
|
||||
monthly_spending: self.monthly_spending.unwrap_or(0),
|
||||
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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::models::user::CustomerServiceData {
|
||||
pub fn builder() -> CustomerServiceDataBuilder {
|
||||
CustomerServiceDataBuilder::new()
|
||||
}
|
||||
}
|
200
heromodels/src/models/tfmarketplace/slice.rs
Normal file
200
heromodels/src/models/tfmarketplace/slice.rs
Normal file
@@ -0,0 +1,200 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use chrono::{DateTime, Utc};
|
||||
use rust_decimal::Decimal;
|
||||
use std::collections::HashMap;
|
||||
use heromodels_core::BaseModelData;
|
||||
use crate::models::tfmarketplace::user::ResourceUtilization;
|
||||
|
||||
/// Slice configuration data structure for product attributes
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SliceConfiguration {
|
||||
pub cpu_cores: i32,
|
||||
pub memory_gb: i32,
|
||||
pub storage_gb: i32,
|
||||
pub bandwidth_mbps: i32,
|
||||
pub min_uptime_sla: f32,
|
||||
pub public_ips: i32,
|
||||
pub node_base_data: BaseModelData::new(),
|
||||
// id: Option<String> - moved to base_data,
|
||||
pub slice_type: SliceType,
|
||||
#[serde(default)]
|
||||
pub pricing: SlicePricing,
|
||||
}
|
||||
|
||||
/// Enhanced pricing structure for slices with multiple time periods
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SlicePricing {
|
||||
pub hourly: Decimal,
|
||||
pub daily: Decimal,
|
||||
pub monthly: Decimal,
|
||||
pub yearly: Decimal,
|
||||
}
|
||||
|
||||
impl Default for SlicePricing {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
hourly: Decimal::ZERO,
|
||||
daily: Decimal::ZERO,
|
||||
monthly: Decimal::ZERO,
|
||||
yearly: Decimal::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SlicePricing {
|
||||
/// Create pricing from hourly rate with automatic calculation
|
||||
pub fn from_hourly(hourly_rate: Decimal, daily_discount: f32, monthly_discount: f32, yearly_discount: f32) -> Self {
|
||||
let base_daily = hourly_rate * Decimal::from(24);
|
||||
let base_monthly = hourly_rate * Decimal::from(24 * 30);
|
||||
let base_yearly = hourly_rate * Decimal::from(24 * 365);
|
||||
|
||||
Self {
|
||||
hourly: hourly_rate,
|
||||
daily: base_daily * Decimal::try_from(1.0 - daily_discount / 100.0).unwrap_or(Decimal::ONE),
|
||||
monthly: base_monthly * Decimal::try_from(1.0 - monthly_discount / 100.0).unwrap_or(Decimal::ONE),
|
||||
yearly: base_yearly * Decimal::try_from(1.0 - yearly_discount / 100.0).unwrap_or(Decimal::ONE),
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate savings compared to hourly rate
|
||||
pub fn calculate_savings(&self) -> (Decimal, Decimal, Decimal) {
|
||||
let hourly_equivalent_daily = self.hourly * Decimal::from(24);
|
||||
let hourly_equivalent_monthly = self.hourly * Decimal::from(24 * 30);
|
||||
let hourly_equivalent_yearly = self.hourly * Decimal::from(24 * 365);
|
||||
|
||||
let daily_savings = hourly_equivalent_daily - self.daily;
|
||||
let monthly_savings = hourly_equivalent_monthly - self.monthly;
|
||||
let yearly_savings = hourly_equivalent_yearly - self.yearly;
|
||||
|
||||
(daily_savings, monthly_savings, yearly_savings)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum SliceType {
|
||||
Basic,
|
||||
Standard,
|
||||
Premium,
|
||||
Custom,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SliceProductBuilder {
|
||||
farmer_base_data: BaseModelData::new(),
|
||||
// id: Option<String> - moved to base_data,
|
||||
farmer_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_base_data: BaseModelData::new(),
|
||||
// id: Option<String> - moved to base_data,
|
||||
slice_type: Option<crate::models::tfmarketplace::product::SliceType>,
|
||||
price_per_hour: Option<rust_decimal::Decimal>,
|
||||
}
|
||||
|
||||
impl SliceProductBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn farmer_id(mut self, farmer_id: &str, name: &str) -> Self{
|
||||
self.farmer_id = Some(farmer_id.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn farmer_name(mut self, farmer_name: impl Into<String>) -> Self {
|
||||
self.farmer_name = Some(farmer_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: &str, name: &str) -> Self{
|
||||
self.node_id = Some(node_id.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn slice_type(mut self, slice_type: crate::models::tfmarketplace::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::tfmarketplace::product::Product, String> {
|
||||
let farmer_id = self.farmer_id.ok_or("farmer_id is required")?;
|
||||
let farmer_name = self.farmer_name.ok_or("farmer_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::tfmarketplace::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_base_data: BaseModelData::new(),
|
||||
// id: self.node_id - moved to base_data,
|
||||
slice_type: self.slice_type.unwrap_or(crate::models::tfmarketplace::product::SliceType::Basic),
|
||||
pricing: crate::models::tfmarketplace::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::tfmarketplace::product::Product::create_slice_product(
|
||||
farmer_id,
|
||||
farmer_name,
|
||||
slice_name,
|
||||
slice_config,
|
||||
price_per_hour,
|
||||
))
|
||||
}
|
||||
}
|
3509
heromodels/src/models/tfmarketplace/user.rs
Normal file
3509
heromodels/src/models/tfmarketplace/user.rs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user