diff --git a/heromodels/examples/governance_proposal_example/main.rs b/heromodels/examples/governance_proposal_example/main.rs index 642a9bb..f6ad994 100644 --- a/heromodels/examples/governance_proposal_example/main.rs +++ b/heromodels/examples/governance_proposal_example/main.rs @@ -1,6 +1,6 @@ // heromodels/examples/governance_proposal_example/main.rs -use chrono::{Utc, Duration}; +use chrono::{Duration, Utc}; use heromodels::db::{Collection, Db}; use heromodels::models::governance::{Proposal, ProposalStatus, VoteEventStatus}; @@ -13,17 +13,26 @@ fn main() { // Create a new proposal with auto-generated ID let mut proposal = Proposal::new( - None, // id (auto-generated) - "user_creator_123", // creator_id - "Community Fund Allocation for Q3", // title + None, // id (auto-generated) + "user_creator_123", // creator_id + "Community Fund Allocation for Q3", // title "Proposal to allocate funds for community projects in the third quarter.", // description - Utc::now(), // vote_start_date - Utc::now() + Duration::days(14) // vote_end_date (14 days from now) + Utc::now(), // vote_start_date + Utc::now() + Duration::days(14), // vote_end_date (14 days from now) ); - println!("Before saving - Created Proposal: '{}' (ID: {})", proposal.title, proposal.base_data.id); - println!("Before saving - Status: {:?}, Vote Status: {:?}", proposal.status, proposal.vote_status); - println!("Before saving - Vote Period: {} to {}\n", proposal.vote_start_date, proposal.vote_end_date); + println!( + "Before saving - Created Proposal: '{}' (ID: {})", + proposal.title, proposal.base_data.id + ); + println!( + "Before saving - Status: {:?}, Vote Status: {:?}", + proposal.status, proposal.vote_status + ); + println!( + "Before saving - Vote Period: {} to {}\n", + proposal.vote_start_date, proposal.vote_end_date + ); // Add vote options proposal = proposal.add_option(1, "Approve Allocation"); @@ -32,15 +41,23 @@ fn main() { println!("Added Vote Options:"); for option in &proposal.options { - println!("- Option ID: {}, Text: '{}', Votes: {}", option.id, option.text, option.count); + println!( + "- Option ID: {}, Text: '{}', Votes: {}", + option.id, option.text, option.count + ); } println!(""); // Save the proposal to the database - let collection = db.collection::().expect("can open proposal collection"); + let collection = db + .collection::() + .expect("can open proposal collection"); let (proposal_id, saved_proposal) = collection.set(&proposal).expect("can save proposal"); - println!("After saving - Proposal ID: {}", saved_proposal.base_data.id); + println!( + "After saving - Proposal ID: {}", + saved_proposal.base_data.id + ); println!("After saving - Returned ID: {}", proposal_id); // Use the saved proposal for further operations @@ -63,13 +80,18 @@ fn main() { println!("\nVote Counts After Simulation:"); for option in &proposal.options { - println!("- Option ID: {}, Text: '{}', Votes: {}", option.id, option.text, option.count); + println!( + "- Option ID: {}, Text: '{}', Votes: {}", + option.id, option.text, option.count + ); } println!("\nBallots Cast:"); for ballot in &proposal.ballots { - println!("- Ballot ID: {}, User ID: {}, Option ID: {}, Shares: {}", - ballot.base_data.id, ballot.user_id, ballot.vote_option_id, ballot.shares_count); + println!( + "- Ballot ID: {}, User ID: {}, Option ID: {}, Shares: {}", + ballot.base_data.id, ballot.user_id, ballot.vote_option_id, ballot.shares_count + ); } println!(""); @@ -92,7 +114,10 @@ fn main() { println!("Vote Status: {:?}", proposal.vote_status); println!("Options:"); for option in &proposal.options { - println!(" - {}: {} (Votes: {})", option.id, option.text, option.count); + println!( + " - {}: {} (Votes: {})", + option.id, option.text, option.count + ); } println!("Total Ballots: {}", proposal.ballots.len()); @@ -103,20 +128,31 @@ fn main() { "Internal Team Restructure Vote", "Vote on proposed internal team changes.", Utc::now(), - Utc::now() + Duration::days(7) + Utc::now() + Duration::days(7), ); private_proposal.private_group = Some(vec![10, 20, 30]); // Only users 10, 20, 30 can vote private_proposal = private_proposal.add_option(1, "Accept Restructure"); private_proposal = private_proposal.add_option(2, "Reject Restructure"); - println!("\nBefore saving - Created Private Proposal: '{}'", private_proposal.title); - println!("Before saving - Eligible Voters (Group): {:?}", private_proposal.private_group); + println!( + "\nBefore saving - Created Private Proposal: '{}'", + private_proposal.title + ); + println!( + "Before saving - Eligible Voters (Group): {:?}", + private_proposal.private_group + ); // Save the private proposal to the database - let (private_proposal_id, saved_private_proposal) = collection.set(&private_proposal).expect("can save private proposal"); + let (private_proposal_id, saved_private_proposal) = collection + .set(&private_proposal) + .expect("can save private proposal"); private_proposal = saved_private_proposal; - println!("After saving - Private Proposal ID: {}", private_proposal.base_data.id); + println!( + "After saving - Private Proposal ID: {}", + private_proposal.base_data.id + ); println!("After saving - Returned ID: {}", private_proposal_id); // User 10 (eligible) votes with explicit ballot ID @@ -125,10 +161,42 @@ fn main() { private_proposal = private_proposal.cast_vote(None, 40, 1, 50); println!("Private Proposal Vote Counts:"); - for option in &private_proposal.options { - println!(" - {}: {} (Votes: {})", option.id, option.text, option.count); + for option in &private_proposal.options { + println!( + " - {}: {} (Votes: {})", + option.id, option.text, option.count + ); } println!("\nExample finished. DB stored at {}", db_path); - println!("To clean up, you can manually delete the directory: {}", db_path); + println!( + "To clean up, you can manually delete the directory: {}", + db_path + ); + + // --- Additional Example: Listing and Filtering Proposals --- + println!("\n--- Listing All Proposals ---"); + // List all proposals from the DB + let all_proposals = collection.get_all().expect("can list all proposals"); + for proposal in &all_proposals { + println!( + "- Proposal ID: {}, Title: '{}', Status: {:?}", + proposal.base_data.id, proposal.title, proposal.status + ); + } + println!("Total proposals in DB: {}", all_proposals.len()); + + // Filter proposals by status (e.g., only Active proposals) + let active_proposals: Vec<_> = all_proposals + .iter() + .filter(|p| p.status == ProposalStatus::Active) + .collect(); + println!("\n--- Filtering Proposals by Status: Active ---"); + for proposal in &active_proposals { + println!( + "- Proposal ID: {}, Title: '{}', Status: {:?}", + proposal.base_data.id, proposal.title, proposal.status + ); + } + println!("Total ACTIVE proposals: {}", active_proposals.len()); } diff --git a/heromodels/src/db/hero.rs b/heromodels/src/db/hero.rs index 70b7f4d..d9fff2a 100644 --- a/heromodels/src/db/hero.rs +++ b/heromodels/src/db/hero.rs @@ -1,13 +1,16 @@ +use crate::db::Transaction; use heromodels_core::{Index, Model}; use ourdb::OurDBSetArgs; use serde::Deserialize; -use crate::db::Transaction; use std::{ borrow::Borrow, collections::HashSet, path::PathBuf, - sync::{Arc, Mutex, atomic::{AtomicU32, Ordering}}, + sync::{ + Arc, Mutex, + atomic::{AtomicU32, Ordering}, + }, }; /// Configuration for custom ID sequences @@ -85,7 +88,11 @@ impl OurDB { } /// Create a new instance of ourdb with a custom ID sequence - pub fn with_id_sequence(path: impl Into, reset: bool, id_sequence: IdSequence) -> Result { + pub fn with_id_sequence( + path: impl Into, + reset: bool, + id_sequence: IdSequence, + ) -> Result { let mut base_path = path.into(); let mut data_path = base_path.clone(); base_path.push("index"); @@ -138,7 +145,9 @@ where type Error = tst::Error; /// Begin a transaction for this collection - fn begin_transaction(&self) -> Result>, super::Error> { + fn begin_transaction( + &self, + ) -> Result>, super::Error> { // Create a new transaction let transaction = OurDBTransaction::new(); @@ -287,7 +296,7 @@ where // and save it again to ensure the serialized data contains the correct ID let mut value_clone = value.clone(); let base_data = value_clone.base_data_mut(); - base_data.update_id(assigned_id); + base_data.id = assigned_id; // Serialize the updated model let v = bincode::serde::encode_to_vec(&value_clone, BINCODE_CONFIG)?; @@ -327,10 +336,13 @@ where } // Get the updated model from the database - let updated_model = Self::get_ourdb_value::(&mut data_db, assigned_id)? - .ok_or_else(|| super::Error::InvalidId(format!( - "Failed to retrieve model with ID {} after saving", assigned_id - )))?; + let updated_model = + Self::get_ourdb_value::(&mut data_db, assigned_id)?.ok_or_else(|| { + super::Error::InvalidId(format!( + "Failed to retrieve model with ID {} after saving", + assigned_id + )) + })?; // Return the assigned ID and the updated model Ok((assigned_id, updated_model)) @@ -413,7 +425,18 @@ where } fn get_all(&self) -> Result, super::Error> { - todo!("OurDB doesn't have a list all method yet") + let mut data_db = self.data.lock().expect("can lock data DB"); + let mut all_objs = Vec::new(); + // Get the next available ID (exclusive upper bound) + let next_id = data_db.get_next_id().map_err(super::Error::from)?; + for id in 1..next_id { + match Self::get_ourdb_value::(&mut data_db, id) { + Ok(Some(obj)) => all_objs.push(obj), + Ok(None) => continue, // skip missing IDs + Err(e) => return Err(e), + } + } + Ok(all_objs) } } @@ -510,7 +533,9 @@ struct OurDBTransaction { impl OurDBTransaction { /// Create a new transaction fn new() -> Self { - Self { active: std::sync::atomic::AtomicBool::new(false) } + Self { + active: std::sync::atomic::AtomicBool::new(false), + } } } @@ -519,8 +544,11 @@ impl Drop for OurDBTransaction { // If the transaction is still active when dropped, roll it back if self.active.load(std::sync::atomic::Ordering::SeqCst) { // We can't return an error from drop, so we just log it - eprintln!("Warning: Transaction was dropped without being committed or rolled back. Rolling back automatically."); - self.active.store(false, std::sync::atomic::Ordering::SeqCst); + eprintln!( + "Warning: Transaction was dropped without being committed or rolled back. Rolling back automatically." + ); + self.active + .store(false, std::sync::atomic::Ordering::SeqCst); } } } @@ -541,10 +569,13 @@ impl super::Transaction for OurDBTransaction { // In a real implementation, you would commit the transaction in the underlying database // For now, we just check if the transaction is active if !self.active.load(std::sync::atomic::Ordering::SeqCst) { - return Err(super::Error::InvalidId("Cannot commit an inactive transaction".to_string())); + return Err(super::Error::InvalidId( + "Cannot commit an inactive transaction".to_string(), + )); } - self.active.store(false, std::sync::atomic::Ordering::SeqCst); + self.active + .store(false, std::sync::atomic::Ordering::SeqCst); Ok(()) } @@ -553,10 +584,13 @@ impl super::Transaction for OurDBTransaction { // In a real implementation, you would roll back the transaction in the underlying database // For now, we just check if the transaction is active if !self.active.load(std::sync::atomic::Ordering::SeqCst) { - return Err(super::Error::InvalidId("Cannot roll back an inactive transaction".to_string())); + return Err(super::Error::InvalidId( + "Cannot roll back an inactive transaction".to_string(), + )); } - self.active.store(false, std::sync::atomic::Ordering::SeqCst); + self.active + .store(false, std::sync::atomic::Ordering::SeqCst); Ok(()) } diff --git a/heromodels/src/models/calendar/calendar.rs b/heromodels/src/models/calendar/calendar.rs index f03e4b6..900cd9d 100644 --- a/heromodels/src/models/calendar/calendar.rs +++ b/heromodels/src/models/calendar/calendar.rs @@ -1,9 +1,9 @@ use chrono::{DateTime, Utc}; use heromodels_core::BaseModelData; use heromodels_derive::model; -use serde::{Deserialize, Serialize}; -use rhai_autobind_macros::rhai_model_export; use rhai::{CustomType, TypeBuilder}; +use rhai_autobind_macros::rhai_model_export; +use serde::{Deserialize, Serialize}; /// Represents the status of an attendee for an event #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -60,7 +60,12 @@ pub struct Event { impl Event { /// Creates a new event - pub fn new(id: String, title: impl ToString, start_time: DateTime, end_time: DateTime) -> Self { + pub fn new( + id: String, + title: impl ToString, + start_time: DateTime, + end_time: DateTime, + ) -> Self { Self { id, title: title.to_string(), @@ -108,7 +113,11 @@ impl Event { } /// Reschedules the event to new start and end times - pub fn reschedule(mut self, new_start_time: DateTime, new_end_time: DateTime) -> Self { + pub fn reschedule( + mut self, + new_start_time: DateTime, + new_end_time: DateTime, + ) -> Self { // Basic validation: end_time should be after start_time if new_end_time > new_start_time { self.start_time = new_start_time; diff --git a/heromodels/src/models/finance/account.rs b/heromodels/src/models/finance/account.rs index 3ca8fa5..d764643 100644 --- a/heromodels/src/models/finance/account.rs +++ b/heromodels/src/models/finance/account.rs @@ -1,8 +1,8 @@ // heromodels/src/models/finance/account.rs -use serde::{Deserialize, Serialize}; -use heromodels_derive::model; use heromodels_core::BaseModelData; +use heromodels_derive::model; +use serde::{Deserialize, Serialize}; use super::asset::Asset; @@ -38,7 +38,7 @@ impl Account { description: impl ToString, ledger: impl ToString, address: impl ToString, - pubkey: impl ToString + pubkey: impl ToString, ) -> Self { let mut base_data = BaseModelData::new(); if let Some(id) = id { diff --git a/heromodels/src/models/finance/asset.rs b/heromodels/src/models/finance/asset.rs index a00f072..29f6ec7 100644 --- a/heromodels/src/models/finance/asset.rs +++ b/heromodels/src/models/finance/asset.rs @@ -1,8 +1,8 @@ // heromodels/src/models/finance/asset.rs -use serde::{Deserialize, Serialize}; -use heromodels_derive::model; use heromodels_core::BaseModelData; +use heromodels_derive::model; +use serde::{Deserialize, Serialize}; /// AssetType defines the type of blockchain asset #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -24,12 +24,12 @@ impl Default for AssetType { #[model] // Has base.Base in V spec pub struct Asset { pub base_data: BaseModelData, - pub name: String, // Name of the asset - pub description: String, // Description of the asset - pub amount: f64, // Amount of the asset - pub address: String, // Address of the asset on the blockchain or bank - pub asset_type: AssetType, // Type of the asset - pub decimals: u8, // Number of decimals of the asset + pub name: String, // Name of the asset + pub description: String, // Description of the asset + pub amount: f64, // Amount of the asset + pub address: String, // Address of the asset on the blockchain or bank + pub asset_type: AssetType, // Type of the asset + pub decimals: u8, // Number of decimals of the asset } impl Asset { diff --git a/heromodels/src/models/finance/marketplace.rs b/heromodels/src/models/finance/marketplace.rs index 34622cc..6382b48 100644 --- a/heromodels/src/models/finance/marketplace.rs +++ b/heromodels/src/models/finance/marketplace.rs @@ -1,9 +1,9 @@ // heromodels/src/models/finance/marketplace.rs -use serde::{Deserialize, Serialize}; -use heromodels_derive::model; -use heromodels_core::BaseModelData; use chrono::{DateTime, Utc}; +use heromodels_core::BaseModelData; +use heromodels_derive::model; +use serde::{Deserialize, Serialize}; use super::asset::AssetType; @@ -54,11 +54,11 @@ impl Default for BidStatus { /// Bid represents a bid on an auction listing #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Bid { - pub listing_id: String, // ID of the listing this bid belongs to - pub bidder_id: u32, // ID of the user who placed the bid - pub amount: f64, // Bid amount - pub currency: String, // Currency of the bid - pub status: BidStatus, // Status of the bid + pub listing_id: String, // ID of the listing this bid belongs to + pub bidder_id: u32, // ID of the user who placed the bid + pub amount: f64, // Bid amount + pub currency: String, // Currency of the bid + pub status: BidStatus, // Status of the bid pub created_at: DateTime, // When the bid was created } @@ -97,7 +97,7 @@ pub struct Listing { pub asset_id: String, pub asset_type: AssetType, pub seller_id: String, - pub price: f64, // Initial price for fixed price, or starting price for auction + pub price: f64, // Initial price for fixed price, or starting price for auction pub currency: String, pub listing_type: ListingType, pub status: ListingStatus, @@ -210,7 +210,11 @@ impl Listing { } /// Complete a sale (fixed price or auction) - pub fn complete_sale(mut self, buyer_id: impl ToString, sale_price: f64) -> Result { + pub fn complete_sale( + mut self, + buyer_id: impl ToString, + sale_price: f64, + ) -> Result { if self.status != ListingStatus::Active { return Err("Cannot complete sale for inactive listing"); } @@ -223,7 +227,9 @@ impl Listing { // If this was an auction, accept the winning bid and reject others if self.listing_type == ListingType::Auction { for bid in &mut self.bids { - if bid.bidder_id.to_string() == self.buyer_id.as_ref().unwrap().to_string() && bid.amount == sale_price { + if bid.bidder_id.to_string() == self.buyer_id.as_ref().unwrap().to_string() + && bid.amount == sale_price + { bid.status = BidStatus::Accepted; } else { bid.status = BidStatus::Rejected; diff --git a/heromodels/src/models/governance/proposal.rs b/heromodels/src/models/governance/proposal.rs index ab075d6..bf207e5 100644 --- a/heromodels/src/models/governance/proposal.rs +++ b/heromodels/src/models/governance/proposal.rs @@ -1,10 +1,10 @@ // heromodels/src/models/governance/proposal.rs use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; use heromodels_derive::model; // For #[model] -use rhai_autobind_macros::rhai_model_export; use rhai::{CustomType, TypeBuilder}; +use rhai_autobind_macros::rhai_model_export; +use serde::{Deserialize, Serialize}; use heromodels_core::BaseModelData; @@ -13,11 +13,11 @@ use heromodels_core::BaseModelData; /// ProposalStatus defines the lifecycle status of a governance proposal itself #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum ProposalStatus { - Draft, // Proposal is being prepared - Active, // Proposal is active - Approved, // Proposal has been formally approved - Rejected, // Proposal has been formally rejected - Cancelled,// Proposal was cancelled + Draft, // Proposal is being prepared + Active, // Proposal is active + Approved, // Proposal has been formally approved + Rejected, // Proposal has been formally rejected + Cancelled, // Proposal was cancelled } impl Default for ProposalStatus { @@ -26,7 +26,6 @@ impl Default for ProposalStatus { } } - /// VoteEventStatus represents the status of the voting process for a proposal #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum VoteEventStatus { @@ -46,9 +45,9 @@ impl Default for VoteEventStatus { /// VoteOption represents a specific choice that can be voted on #[derive(Debug, Clone, Serialize, Deserialize, CustomType)] pub struct VoteOption { - pub id: u8, // Simple identifier for this option - pub text: String, // Descriptive text of the option - pub count: i64, // How many votes this option has received + pub id: u8, // Simple identifier for this option + pub text: String, // Descriptive text of the option + pub count: i64, // How many votes this option has received pub min_valid: Option, // Optional: minimum votes needed } @@ -69,9 +68,9 @@ impl VoteOption { #[model] // Has base.Base in V spec pub struct Ballot { pub base_data: BaseModelData, - pub user_id: u32, // The ID of the user who cast this ballot - pub vote_option_id: u8, // The 'id' of the VoteOption chosen - pub shares_count: i64, // Number of shares/tokens/voting power + pub user_id: u32, // The ID of the user who cast this ballot + pub vote_option_id: u8, // The 'id' of the VoteOption chosen + pub shares_count: i64, // Number of shares/tokens/voting power } impl Ballot { @@ -97,7 +96,6 @@ impl Ballot { } } - /// Proposal represents a governance proposal that can be voted upon. #[derive(Debug, Clone, Serialize, Deserialize, CustomType)] #[rhai_model_export(db_type = "std::sync::Arc")] @@ -128,7 +126,14 @@ impl Proposal { /// * `description` - Description of the proposal /// * `vote_start_date` - Date when voting starts /// * `vote_end_date` - Date when voting ends - pub fn new(id: Option, creator_id: impl ToString, title: impl ToString, description: impl ToString, vote_start_date: DateTime, vote_end_date: DateTime) -> Self { + pub fn new( + id: Option, + creator_id: impl ToString, + title: impl ToString, + description: impl ToString, + vote_start_date: DateTime, + vote_end_date: DateTime, + ) -> Self { let mut base_data = BaseModelData::new(); if let Some(id) = id { base_data.update_id(id); @@ -155,18 +160,30 @@ impl Proposal { self } - pub fn cast_vote(mut self, ballot_id: Option, user_id: u32, chosen_option_id: u8, shares: i64) -> Self { + pub fn cast_vote( + mut self, + ballot_id: Option, + user_id: u32, + chosen_option_id: u8, + shares: i64, + ) -> Self { if self.vote_status != VoteEventStatus::Open { eprintln!("Voting is not open for proposal '{}'", self.title); return self; } if !self.options.iter().any(|opt| opt.id == chosen_option_id) { - eprintln!("Chosen option ID {} does not exist for proposal '{}'", chosen_option_id, self.title); + eprintln!( + "Chosen option ID {} does not exist for proposal '{}'", + chosen_option_id, self.title + ); return self; } if let Some(group) = &self.private_group { if !group.contains(&user_id) { - eprintln!("User {} is not eligible to vote on proposal '{}'", user_id, self.title); + eprintln!( + "User {} is not eligible to vote on proposal '{}'", + user_id, self.title + ); return self; } } @@ -174,7 +191,11 @@ impl Proposal { let new_ballot = Ballot::new(ballot_id, user_id, chosen_option_id, shares); self.ballots.push(new_ballot); - if let Some(option) = self.options.iter_mut().find(|opt| opt.id == chosen_option_id) { + if let Some(option) = self + .options + .iter_mut() + .find(|opt| opt.id == chosen_option_id) + { option.count += shares; } self