// heromodels/examples/governance_activity_example.rs use chrono::{Duration, Utc}; use heromodels::db::{Collection, Db}; use heromodels::models::governance::{ ActivityType, GovernanceActivity, Proposal, ProposalStatus, VoteEventStatus, }; fn main() { println!("Governance Activity Tracking Example\n"); // Create a new DB instance with a unique path, reset before every run let db_path = format!( "/tmp/ourdb_governance_activity_example_{}", chrono::Utc::now().timestamp() ); let db = heromodels::db::hero::OurDB::new(&db_path, true).expect("Can create DB"); // Get collections for proposals and activities let proposal_collection = db .collection::() .expect("can open proposal collection"); let activity_collection = db .collection::() .expect("can open activity collection"); // === STEP 1: Create a proposal and track the activity === println!("=== Creating a Proposal ==="); let now = Utc::now(); let mut proposal = Proposal::new( None, // auto-generated ID "user_creator_123", "Alice Johnson", "Community Fund Allocation for Q4", "Proposal to allocate funds for community projects in the fourth quarter.", ProposalStatus::Draft, now, now, now, now + Duration::days(14), ); // Save the proposal let (proposal_id, saved_proposal) = proposal_collection .set(&proposal) .expect("can save proposal"); proposal = saved_proposal; println!( "Created proposal: '{}' (ID: {})", proposal.title, proposal_id ); // Track the proposal creation activity let creation_activity = GovernanceActivity::proposal_created( proposal_id, &proposal.title, &proposal.creator_id, &proposal.creator_name, ); let (activity_id, _) = activity_collection .set(&creation_activity) .expect("can save activity"); println!( "Tracked activity: Proposal creation (Activity ID: {})", activity_id ); // === STEP 2: Add vote options and track activities === println!("\n=== Adding Vote Options ==="); proposal = proposal.add_option(1, "Approve Allocation", None::); proposal = proposal.add_option(2, "Reject Allocation", None::); proposal = proposal.add_option(3, "Abstain", None::); // Track vote option additions for option in &proposal.options { let option_activity = GovernanceActivity::vote_option_added( proposal_id, &proposal.title, option.id, &option.text, Some(&proposal.creator_id), Some(&proposal.creator_name), ); let (option_activity_id, _) = activity_collection .set(&option_activity) .expect("can save option activity"); println!( "Added option '{}' and tracked activity (ID: {})", option.text, option_activity_id ); } // === STEP 3: Start voting and track activity === println!("\n=== Starting Voting Period ==="); let voting_start_activity = GovernanceActivity::voting_started( proposal_id, &proposal.title, proposal.vote_start_date, proposal.vote_end_date, ); let (voting_start_id, _) = activity_collection .set(&voting_start_activity) .expect("can save voting start activity"); println!("Voting period started (Activity ID: {})", voting_start_id); // === STEP 4: Cast votes and track activities === println!("\n=== Casting Votes ==="); // Simulate vote casting let votes = vec![ (101, 1, 1, 100, "Bob Smith"), (102, 2, 2, 50, "Carol Davis"), (0, 3, 1, 75, "David Wilson"), // auto-generated ballot ID (0, 4, 3, 20, "Eve Brown"), // auto-generated ballot ID ]; for (ballot_id, user_id, option_id, shares, voter_name) in votes { // Cast the vote proposal = proposal.cast_vote( if ballot_id == 0 { None } else { Some(ballot_id) }, user_id, option_id, shares, ); // Find the option text let option_text = proposal .options .iter() .find(|opt| opt.id == option_id) .map(|opt| opt.text.clone()) .unwrap_or_else(|| "Unknown".to_string()); // Track the vote casting activity let vote_activity = GovernanceActivity::vote_cast( proposal_id, &proposal.title, ballot_id, user_id.to_string(), voter_name, &option_text, shares, ); let (vote_activity_id, _) = activity_collection .set(&vote_activity) .expect("can save vote activity"); println!( "{} voted '{}' with {} shares (Activity ID: {})", voter_name, option_text, shares, vote_activity_id ); } // === STEP 5: Change proposal status and track activity === println!("\n=== Changing Proposal Status ==="); let old_status = format!("{:?}", proposal.status); proposal = proposal.change_proposal_status(ProposalStatus::Active); let new_status = format!("{:?}", proposal.status); let status_change_activity = GovernanceActivity::proposal_status_changed( proposal_id, &proposal.title, &old_status, &new_status, Some("admin_user"), Some("Admin User"), ); let (status_activity_id, _) = activity_collection .set(&status_change_activity) .expect("can save status change activity"); println!( "Proposal status changed from {} to {} (Activity ID: {})", old_status, new_status, status_activity_id ); // === STEP 6: End voting and track activity === println!("\n=== Ending Voting Period ==="); proposal = proposal.change_vote_event_status(VoteEventStatus::Closed); let total_votes = proposal.ballots.len(); let total_shares: i64 = proposal.ballots.iter().map(|b| b.shares_count).sum(); let voting_end_activity = GovernanceActivity::voting_ended(proposal_id, &proposal.title, total_votes, total_shares); let (voting_end_id, _) = activity_collection .set(&voting_end_activity) .expect("can save voting end activity"); println!( "Voting period ended. Total votes: {}, Total shares: {} (Activity ID: {})", total_votes, total_shares, voting_end_id ); // === STEP 7: Display all activities === println!("\n=== Recent Governance Activities ==="); // Try to retrieve activities one by one to identify the problematic one println!("Trying to retrieve activities by ID..."); for id in 2..=12 { match activity_collection.get_by_id(id) { Ok(Some(activity)) => { println!( "ID {}: {} (Type: {:?})", id, activity.title, activity.activity_type ); } Ok(None) => { println!("ID {}: Not found", id); } Err(e) => { println!("ID {}: Error - {:?}", id, e); } } } let all_activities = match activity_collection.get_all() { Ok(activities) => { println!("Successfully retrieved {} activities", activities.len()); activities } Err(e) => { println!("Error retrieving all activities: {:?}", e); println!( "This might be due to enum serialization changes. Continuing with empty list." ); Vec::new() } }; // Sort activities by occurred_at timestamp (most recent first) let mut sorted_activities = all_activities; sorted_activities.sort_by(|a, b| b.occurred_at.cmp(&a.occurred_at)); for (i, activity) in sorted_activities.iter().enumerate() { println!("{}. {}", i + 1, activity.summary()); // Show additional details for some activities if !activity.metadata.is_empty() { println!(" Details: {}", activity.metadata); } if !activity.tags.is_empty() { println!(" Tags: {:?}", activity.tags); } println!( " Severity: {:?}, Status: {:?}", activity.severity, activity.status ); println!(); } // === STEP 8: Filter activities by type === println!("=== Vote-Related Activities ==="); let vote_activities: Vec<_> = sorted_activities .iter() .filter(|activity| { matches!( activity.activity_type, ActivityType::VoteCast | ActivityType::VotingStarted | ActivityType::VotingEnded | ActivityType::VoteOptionAdded ) }) .collect(); for (i, activity) in vote_activities.iter().enumerate() { println!("{}. {}", i + 1, activity.summary()); } // // === STEP 9: Filter activities by user - "My Requests" === // println!("\n=== My Requests (Alice Johnson's Activities) ==="); // let my_activities: Vec<_> = sorted_activities // .iter() // .filter(|activity| activity.actor_name.as_deref() == Some("Alice Johnson")) // .collect(); // if my_activities.is_empty() { // println!("No activities found for Alice Johnson"); // } else { // for (i, activity) in my_activities.iter().enumerate() { // println!("{}. {}", i + 1, activity.summary()); // // Show additional details for user's own activities // if !activity.metadata.is_empty() { // println!(" Details: {}", activity.metadata); // } // if !activity.tags.is_empty() { // println!(" Tags: {:?}", activity.tags); // } // println!(); // } // } // === STEP 10: Filter activities by another user === println!("=== Bob Smith's Activities ==="); let bob_activities: Vec<_> = sorted_activities .iter() .filter(|activity| activity.actor_name.as_deref() == Some("Bob Smith")) .collect(); if bob_activities.is_empty() { println!("No activities found for Bob Smith"); } else { for (i, activity) in bob_activities.iter().enumerate() { println!("{}. {}", i + 1, activity.summary()); } } // === STEP 9: Show statistics === println!("\n=== Activity Statistics ==="); println!("Total activities recorded: {}", sorted_activities.len()); let activity_counts = sorted_activities .iter() .fold(std::collections::HashMap::new(), |mut acc, activity| { *acc.entry(format!("{:?}", activity.activity_type)) .or_insert(0) += 1; acc }); for (activity_type, count) in activity_counts { println!("- {}: {}", activity_type, count); } println!("\nExample finished. DB stored at {}", db_path); println!( "To clean up, you can manually delete the directory: {}", db_path ); }