303 lines
11 KiB
Rust
303 lines
11 KiB
Rust
// heromodels/examples/governance_proposal_example/main.rs
|
|
|
|
use chrono::{Duration, Utc};
|
|
use heromodels::db::{Collection, Db};
|
|
use heromodels::models::governance::{Ballot, Proposal, ProposalStatus, VoteEventStatus};
|
|
|
|
fn main() {
|
|
println!("Governance Proposal Model Example\n");
|
|
|
|
// Create a new DB instance, reset before every run
|
|
let db_path = "/tmp/ourdb_governance_proposal_example";
|
|
let db = heromodels::db::hero::OurDB::new(db_path, true).expect("Can create DB");
|
|
|
|
// Create a new proposal with auto-generated ID
|
|
let mut proposal = Proposal::new(
|
|
None, // id (auto-generated)
|
|
"user_creator_123",
|
|
"Ahmed fared", // creator_id
|
|
"Community Fund Allocation for Q3", // title
|
|
"Proposal to allocate funds for community projects in the third quarter.", // description
|
|
ProposalStatus::Draft,
|
|
Utc::now(), // created_at
|
|
Utc::now(), // updated_at
|
|
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
|
|
);
|
|
|
|
// Add vote options
|
|
proposal = proposal.add_option(1, "Approve Allocation", Some("This is the approval option"));
|
|
proposal = proposal.add_option(2, "Reject Allocation", Some("This is the rejection option"));
|
|
proposal = proposal.add_option(3, "Abstain", Some("This is the abstain option"));
|
|
|
|
println!("Added Vote Options:");
|
|
for option in &proposal.options {
|
|
println!(
|
|
"- Option ID: {}, Text: '{}', Votes: {}",
|
|
option.id, option.text, option.count
|
|
);
|
|
}
|
|
println!("");
|
|
|
|
// Save the proposal to the database
|
|
let collection = db
|
|
.collection::<Proposal>()
|
|
.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 - Returned ID: {}", proposal_id);
|
|
|
|
// Use the saved proposal for further operations
|
|
proposal = saved_proposal;
|
|
|
|
// Simulate casting votes
|
|
println!("Simulating Votes...");
|
|
// User 1 votes for 'Approve Allocation' with 100 shares (with explicit ballot ID)
|
|
proposal = proposal.cast_vote(Some(101), 1, 1, 100);
|
|
// User 2 votes for 'Reject Allocation' with 50 shares (with explicit ballot ID)
|
|
proposal = proposal.cast_vote(Some(102), 2, 2, 50);
|
|
// User 3 votes for 'Approve Allocation' with 75 shares (with auto-generated ballot ID)
|
|
proposal = proposal.cast_vote(None, 3, 1, 75);
|
|
// User 4 abstains with 20 shares (with auto-generated ballot ID)
|
|
proposal = proposal.cast_vote(None, 4, 3, 20);
|
|
// User 5 attempts to vote for a non-existent option (should be handled gracefully)
|
|
proposal = proposal.cast_vote(Some(105), 5, 99, 10);
|
|
// User 1 tries to vote again (not explicitly prevented by current model, but could be a future enhancement)
|
|
// proposal = proposal.cast_vote(Some(106), 1, 1, 10);
|
|
|
|
println!("\nVote Counts After Simulation:");
|
|
for option in &proposal.options {
|
|
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!("");
|
|
|
|
// Example of voting with comments using the cast_vote_with_comment method
|
|
println!("Adding votes with comments...");
|
|
|
|
// User 7 votes for 'Approve Allocation' with a comment
|
|
proposal = proposal.cast_vote_with_comment(
|
|
Some(110), // ballot_id
|
|
7, // user_id
|
|
1, // chosen_option_id (Approve Allocation)
|
|
80, // shares
|
|
"I strongly support this proposal because it aligns with our community values.",
|
|
);
|
|
|
|
// User 8 votes for 'Reject Allocation' with a comment
|
|
proposal = proposal.cast_vote_with_comment(
|
|
Some(111), // ballot_id
|
|
8, // user_id
|
|
2, // chosen_option_id (Reject Allocation)
|
|
60, // shares
|
|
"I have concerns about the allocation priorities.",
|
|
);
|
|
|
|
println!("\nBallots with Comments:");
|
|
for ballot in &proposal.ballots {
|
|
if let Some(comment) = &ballot.comment {
|
|
println!(
|
|
"- Ballot ID: {}, User ID: {}, Option ID: {}, Shares: {}",
|
|
ballot.base_data.id, ballot.user_id, ballot.vote_option_id, ballot.shares_count
|
|
);
|
|
println!(" Comment: \"{}\"", comment);
|
|
}
|
|
}
|
|
|
|
println!("\nUpdated Vote Counts After Comments:");
|
|
for option in &proposal.options {
|
|
println!(
|
|
"- Option ID: {}, Text: '{}', Votes: {}",
|
|
option.id, option.text, option.count
|
|
);
|
|
}
|
|
|
|
// Change proposal status
|
|
proposal = proposal.change_proposal_status(ProposalStatus::Active);
|
|
println!("Changed Proposal Status to: {:?}", proposal.status);
|
|
|
|
// Simulate closing the vote
|
|
proposal = proposal.change_vote_event_status(VoteEventStatus::Closed);
|
|
println!("Changed Vote Event Status to: {:?}", proposal.vote_status);
|
|
|
|
// Attempt to cast a vote after closing (should be handled)
|
|
println!("\nAttempting to cast vote after voting is closed...");
|
|
proposal = proposal.cast_vote(None, 6, 1, 25);
|
|
|
|
// Final proposal state
|
|
println!("\nFinal Proposal State:");
|
|
println!("Title: '{}'", proposal.title);
|
|
println!("Status: {:?}", proposal.status);
|
|
println!("Vote Status: {:?}", proposal.vote_status);
|
|
println!("Options:");
|
|
for option in &proposal.options {
|
|
println!(
|
|
" - {}: {} (Votes: {})",
|
|
option.id, option.text, option.count
|
|
);
|
|
}
|
|
println!("Total Ballots: {}", proposal.ballots.len());
|
|
|
|
// Example of a private proposal (not fully implemented in cast_vote eligibility yet)
|
|
let mut private_proposal = Proposal::new(
|
|
None, // auto-generated ID
|
|
"user_admin_001",
|
|
"Wael Ghonem",
|
|
"Internal Team Restructure Vote",
|
|
"Vote on proposed internal team changes.",
|
|
ProposalStatus::Draft,
|
|
Utc::now(),
|
|
Utc::now(),
|
|
Utc::now(),
|
|
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",
|
|
Some("This is the accept restructure option".to_string()),
|
|
);
|
|
private_proposal = private_proposal.add_option(
|
|
2,
|
|
"Reject Restructure",
|
|
Some("This is the reject restructure option".to_string()),
|
|
);
|
|
|
|
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");
|
|
private_proposal = saved_private_proposal;
|
|
|
|
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
|
|
private_proposal = private_proposal.cast_vote(Some(201), 10, 1, 100);
|
|
// User 40 (ineligible) tries to vote with auto-generated ballot ID
|
|
private_proposal = private_proposal.cast_vote(None, 40, 1, 50);
|
|
|
|
// Example of voting with comments on a private proposal
|
|
println!("\nAdding votes with comments to private proposal...");
|
|
|
|
// User 20 (eligible) votes with a comment
|
|
private_proposal = private_proposal.cast_vote_with_comment(
|
|
Some(202), // ballot_id
|
|
20, // user_id (eligible)
|
|
1, // chosen_option_id
|
|
75, // shares
|
|
"I support this restructuring plan with some reservations.",
|
|
);
|
|
|
|
// User 30 (eligible) votes with a comment
|
|
private_proposal = private_proposal.cast_vote_with_comment(
|
|
Some(203), // ballot_id
|
|
30, // user_id (eligible)
|
|
2, // chosen_option_id
|
|
90, // shares
|
|
"I believe we should reconsider the timing of these changes.",
|
|
);
|
|
|
|
// User 40 (ineligible) tries to vote with a comment
|
|
private_proposal = private_proposal.cast_vote_with_comment(
|
|
Some(204), // ballot_id
|
|
40, // user_id (ineligible)
|
|
1, // chosen_option_id
|
|
50, // shares
|
|
"This restructuring seems unnecessary.",
|
|
);
|
|
|
|
println!("Eligible users 20 and 30 added votes with comments.");
|
|
println!("Ineligible user 40 attempted to vote with a comment (should be rejected).");
|
|
|
|
println!("\nPrivate Proposal Ballots with Comments:");
|
|
for ballot in &private_proposal.ballots {
|
|
if let Some(comment) = &ballot.comment {
|
|
println!(
|
|
"- Ballot ID: {}, User ID: {}, Option ID: {}, Shares: {}",
|
|
ballot.base_data.id, ballot.user_id, ballot.vote_option_id, ballot.shares_count
|
|
);
|
|
println!(" Comment: \"{}\"", comment);
|
|
}
|
|
}
|
|
|
|
println!("Private Proposal Vote Counts:");
|
|
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
|
|
);
|
|
|
|
// --- 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());
|
|
}
|