- Added a comprehensive example demonstrating governance activity tracking. - Created `GovernanceActivity` model to record governance events. - Improved error handling in `OurDB::get_all` to gracefully handle deserialization failures.
358 lines
11 KiB
Rust
358 lines
11 KiB
Rust
// 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::<Proposal>()
|
|
.expect("can open proposal collection");
|
|
let activity_collection = db
|
|
.collection::<GovernanceActivity>()
|
|
.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::<String>);
|
|
proposal = proposal.add_option(2, "Reject Allocation", None::<String>);
|
|
proposal = proposal.add_option(3, "Abstain", None::<String>);
|
|
|
|
// 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
|
|
);
|
|
}
|