Compare commits
	
		
			4 Commits
		
	
	
		
			7a999b7b6e
			...
			developmen
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 6569e819ae | ||
|  | 130822b69b | ||
|  | 7439980b33 | ||
|  | 453e86edd2 | 
							
								
								
									
										48
									
								
								heromodels/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										48
									
								
								heromodels/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -60,7 +60,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.104", | ||||
|  "syn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -233,14 +233,6 @@ dependencies = [ | ||||
|  "typenum", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "derive" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "quote", | ||||
|  "syn 1.0.109", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "digest" | ||||
| version = "0.10.7" | ||||
| @@ -300,7 +292,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.104", | ||||
|  "syn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -387,7 +379,6 @@ version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "bincode", | ||||
|  "chrono", | ||||
|  "derive", | ||||
|  "heromodels-derive", | ||||
|  "heromodels_core", | ||||
|  "jsonb", | ||||
| @@ -411,7 +402,7 @@ version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.104", | ||||
|  "syn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -514,7 +505,7 @@ checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.104", | ||||
|  "syn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -952,7 +943,7 @@ checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.104", | ||||
|  "syn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -1015,7 +1006,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.104", | ||||
|  "syn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -1145,7 +1136,7 @@ dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "rustversion", | ||||
|  "syn 2.0.104", | ||||
|  "syn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -1154,17 +1145,6 @@ version = "2.6.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" | ||||
|  | ||||
| [[package]] | ||||
| name = "syn" | ||||
| version = "1.0.109" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "unicode-ident", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "syn" | ||||
| version = "2.0.104" | ||||
| @@ -1199,7 +1179,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.104", | ||||
|  "syn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -1254,7 +1234,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.104", | ||||
|  "syn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -1409,7 +1389,7 @@ dependencies = [ | ||||
|  "log", | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.104", | ||||
|  "syn", | ||||
|  "wasm-bindgen-shared", | ||||
| ] | ||||
|  | ||||
| @@ -1431,7 +1411,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.104", | ||||
|  "syn", | ||||
|  "wasm-bindgen-backend", | ||||
|  "wasm-bindgen-shared", | ||||
| ] | ||||
| @@ -1487,7 +1467,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.104", | ||||
|  "syn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -1498,7 +1478,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.104", | ||||
|  "syn", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -1633,5 +1613,5 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.104", | ||||
|  "syn", | ||||
| ] | ||||
|   | ||||
							
								
								
									
										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